From 19fcec84d8d7d21e796c7624e521b60d28ee21ed Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 20:45:59 +0200 Subject: Adding upstream version 16.2.11+ds. Signed-off-by: Daniel Baumann --- src/pybind/mgr/.gitignore | 17 + src/pybind/mgr/.pylintrc | 593 + src/pybind/mgr/CMakeLists.txt | 34 + src/pybind/mgr/alerts/__init__.py | 1 + src/pybind/mgr/alerts/module.py | 264 + src/pybind/mgr/balancer/__init__.py | 1 + src/pybind/mgr/balancer/module.py | 1404 + src/pybind/mgr/ceph_module.pyi | 114 + src/pybind/mgr/cephadm/.gitignore | 2 + src/pybind/mgr/cephadm/HACKING.rst | 272 + src/pybind/mgr/cephadm/Vagrantfile | 66 + src/pybind/mgr/cephadm/__init__.py | 10 + src/pybind/mgr/cephadm/autotune.py | 54 + src/pybind/mgr/cephadm/ceph.repo | 23 + src/pybind/mgr/cephadm/configchecks.py | 705 + src/pybind/mgr/cephadm/inventory.py | 1019 + src/pybind/mgr/cephadm/migrations.py | 333 + src/pybind/mgr/cephadm/module.py | 2974 ++ src/pybind/mgr/cephadm/offline_watcher.py | 70 + src/pybind/mgr/cephadm/registry.py | 65 + src/pybind/mgr/cephadm/remotes.py | 50 + src/pybind/mgr/cephadm/schedule.py | 447 + src/pybind/mgr/cephadm/serve.py | 1487 + src/pybind/mgr/cephadm/services/__init__.py | 0 src/pybind/mgr/cephadm/services/cephadmservice.py | 1043 + src/pybind/mgr/cephadm/services/container.py | 29 + src/pybind/mgr/cephadm/services/exporter.py | 147 + src/pybind/mgr/cephadm/services/ingress.py | 296 + src/pybind/mgr/cephadm/services/iscsi.py | 210 + src/pybind/mgr/cephadm/services/monitoring.py | 502 + src/pybind/mgr/cephadm/services/nfs.py | 290 + src/pybind/mgr/cephadm/services/osd.py | 956 + src/pybind/mgr/cephadm/template.py | 109 + .../cephadm/templates/blink_device_light_cmd.j2 | 1 + .../services/alertmanager/alertmanager.yml.j2 | 47 + .../services/grafana/ceph-dashboard.yml.j2 | 18 + .../templates/services/grafana/grafana.ini.j2 | 24 + .../templates/services/ingress/haproxy.cfg.j2 | 83 + .../templates/services/ingress/keepalived.conf.j2 | 34 + .../templates/services/iscsi/iscsi-gateway.cfg.j2 | 13 + .../cephadm/templates/services/nfs/ganesha.conf.j2 | 35 + .../services/prometheus/prometheus.yml.j2 | 41 + src/pybind/mgr/cephadm/tests/__init__.py | 0 src/pybind/mgr/cephadm/tests/conftest.py | 27 + src/pybind/mgr/cephadm/tests/fixtures.py | 151 + src/pybind/mgr/cephadm/tests/test_autotune.py | 69 + src/pybind/mgr/cephadm/tests/test_cephadm.py | 1805 ++ src/pybind/mgr/cephadm/tests/test_completion.py | 40 + src/pybind/mgr/cephadm/tests/test_configchecks.py | 668 + src/pybind/mgr/cephadm/tests/test_facts.py | 10 + src/pybind/mgr/cephadm/tests/test_migration.py | 229 + src/pybind/mgr/cephadm/tests/test_osd_removal.py | 298 + src/pybind/mgr/cephadm/tests/test_scheduling.py | 1591 ++ src/pybind/mgr/cephadm/tests/test_services.py | 1031 + src/pybind/mgr/cephadm/tests/test_spec.py | 600 + src/pybind/mgr/cephadm/tests/test_template.py | 33 + src/pybind/mgr/cephadm/tests/test_upgrade.py | 322 + src/pybind/mgr/cephadm/upgrade.py | 1167 + src/pybind/mgr/cephadm/utils.py | 136 + src/pybind/mgr/cephadm/vagrant.config.example.json | 13 + src/pybind/mgr/cli_api/__init__.py | 10 + src/pybind/mgr/cli_api/module.py | 120 + src/pybind/mgr/cli_api/tests/__init__.py | 0 src/pybind/mgr/cli_api/tests/test_cli_api.py | 40 + src/pybind/mgr/crash/__init__.py | 1 + src/pybind/mgr/crash/module.py | 412 + src/pybind/mgr/dashboard/.coveragerc | 7 + src/pybind/mgr/dashboard/.editorconfig | 29 + src/pybind/mgr/dashboard/.gitignore | 15 + src/pybind/mgr/dashboard/.pylintrc | 541 + src/pybind/mgr/dashboard/CMakeLists.txt | 141 + src/pybind/mgr/dashboard/HACKING.rst | 10 + src/pybind/mgr/dashboard/README.rst | 35 + src/pybind/mgr/dashboard/__init__.py | 61 + src/pybind/mgr/dashboard/api/__init__.py | 1 + src/pybind/mgr/dashboard/api/doc.py | 53 + src/pybind/mgr/dashboard/awsauth.py | 165 + src/pybind/mgr/dashboard/cherrypy_backports.py | 199 + .../mgr/dashboard/ci/cephadm/bootstrap-cluster.sh | 36 + .../mgr/dashboard/ci/cephadm/ceph_cluster.yml | 45 + .../dashboard/ci/cephadm/run-cephadm-e2e-tests.sh | 50 + .../mgr/dashboard/ci/cephadm/start-cluster.sh | 92 + .../mgr/dashboard/ci/check_grafana_dashboards.py | 179 + src/pybind/mgr/dashboard/constraints.txt | 7 + src/pybind/mgr/dashboard/controllers/__init__.py | 35 + .../mgr/dashboard/controllers/_api_router.py | 13 + src/pybind/mgr/dashboard/controllers/_auth.py | 18 + .../mgr/dashboard/controllers/_base_controller.py | 314 + src/pybind/mgr/dashboard/controllers/_docs.py | 128 + src/pybind/mgr/dashboard/controllers/_endpoint.py | 82 + src/pybind/mgr/dashboard/controllers/_helpers.py | 127 + .../mgr/dashboard/controllers/_permissions.py | 60 + .../mgr/dashboard/controllers/_rest_controller.py | 249 + src/pybind/mgr/dashboard/controllers/_router.py | 69 + src/pybind/mgr/dashboard/controllers/_task.py | 79 + src/pybind/mgr/dashboard/controllers/_ui_router.py | 13 + src/pybind/mgr/dashboard/controllers/_version.py | 75 + src/pybind/mgr/dashboard/controllers/auth.py | 123 + src/pybind/mgr/dashboard/controllers/cephfs.py | 558 + src/pybind/mgr/dashboard/controllers/cluster.py | 21 + .../dashboard/controllers/cluster_configuration.py | 133 + src/pybind/mgr/dashboard/controllers/crush_rule.py | 65 + src/pybind/mgr/dashboard/controllers/daemon.py | 33 + src/pybind/mgr/dashboard/controllers/docs.py | 429 + .../dashboard/controllers/erasure_code_profile.py | 66 + .../mgr/dashboard/controllers/frontend_logging.py | 15 + src/pybind/mgr/dashboard/controllers/grafana.py | 51 + src/pybind/mgr/dashboard/controllers/health.py | 294 + src/pybind/mgr/dashboard/controllers/home.py | 149 + src/pybind/mgr/dashboard/controllers/host.py | 507 + src/pybind/mgr/dashboard/controllers/iscsi.py | 1075 + src/pybind/mgr/dashboard/controllers/logs.py | 73 + .../mgr/dashboard/controllers/mgr_modules.py | 197 + src/pybind/mgr/dashboard/controllers/monitor.py | 134 + src/pybind/mgr/dashboard/controllers/nfs.py | 280 + .../mgr/dashboard/controllers/orchestrator.py | 48 + src/pybind/mgr/dashboard/controllers/osd.py | 655 + .../mgr/dashboard/controllers/perf_counters.py | 83 + src/pybind/mgr/dashboard/controllers/pool.py | 352 + src/pybind/mgr/dashboard/controllers/prometheus.py | 101 + src/pybind/mgr/dashboard/controllers/rbd.py | 561 + .../mgr/dashboard/controllers/rbd_mirroring.py | 639 + src/pybind/mgr/dashboard/controllers/rgw.py | 530 + src/pybind/mgr/dashboard/controllers/role.py | 144 + src/pybind/mgr/dashboard/controllers/saml2.py | 111 + src/pybind/mgr/dashboard/controllers/service.py | 89 + src/pybind/mgr/dashboard/controllers/settings.py | 141 + src/pybind/mgr/dashboard/controllers/summary.py | 124 + src/pybind/mgr/dashboard/controllers/task.py | 47 + src/pybind/mgr/dashboard/controllers/telemetry.py | 240 + src/pybind/mgr/dashboard/controllers/user.py | 215 + src/pybind/mgr/dashboard/exceptions.py | 124 + src/pybind/mgr/dashboard/frontend/.browserslistrc | 11 + src/pybind/mgr/dashboard/frontend/.editorconfig | 13 + src/pybind/mgr/dashboard/frontend/.gherkin-lintrc | 33 + src/pybind/mgr/dashboard/frontend/.gitignore | 49 + src/pybind/mgr/dashboard/frontend/.htmllintrc | 70 + src/pybind/mgr/dashboard/frontend/.npmrc | 2 + src/pybind/mgr/dashboard/frontend/.prettierignore | 1 + src/pybind/mgr/dashboard/frontend/.prettierrc | 6 + src/pybind/mgr/dashboard/frontend/.stylelintrc | 43 + src/pybind/mgr/dashboard/frontend/angular.json | 227 + .../mgr/dashboard/frontend/applitools.config.js | 21 + src/pybind/mgr/dashboard/frontend/babel.config.js | 11 + src/pybind/mgr/dashboard/frontend/cd.js | 166 + src/pybind/mgr/dashboard/frontend/cypress.json | 22 + .../cypress/fixtures/block-rbd-status.json | 1 + .../cypress/fixtures/nfs-ganesha-status.json | 4 + .../cypress/fixtures/orchestrator/inventory.json | 390 + .../cypress/fixtures/orchestrator/services.json | 523 + .../frontend/cypress/fixtures/rgw-status.json | 1 + .../cypress/integration/block/images.e2e-spec.ts | 95 + .../cypress/integration/block/images.po.ts | 110 + .../cypress/integration/block/iscsi.e2e-spec.ts | 25 + .../frontend/cypress/integration/block/iscsi.po.ts | 7 + .../integration/block/mirroring.e2e-spec.ts | 54 + .../cypress/integration/block/mirroring.po.ts | 35 + .../integration/cluster/configuration.e2e-spec.ts | 78 + .../integration/cluster/configuration.po.ts | 75 + .../integration/cluster/create-cluster.po.ts | 56 + .../integration/cluster/crush-map.e2e-spec.ts | 37 + .../cypress/integration/cluster/crush-map.po.ts | 13 + .../cypress/integration/cluster/hosts.e2e-spec.ts | 35 + .../cypress/integration/cluster/hosts.po.ts | 184 + .../cypress/integration/cluster/inventory.po.ts | 22 + .../cypress/integration/cluster/logs.e2e-spec.ts | 58 + .../cypress/integration/cluster/logs.po.ts | 70 + .../integration/cluster/mgr-modules.e2e-spec.ts | 78 + .../cypress/integration/cluster/mgr-modules.po.ts | 57 + .../integration/cluster/monitors.e2e-spec.ts | 62 + .../cypress/integration/cluster/monitors.po.ts | 7 + .../cypress/integration/cluster/osds.e2e-spec.ts | 57 + .../cypress/integration/cluster/osds.po.ts | 84 + .../cypress/integration/cluster/services.po.ts | 204 + .../integration/common/01-global.feature.po.ts | 188 + .../create-cluster/create-cluster.feature.po.ts | 12 + .../integration/common/grafana.feature.po.ts | 86 + .../frontend/cypress/integration/common/urls.po.ts | 44 + .../filesystems/filesystems.e2e-spec.ts | 17 + .../integration/filesystems/filesystems.po.ts | 5 + .../integration/orchestrator/01-hosts.e2e-spec.ts | 86 + .../orchestrator/03-inventory.e2e-spec.ts | 26 + .../integration/orchestrator/04-osds.e2e-spec.ts | 50 + .../orchestrator/05-services.e2e-spec.ts | 36 + .../orchestrator/grafana/grafana.feature | 63 + .../workflow/01-create-cluster-welcome.feature | 26 + .../workflow/02-create-cluster-add-host.feature | 74 + .../03-create-cluster-create-services.e2e-spec.ts | 47 + .../04-create-cluster-create-osds.e2e-spec.ts | 41 + .../workflow/05-create-cluster-review.e2e-spec.ts | 67 + .../workflow/06-cluster-check.e2e-spec.ts | 99 + .../orchestrator/workflow/07-osds.e2e-spec.ts | 24 + .../orchestrator/workflow/08-hosts.e2e-spec.ts | 49 + .../orchestrator/workflow/09-services.e2e-spec.ts | 114 + .../workflow/10-nfs-exports.e2e-spec.ts | 83 + .../orchestrator/workflow/nfs/nfs-export.po.ts | 52 + .../frontend/cypress/integration/page-helper.po.ts | 309 + .../cypress/integration/pools/pools.e2e-spec.ts | 54 + .../frontend/cypress/integration/pools/pools.po.ts | 70 + .../cypress/integration/rgw/buckets.e2e-spec.ts | 62 + .../frontend/cypress/integration/rgw/buckets.po.ts | 193 + .../cypress/integration/rgw/daemons.e2e-spec.ts | 35 + .../frontend/cypress/integration/rgw/daemons.po.ts | 34 + .../cypress/integration/rgw/users.e2e-spec.ts | 46 + .../frontend/cypress/integration/rgw/users.po.ts | 139 + .../cypress/integration/ui/api-docs.e2e-spec.ts | 15 + .../frontend/cypress/integration/ui/api-docs.po.ts | 5 + .../cypress/integration/ui/dashboard.e2e-spec.ts | 124 + .../cypress/integration/ui/dashboard.po.ts | 31 + .../cypress/integration/ui/language.e2e-spec.ts | 20 + .../frontend/cypress/integration/ui/language.po.ts | 15 + .../cypress/integration/ui/login.e2e-spec.ts | 17 + .../frontend/cypress/integration/ui/login.po.ts | 22 + .../cypress/integration/ui/navigation.e2e-spec.ts | 24 + .../cypress/integration/ui/navigation.po.ts | 69 + .../integration/ui/notification.e2e-spec.ts | 59 + .../cypress/integration/ui/notification.po.ts | 45 + .../cypress/integration/ui/role-mgmt.e2e-spec.ts | 37 + .../cypress/integration/ui/role-mgmt.po.ts | 40 + .../cypress/integration/ui/user-mgmt.e2e-spec.ts | 37 + .../cypress/integration/ui/user-mgmt.po.ts | 39 + .../integration/visualTests/dashboard.vrt-spec.ts | 22 + .../integration/visualTests/login.vrt-spec.ts | 19 + .../dashboard/frontend/cypress/plugins/index.js | 26 + .../dashboard/frontend/cypress/support/commands.ts | 59 + .../frontend/cypress/support/eyes-index.d.ts | 1 + .../dashboard/frontend/cypress/support/index.ts | 11 + .../mgr/dashboard/frontend/cypress/tsconfig.json | 16 + .../dist/en-US/117.9781bbf8cc6a4aaa7e8e.js | 1 + .../dist/en-US/281.0d0cd268ddc6a6760dd4.js | 1 + .../frontend/dist/en-US/3rdpartylicenses.txt | 3508 +++ .../dist/en-US/483.f42c1d67e206231ecdac.js | 1 + .../dist/en-US/Ceph_Logo.487a0001b327fa7f5232.svg | 71 + .../assets/Ceph_Ceph_Logo_with_text_red_white.svg | 69 + .../assets/Ceph_Ceph_Logo_with_text_white.svg | 69 + .../frontend/dist/en-US/assets/Ceph_Logo.svg | 71 + .../frontend/dist/en-US/assets/ceph_background.gif | Bin 0 -> 98115 bytes .../frontend/dist/en-US/assets/loading.gif | Bin 0 -> 35386 bytes .../frontend/dist/en-US/assets/logo-mini.png | Bin 0 -> 1811 bytes .../frontend/dist/en-US/assets/prometheus_logo.svg | 50 + .../en-US/ceph_background.e82dd79127290ddbe8cb.gif | Bin 0 -> 98115 bytes .../mgr/dashboard/frontend/dist/en-US/favicon.ico | Bin 0 -> 1150 bytes .../forkawesome-webfont.2dfb5f36fc148e26e398.woff | Bin 0 -> 115148 bytes .../forkawesome-webfont.7c20758e3e7c7dff7c8d.woff2 | Bin 0 -> 91624 bytes .../forkawesome-webfont.86541105409e56d17291.svg | 2849 ++ .../forkawesome-webfont.e182ad6df04f9177b326.eot | Bin 0 -> 188946 bytes .../forkawesome-webfont.ee4d8bfd0af89fc714a2.ttf | Bin 0 -> 188756 bytes .../mgr/dashboard/frontend/dist/en-US/index.html | 23 + .../dist/en-US/main.c3b711a3156fe72f66f4.js | 3 + .../dist/en-US/polyfills.2068f3f22a496426465b.js | 1 + .../en-US/prometheus_logo.8b3183e5a2db0e87bb2b.svg | 50 + .../dist/en-US/runtime.dfeb6a20b4d203b567dc.js | 1 + .../dist/en-US/scripts.6bda3fa7e09a87cd4228.js | 7 + .../dist/en-US/styles.f05c06a6a64f4730faae.css | 20 + .../mgr/dashboard/frontend/html-linter.config.json | 12 + src/pybind/mgr/dashboard/frontend/i18n.config.json | 12 + src/pybind/mgr/dashboard/frontend/ngcc.config.js | 10 + .../mgr/dashboard/frontend/package-lock.json | 28313 +++++++++++++++++++ src/pybind/mgr/dashboard/frontend/package.json | 157 + .../mgr/dashboard/frontend/proxy.conf.json.sample | 12 + .../frontend/src/app/app-routing.module.ts | 392 + .../dashboard/frontend/src/app/app.component.html | 1 + .../dashboard/frontend/src/app/app.component.scss | 0 .../frontend/src/app/app.component.spec.ts | 25 + .../dashboard/frontend/src/app/app.component.ts | 18 + .../mgr/dashboard/frontend/src/app/app.module.ts | 51 + .../frontend/src/app/ceph/block/block.module.ts | 205 + .../iscsi-setting/iscsi-setting.component.html | 57 + .../iscsi-setting/iscsi-setting.component.scss | 0 .../iscsi-setting/iscsi-setting.component.spec.ts | 37 + .../block/iscsi-setting/iscsi-setting.component.ts | 31 + .../block/iscsi-tabs/iscsi-tabs.component.html | 14 + .../block/iscsi-tabs/iscsi-tabs.component.scss | 0 .../block/iscsi-tabs/iscsi-tabs.component.spec.ts | 28 + .../ceph/block/iscsi-tabs/iscsi-tabs.component.ts | 11 + .../iscsi-target-details.component.html | 41 + .../iscsi-target-details.component.scss | 0 .../iscsi-target-details.component.spec.ts | 207 + .../iscsi-target-details.component.ts | 346 + .../iscsi-target-discovery-modal.component.html | 132 + .../iscsi-target-discovery-modal.component.scss | 0 .../iscsi-target-discovery-modal.component.spec.ts | 133 + .../iscsi-target-discovery-modal.component.ts | 123 + .../iscsi-target-form.component.html | 692 + .../iscsi-target-form.component.scss | 3 + .../iscsi-target-form.component.spec.ts | 593 + .../iscsi-target-form.component.ts | 822 + ...scsi-target-image-settings-modal.component.html | 92 + ...scsi-target-image-settings-modal.component.scss | 0 ...i-target-image-settings-modal.component.spec.ts | 98 + .../iscsi-target-image-settings-modal.component.ts | 87 + .../iscsi-target-iqn-settings-modal.component.html | 32 + .../iscsi-target-iqn-settings-modal.component.scss | 0 ...csi-target-iqn-settings-modal.component.spec.ts | 71 + .../iscsi-target-iqn-settings-modal.component.ts | 60 + .../iscsi-target-list.component.html | 53 + .../iscsi-target-list.component.scss | 0 .../iscsi-target-list.component.spec.ts | 309 + .../iscsi-target-list.component.ts | 242 + .../src/app/ceph/block/iscsi/iscsi.component.html | 49 + .../src/app/ceph/block/iscsi/iscsi.component.scss | 0 .../app/ceph/block/iscsi/iscsi.component.spec.ts | 83 + .../src/app/ceph/block/iscsi/iscsi.component.ts | 117 + .../bootstrap-create-modal.component.html | 87 + .../bootstrap-create-modal.component.scss | 3 + .../bootstrap-create-modal.component.spec.ts | 113 + .../bootstrap-create-modal.component.ts | 153 + .../bootstrap-import-modal.component.html | 96 + .../bootstrap-import-modal.component.scss | 0 .../bootstrap-import-modal.component.spec.ts | 131 + .../bootstrap-import-modal.component.ts | 187 + .../daemon-list/daemon-list.component.html | 13 + .../daemon-list/daemon-list.component.scss | 0 .../daemon-list/daemon-list.component.spec.ts | 28 + .../mirroring/daemon-list/daemon-list.component.ts | 62 + .../mirroring/image-list/image-list.component.html | 63 + .../mirroring/image-list/image-list.component.scss | 0 .../image-list/image-list.component.spec.ts | 36 + .../mirroring/image-list/image-list.component.ts | 99 + .../mirroring/mirror-health-color.pipe.spec.ts | 25 + .../block/mirroring/mirror-health-color.pipe.ts | 17 + .../app/ceph/block/mirroring/mirroring.module.ts | 42 + .../mirroring/overview/overview.component.html | 66 + .../mirroring/overview/overview.component.scss | 0 .../mirroring/overview/overview.component.spec.ts | 79 + .../block/mirroring/overview/overview.component.ts | 122 + .../pool-edit-mode-modal.component.html | 44 + .../pool-edit-mode-modal.component.scss | 0 .../pool-edit-mode-modal.component.spec.ts | 86 + .../pool-edit-mode-modal.component.ts | 111 + .../pool-edit-mode-response.model.ts | 3 + .../pool-edit-peer-modal.component.html | 100 + .../pool-edit-peer-modal.component.scss | 0 .../pool-edit-peer-modal.component.spec.ts | 148 + .../pool-edit-peer-modal.component.ts | 141 + .../pool-edit-peer-response.model.ts | 7 + .../mirroring/pool-list/pool-list.component.html | 23 + .../mirroring/pool-list/pool-list.component.scss | 0 .../pool-list/pool-list.component.spec.ts | 37 + .../mirroring/pool-list/pool-list.component.ts | 174 + .../rbd-configuration-form.component.html | 74 + .../rbd-configuration-form.component.scss | 4 + .../rbd-configuration-form.component.spec.ts | 294 + .../rbd-configuration-form.component.ts | 166 + .../rbd-configuration-list.component.html | 29 + .../rbd-configuration-list.component.scss | 0 .../rbd-configuration-list.component.spec.ts | 99 + .../rbd-configuration-list.component.ts | 65 + .../block/rbd-details/rbd-details.component.html | 180 + .../block/rbd-details/rbd-details.component.scss | 0 .../rbd-details/rbd-details.component.spec.ts | 30 + .../block/rbd-details/rbd-details.component.ts | 31 + .../ceph/block/rbd-form/rbd-feature.interface.ts | 9 + .../block/rbd-form/rbd-form-clone-request.model.ts | 13 + .../block/rbd-form/rbd-form-copy-request.model.ts | 14 + .../rbd-form/rbd-form-create-request.model.ts | 5 + .../block/rbd-form/rbd-form-edit-request.model.ts | 14 + .../app/ceph/block/rbd-form/rbd-form-mode.enum.ts | 5 + .../ceph/block/rbd-form/rbd-form-response.model.ts | 7 + .../ceph/block/rbd-form/rbd-form.component.html | 395 + .../ceph/block/rbd-form/rbd-form.component.scss | 0 .../ceph/block/rbd-form/rbd-form.component.spec.ts | 480 + .../app/ceph/block/rbd-form/rbd-form.component.ts | 815 + .../src/app/ceph/block/rbd-form/rbd-form.model.ts | 26 + .../app/ceph/block/rbd-form/rbd-parent.model.ts | 6 + .../ceph/block/rbd-list/rbd-list.component.html | 128 + .../ceph/block/rbd-list/rbd-list.component.scss | 5 + .../ceph/block/rbd-list/rbd-list.component.spec.ts | 438 + .../app/ceph/block/rbd-list/rbd-list.component.ts | 629 + .../src/app/ceph/block/rbd-list/rbd-model.ts | 15 + .../rbd-namespace-form-modal.component.html | 79 + .../rbd-namespace-form-modal.component.scss | 0 .../rbd-namespace-form-modal.component.spec.ts | 39 + .../rbd-namespace-form-modal.component.ts | 144 + .../rbd-namespace-list.component.html | 18 + .../rbd-namespace-list.component.scss | 0 .../rbd-namespace-list.component.spec.ts | 41 + .../rbd-namespace-list.component.ts | 157 + .../rbd-performance/rbd-performance.component.html | 6 + .../rbd-performance/rbd-performance.component.scss | 0 .../rbd-performance.component.spec.ts | 30 + .../rbd-performance/rbd-performance.component.ts | 8 + .../rbd-snapshot-form-modal.component.html | 41 + .../rbd-snapshot-form-modal.component.scss | 0 .../rbd-snapshot-form-modal.component.spec.ts | 62 + .../rbd-snapshot-form-modal.component.ts | 137 + .../rbd-snapshot-actions.model.ts | 131 + .../rbd-snapshot-list.component.html | 17 + .../rbd-snapshot-list.component.scss | 0 .../rbd-snapshot-list.component.spec.ts | 305 + .../rbd-snapshot-list.component.ts | 336 + .../block/rbd-snapshot-list/rbd-snapshot.model.ts | 9 + .../ceph/block/rbd-tabs/rbd-tabs.component.html | 23 + .../ceph/block/rbd-tabs/rbd-tabs.component.scss | 0 .../ceph/block/rbd-tabs/rbd-tabs.component.spec.ts | 27 + .../app/ceph/block/rbd-tabs/rbd-tabs.component.ts | 19 + .../rbd-trash-list/rbd-trash-list.component.html | 52 + .../rbd-trash-list/rbd-trash-list.component.scss | 0 .../rbd-trash-list.component.spec.ts | 172 + .../rbd-trash-list/rbd-trash-list.component.ts | 225 + .../rbd-trash-move-modal.component.html | 57 + .../rbd-trash-move-modal.component.scss | 0 .../rbd-trash-move-modal.component.spec.ts | 94 + .../rbd-trash-move-modal.component.ts | 94 + .../rbd-trash-purge-modal.component.html | 46 + .../rbd-trash-purge-modal.component.scss | 0 .../rbd-trash-purge-modal.component.spec.ts | 105 + .../rbd-trash-purge-modal.component.ts | 74 + .../rbd-trash-restore-modal.component.html | 41 + .../rbd-trash-restore-modal.component.scss | 0 .../rbd-trash-restore-modal.component.spec.ts | 81 + .../rbd-trash-restore-modal.component.ts | 65 + .../dashboard/frontend/src/app/ceph/ceph.module.ts | 23 + .../cephfs-chart/cephfs-chart.component.html | 12 + .../cephfs-chart/cephfs-chart.component.scss | 8 + .../cephfs-chart/cephfs-chart.component.spec.ts | 81 + .../cephfs/cephfs-chart/cephfs-chart.component.ts | 196 + .../cephfs-clients/cephfs-clients.component.html | 13 + .../cephfs-clients/cephfs-clients.component.scss | 0 .../cephfs-clients.component.spec.ts | 83 + .../cephfs-clients/cephfs-clients.component.ts | 102 + .../cephfs-detail/cephfs-detail.component.html | 42 + .../cephfs-detail/cephfs-detail.component.scss | 3 + .../cephfs-detail/cephfs-detail.component.spec.ts | 55 + .../cephfs-detail/cephfs-detail.component.ts | 91 + .../cephfs-directories.component.html | 75 + .../cephfs-directories.component.scss | 17 + .../cephfs-directories.component.spec.ts | 1111 + .../cephfs-directories.component.ts | 733 + .../cephfs/cephfs-list/cephfs-list.component.html | 14 + .../cephfs/cephfs-list/cephfs-list.component.scss | 0 .../cephfs-list/cephfs-list.component.spec.ts | 35 + .../cephfs/cephfs-list/cephfs-list.component.ts | 61 + .../cephfs/cephfs-tabs/cephfs-tabs.component.html | 47 + .../cephfs/cephfs-tabs/cephfs-tabs.component.scss | 0 .../cephfs-tabs/cephfs-tabs.component.spec.ts | 215 + .../cephfs/cephfs-tabs/cephfs-tabs.component.ts | 130 + .../frontend/src/app/ceph/cephfs/cephfs.module.ts | 28 + .../src/app/ceph/cluster/cluster.module.ts | 123 + .../configuration-details.component.html | 105 + .../configuration-details.component.scss | 0 .../configuration-details.component.spec.ts | 26 + .../configuration-details.component.ts | 29 + .../configuration-form-create-request.model.ts | 4 + .../configuration-form.component.html | 160 + .../configuration-form.component.scss | 12 + .../configuration-form.component.spec.ts | 106 + .../configuration-form.component.ts | 172 + .../configuration/configuration.component.html | 26 + .../configuration/configuration.component.scss | 16 + .../configuration/configuration.component.spec.ts | 46 + .../configuration/configuration.component.ts | 149 + .../create-cluster-review.component.html | 54 + .../create-cluster-review.component.scss | 5 + .../create-cluster-review.component.spec.ts | 29 + .../create-cluster-review.component.ts | 72 + .../create-cluster/create-cluster.component.html | 98 + .../create-cluster/create-cluster.component.scss | 22 + .../create-cluster.component.spec.ts | 154 + .../create-cluster/create-cluster.component.ts | 231 + .../ceph/cluster/crushmap/crushmap.component.html | 39 + .../ceph/cluster/crushmap/crushmap.component.scss | 3 + .../cluster/crushmap/crushmap.component.spec.ts | 137 + .../ceph/cluster/crushmap/crushmap.component.ts | 122 + .../cluster/hosts/fixtures/host_list_response.json | 32 + .../hosts/host-details/host-details.component.html | 59 + .../hosts/host-details/host-details.component.scss | 0 .../host-details/host-details.component.spec.ts | 68 + .../hosts/host-details/host-details.component.ts | 20 + .../hosts/host-form/host-form.component.html | 107 + .../hosts/host-form/host-form.component.scss | 0 .../hosts/host-form/host-form.component.spec.ts | 168 + .../cluster/hosts/host-form/host-form.component.ts | 171 + .../app/ceph/cluster/hosts/hosts.component.html | 77 + .../app/ceph/cluster/hosts/hosts.component.scss | 0 .../app/ceph/cluster/hosts/hosts.component.spec.ts | 419 + .../src/app/ceph/cluster/hosts/hosts.component.ts | 535 + .../fixtures/inventory_list_response.json | 324 + .../inventory-devices/inventory-device.model.ts | 20 + .../inventory-devices.component.html | 16 + .../inventory-devices.component.scss | 12 + .../inventory-devices.component.spec.ts | 194 + .../inventory-devices.component.ts | 254 + .../ceph/cluster/inventory/inventory-host.model.ts | 6 + .../cluster/inventory/inventory.component.html | 14 + .../cluster/inventory/inventory.component.scss | 0 .../cluster/inventory/inventory.component.spec.ts | 67 + .../ceph/cluster/inventory/inventory.component.ts | 90 + .../src/app/ceph/cluster/logs/logs.component.html | 159 + .../src/app/ceph/cluster/logs/logs.component.scss | 54 + .../app/ceph/cluster/logs/logs.component.spec.ts | 169 + .../src/app/ceph/cluster/logs/logs.component.ts | 157 + .../mgr-module-details.component.html | 4 + .../mgr-module-details.component.scss | 0 .../mgr-module-details.component.spec.ts | 27 + .../mgr-module-details.component.ts | 25 + .../mgr-module-form/mgr-module-form.component.html | 110 + .../mgr-module-form/mgr-module-form.component.scss | 0 .../mgr-module-form.component.spec.ts | 80 + .../mgr-module-form/mgr-module-form.component.ts | 135 + .../mgr-module-list/mgr-module-list.component.html | 20 + .../mgr-module-list/mgr-module-list.component.scss | 0 .../mgr-module-list.component.spec.ts | 155 + .../mgr-module-list/mgr-module-list.component.ts | 198 + .../ceph/cluster/mgr-modules/mgr-modules.module.ts | 17 + .../ceph/cluster/monitor/monitor.component.html | 61 + .../ceph/cluster/monitor/monitor.component.scss | 0 .../ceph/cluster/monitor/monitor.component.spec.ts | 105 + .../app/ceph/cluster/monitor/monitor.component.ts | 74 + .../osd-creation-preview-modal.component.html | 20 + .../osd-creation-preview-modal.component.scss | 0 .../osd-creation-preview-modal.component.spec.ts | 38 + .../osd-creation-preview-modal.component.ts | 62 + .../osd/osd-details/osd-details.component.html | 67 + .../osd/osd-details/osd-details.component.scss | 0 .../osd/osd-details/osd-details.component.spec.ts | 31 + .../osd/osd-details/osd-details.component.ts | 44 + .../devices-selection-change-event.interface.ts | 5 + .../devices-selection-clear-event.interface.ts | 6 + .../osd-devices-selection-groups.component.html | 51 + .../osd-devices-selection-groups.component.scss | 3 + .../osd-devices-selection-groups.component.spec.ts | 125 + .../osd-devices-selection-groups.component.ts | 135 + .../osd-devices-selection-modal.component.html | 42 + .../osd-devices-selection-modal.component.scss | 0 .../osd-devices-selection-modal.component.spec.ts | 109 + .../osd-devices-selection-modal.component.ts | 92 + .../osd-flags-indiv-modal.component.html | 48 + .../osd-flags-indiv-modal.component.scss | 0 .../osd-flags-indiv-modal.component.spec.ts | 353 + .../osd-flags-indiv-modal.component.ts | 134 + .../osd-flags-modal/osd-flags-modal.component.html | 41 + .../osd-flags-modal/osd-flags-modal.component.scss | 0 .../osd-flags-modal.component.spec.ts | 99 + .../osd-flags-modal/osd-flags-modal.component.ts | 156 + .../ceph/cluster/osd/osd-form/drive-group.model.ts | 97 + .../cluster/osd/osd-form/osd-feature.interface.ts | 4 + .../cluster/osd/osd-form/osd-form.component.html | 213 + .../cluster/osd/osd-form/osd-form.component.scss | 0 .../osd/osd-form/osd-form.component.spec.ts | 309 + .../cluster/osd/osd-form/osd-form.component.ts | 285 + .../osd/osd-list/fixtures/osd_list_response.json | 605 + .../cluster/osd/osd-list/osd-list.component.html | 150 + .../cluster/osd/osd-list/osd-list.component.scss | 0 .../osd/osd-list/osd-list.component.spec.ts | 641 + .../cluster/osd/osd-list/osd-list.component.ts | 624 + .../osd-pg-scrub-modal.component.html | 45 + .../osd-pg-scrub-modal.component.scss | 0 .../osd-pg-scrub-modal.component.spec.ts | 64 + .../osd-pg-scrub-modal.component.ts | 68 + .../osd-pg-scrub-modal.options.ts | 36 + .../osd-recv-speed-modal.component.html | 92 + .../osd-recv-speed-modal.component.scss | 0 .../osd-recv-speed-modal.component.spec.ts | 317 + .../osd-recv-speed-modal.component.ts | 238 + .../osd-reweight-modal.component.html | 38 + .../osd-reweight-modal.component.scss | 0 .../osd-reweight-modal.component.spec.ts | 56 + .../osd-reweight-modal.component.ts | 43 + .../osd-scrub-modal/osd-scrub-modal.component.html | 22 + .../osd-scrub-modal/osd-scrub-modal.component.scss | 0 .../osd-scrub-modal.component.spec.ts | 50 + .../osd-scrub-modal/osd-scrub-modal.component.ts | 52 + .../active-alert-list.component.html | 41 + .../active-alert-list.component.scss | 0 .../active-alert-list.component.spec.ts | 103 + .../active-alert-list.component.ts | 101 + .../cluster/prometheus/prometheus-list-helper.ts | 24 + .../prometheus-tabs/prometheus-tabs.component.html | 18 + .../prometheus-tabs/prometheus-tabs.component.scss | 0 .../prometheus-tabs.component.spec.ts | 27 + .../prometheus-tabs/prometheus-tabs.component.ts | 11 + .../rules-list/rules-list.component.html | 22 + .../rules-list/rules-list.component.scss | 0 .../rules-list/rules-list.component.spec.ts | 40 + .../prometheus/rules-list/rules-list.component.ts | 44 + .../silence-form/silence-form.component.html | 224 + .../silence-form/silence-form.component.scss | 3 + .../silence-form/silence-form.component.spec.ts | 598 + .../silence-form/silence-form.component.ts | 340 + .../silence-list/silence-list.component.html | 34 + .../silence-list/silence-list.component.scss | 0 .../silence-list/silence-list.component.spec.ts | 140 + .../silence-list/silence-list.component.ts | 191 + .../silence-matcher-modal.component.html | 85 + .../silence-matcher-modal.component.scss | 0 .../silence-matcher-modal.component.spec.ts | 209 + .../silence-matcher-modal.component.ts | 107 + .../ceph/cluster/services/placement.pipe.spec.ts | 78 + .../app/ceph/cluster/services/placement.pipe.ts | 41 + .../service-daemon-list.component.html | 102 + .../service-daemon-list.component.scss | 14 + .../service-daemon-list.component.spec.ts | 253 + .../service-daemon-list.component.ts | 347 + .../service-details/service-details.component.html | 4 + .../service-details/service-details.component.scss | 0 .../service-details.component.spec.ts | 43 + .../service-details/service-details.component.ts | 17 + .../service-form/service-form.component.html | 696 + .../service-form/service-form.component.scss | 0 .../service-form/service-form.component.spec.ts | 550 + .../service-form/service-form.component.ts | 678 + .../ceph/cluster/services/services.component.html | 25 + .../ceph/cluster/services/services.component.scss | 0 .../cluster/services/services.component.spec.ts | 97 + .../ceph/cluster/services/services.component.ts | 259 + .../cluster/telemetry/telemetry.component.html | 322 + .../cluster/telemetry/telemetry.component.scss | 0 .../cluster/telemetry/telemetry.component.spec.ts | 188 + .../ceph/cluster/telemetry/telemetry.component.ts | 244 + .../src/app/ceph/dashboard/dashboard.module.ts | 43 + .../dashboard/dashboard/dashboard.component.html | 28 + .../dashboard/dashboard/dashboard.component.scss | 3 + .../dashboard/dashboard.component.spec.ts | 28 + .../dashboard/dashboard/dashboard.component.ts | 10 + .../dashboard/health-pie/health-pie.component.html | 16 + .../dashboard/health-pie/health-pie.component.scss | 22 + .../health-pie/health-pie.component.spec.ts | 75 + .../dashboard/health-pie/health-pie.component.ts | 198 + .../ceph/dashboard/health/health.component.html | 237 + .../ceph/dashboard/health/health.component.scss | 41 + .../ceph/dashboard/health/health.component.spec.ts | 348 + .../app/ceph/dashboard/health/health.component.ts | 278 + .../dashboard/info-card/info-card-popover.scss | 42 + .../dashboard/info-card/info-card.component.html | 18 + .../dashboard/info-card/info-card.component.scss | 40 + .../info-card/info-card.component.spec.ts | 65 + .../dashboard/info-card/info-card.component.ts | 17 + .../dashboard/info-group/info-group.component.html | 26 + .../dashboard/info-group/info-group.component.scss | 8 + .../info-group/info-group.component.spec.ts | 35 + .../dashboard/info-group/info-group.component.ts | 14 + .../app/ceph/dashboard/mds-summary.pipe.spec.ts | 72 + .../src/app/ceph/dashboard/mds-summary.pipe.ts | 78 + .../app/ceph/dashboard/mgr-summary.pipe.spec.ts | 52 + .../src/app/ceph/dashboard/mgr-summary.pipe.ts | 48 + .../app/ceph/dashboard/mon-summary.pipe.spec.ts | 40 + .../src/app/ceph/dashboard/mon-summary.pipe.ts | 17 + .../app/ceph/dashboard/osd-summary.pipe.spec.ts | 193 + .../src/app/ceph/dashboard/osd-summary.pipe.ts | 91 + .../frontend/src/app/ceph/nfs/models/nfs.fsal.ts | 5 + .../nfs/nfs-details/nfs-details.component.html | 32 + .../nfs/nfs-details/nfs-details.component.scss | 0 .../nfs/nfs-details/nfs-details.component.spec.ts | 102 + .../ceph/nfs/nfs-details/nfs-details.component.ts | 68 + .../nfs-form-client/nfs-form-client.component.html | 109 + .../nfs-form-client/nfs-form-client.component.scss | 0 .../nfs-form-client.component.spec.ts | 71 + .../nfs-form-client/nfs-form-client.component.ts | 95 + .../app/ceph/nfs/nfs-form/nfs-form.component.html | 400 + .../app/ceph/nfs/nfs-form/nfs-form.component.scss | 11 + .../ceph/nfs/nfs-form/nfs-form.component.spec.ts | 238 + .../app/ceph/nfs/nfs-form/nfs-form.component.ts | 535 + .../app/ceph/nfs/nfs-list/nfs-list.component.html | 30 + .../app/ceph/nfs/nfs-list/nfs-list.component.scss | 0 .../ceph/nfs/nfs-list/nfs-list.component.spec.ts | 195 + .../app/ceph/nfs/nfs-list/nfs-list.component.ts | 199 + .../frontend/src/app/ceph/nfs/nfs.module.ts | 26 + .../performance-counter.module.ts | 14 + .../performance-counter.component.html | 4 + .../performance-counter.component.scss | 0 .../performance-counter.component.spec.ts | 29 + .../performance-counter.component.ts | 25 + .../table-performance-counter.component.html | 15 + .../table-performance-counter.component.scss | 0 .../table-performance-counter.component.spec.ts | 62 + .../table-performance-counter.component.ts | 72 + .../crush-rule-form-modal.component.html | 123 + .../crush-rule-form-modal.component.scss | 0 .../crush-rule-form-modal.component.spec.ts | 210 + .../crush-rule-form-modal.component.ts | 108 + .../erasure-code-profile-form-modal.component.html | 420 + .../erasure-code-profile-form-modal.component.scss | 0 ...asure-code-profile-form-modal.component.spec.ts | 688 + .../erasure-code-profile-form-modal.component.ts | 459 + .../pool/pool-details/pool-details.component.html | 51 + .../pool/pool-details/pool-details.component.scss | 0 .../pool-details/pool-details.component.spec.ts | 171 + .../pool/pool-details/pool-details.component.ts | 80 + .../src/app/ceph/pool/pool-form/pool-form-data.ts | 37 + .../ceph/pool/pool-form/pool-form.component.html | 609 + .../ceph/pool/pool-form/pool-form.component.scss | 0 .../pool/pool-form/pool-form.component.spec.ts | 1466 + .../app/ceph/pool/pool-form/pool-form.component.ts | 919 + .../ceph/pool/pool-list/pool-list.component.html | 57 + .../ceph/pool/pool-list/pool-list.component.scss | 19 + .../pool/pool-list/pool-list.component.spec.ts | 518 + .../app/ceph/pool/pool-list/pool-list.component.ts | 332 + .../frontend/src/app/ceph/pool/pool-stat.ts | 16 + .../frontend/src/app/ceph/pool/pool.module.ts | 57 + .../dashboard/frontend/src/app/ceph/pool/pool.ts | 73 + .../app/ceph/rgw/models/rgw-bucket-mfa-delete.ts | 4 + .../app/ceph/rgw/models/rgw-bucket-versioning.ts | 4 + .../frontend/src/app/ceph/rgw/models/rgw-daemon.ts | 10 + .../app/ceph/rgw/models/rgw-user-capabilities.ts | 15 + .../src/app/ceph/rgw/models/rgw-user-capability.ts | 4 + .../src/app/ceph/rgw/models/rgw-user-s3-key.ts | 6 + .../src/app/ceph/rgw/models/rgw-user-subuser.ts | 6 + .../src/app/ceph/rgw/models/rgw-user-swift-key.ts | 4 + .../rgw-bucket-details.component.html | 127 + .../rgw-bucket-details.component.scss | 7 + .../rgw-bucket-details.component.spec.ts | 42 + .../rgw-bucket-details.component.ts | 24 + .../rgw-bucket-form/rgw-bucket-form.component.html | 291 + .../rgw-bucket-form/rgw-bucket-form.component.scss | 0 .../rgw-bucket-form.component.spec.ts | 300 + .../rgw-bucket-form/rgw-bucket-form.component.ts | 264 + .../rgw-bucket-list/rgw-bucket-list.component.html | 44 + .../rgw-bucket-list/rgw-bucket-list.component.scss | 0 .../rgw-bucket-list.component.spec.ts | 178 + .../rgw-bucket-list/rgw-bucket-list.component.ts | 188 + .../rgw-daemon-details.component.html | 38 + .../rgw-daemon-details.component.scss | 0 .../rgw-daemon-details.component.spec.ts | 42 + .../rgw-daemon-details.component.ts | 46 + .../rgw-daemon-list/rgw-daemon-list.component.html | 46 + .../rgw-daemon-list/rgw-daemon-list.component.scss | 0 .../rgw-daemon-list.component.spec.ts | 106 + .../rgw-daemon-list/rgw-daemon-list.component.ts | 82 + .../rgw-user-capability-modal.component.html | 70 + .../rgw-user-capability-modal.component.scss | 0 .../rgw-user-capability-modal.component.spec.ts | 30 + .../rgw-user-capability-modal.component.ts | 92 + .../rgw-user-details.component.html | 178 + .../rgw-user-details.component.scss | 0 .../rgw-user-details.component.spec.ts | 94 + .../rgw-user-details/rgw-user-details.component.ts | 120 + .../rgw/rgw-user-form/rgw-user-form.component.html | 668 + .../rgw/rgw-user-form/rgw-user-form.component.scss | 0 .../rgw-user-form/rgw-user-form.component.spec.ts | 339 + .../rgw/rgw-user-form/rgw-user-form.component.ts | 756 + .../rgw/rgw-user-list/rgw-user-list.component.html | 44 + .../rgw/rgw-user-list/rgw-user-list.component.scss | 0 .../rgw-user-list/rgw-user-list.component.spec.ts | 166 + .../rgw/rgw-user-list/rgw-user-list.component.ts | 180 + .../rgw-user-s3-key-modal.component.html | 125 + .../rgw-user-s3-key-modal.component.scss | 0 .../rgw-user-s3-key-modal.component.spec.ts | 30 + .../rgw-user-s3-key-modal.component.ts | 84 + .../rgw-user-subuser-modal.component.html | 128 + .../rgw-user-subuser-modal.component.scss | 0 .../rgw-user-subuser-modal.component.spec.ts | 71 + .../rgw-user-subuser-modal.component.ts | 130 + .../rgw-user-swift-key-modal.component.html | 54 + .../rgw-user-swift-key-modal.component.scss | 0 .../rgw-user-swift-key-modal.component.spec.ts | 31 + .../rgw-user-swift-key-modal.component.ts | 30 + .../frontend/src/app/ceph/rgw/rgw.module.ts | 108 + .../src/app/ceph/shared/ceph-shared.module.ts | 17 + .../shared/device-list/device-list.component.html | 26 + .../shared/device-list/device-list.component.scss | 0 .../device-list/device-list.component.spec.ts | 26 + .../shared/device-list/device-list.component.ts | 84 + .../src/app/ceph/shared/pg-category.model.ts | 71 + .../app/ceph/shared/pg-category.service.spec.ts | 56 + .../src/app/ceph/shared/pg-category.service.ts | 63 + .../smart_data_version_1_0_ata_response.json | 570 + .../smart_data_version_1_0_nvme_response.json | 134 + .../smart_data_version_1_0_scsi_response.json | 208 + .../shared/smart-list/smart-list.component.html | 110 + .../shared/smart-list/smart-list.component.scss | 0 .../shared/smart-list/smart-list.component.spec.ts | 264 + .../ceph/shared/smart-list/smart-list.component.ts | 212 + .../frontend/src/app/core/auth/auth.module.ts | 87 + .../login-password-form.component.html | 95 + .../login-password-form.component.scss | 68 + .../login-password-form.component.spec.ts | 77 + .../login-password-form.component.ts | 51 + .../src/app/core/auth/login/login.component.html | 64 + .../src/app/core/auth/login/login.component.scss | 54 + .../app/core/auth/login/login.component.spec.ts | 58 + .../src/app/core/auth/login/login.component.ts | 76 + .../auth/role-details/role-details.component.html | 11 + .../auth/role-details/role-details.component.scss | 9 + .../role-details/role-details.component.spec.ts | 67 + .../auth/role-details/role-details.component.ts | 79 + .../app/core/auth/role-form/role-form-mode.enum.ts | 3 + .../core/auth/role-form/role-form.component.html | 121 + .../core/auth/role-form/role-form.component.scss | 4 + .../auth/role-form/role-form.component.spec.ts | 222 + .../app/core/auth/role-form/role-form.component.ts | 315 + .../src/app/core/auth/role-form/role-form.model.ts | 5 + .../core/auth/role-list/role-list.component.html | 21 + .../core/auth/role-list/role-list.component.scss | 0 .../auth/role-list/role-list.component.spec.ts | 83 + .../app/core/auth/role-list/role-list.component.ts | 169 + .../app/core/auth/user-form/user-form-mode.enum.ts | 3 + .../core/auth/user-form/user-form-role.model.ts | 14 + .../core/auth/user-form/user-form.component.html | 263 + .../core/auth/user-form/user-form.component.scss | 0 .../auth/user-form/user-form.component.spec.ts | 258 + .../app/core/auth/user-form/user-form.component.ts | 305 + .../src/app/core/auth/user-form/user-form.model.ts | 10 + .../core/auth/user-list/user-list.component.html | 22 + .../core/auth/user-list/user-list.component.scss | 0 .../auth/user-list/user-list.component.spec.ts | 82 + .../app/core/auth/user-list/user-list.component.ts | 164 + .../user-password-form.component.html | 121 + .../user-password-form.component.scss | 0 .../user-password-form.component.spec.ts | 83 + .../user-password-form.component.ts | 119 + .../core/auth/user-tabs/user-tabs.component.html | 14 + .../core/auth/user-tabs/user-tabs.component.scss | 0 .../auth/user-tabs/user-tabs.component.spec.ts | 29 + .../app/core/auth/user-tabs/user-tabs.component.ts | 13 + .../src/app/core/context/context.component.html | 27 + .../src/app/core/context/context.component.scss | 5 + .../src/app/core/context/context.component.spec.ts | 100 + .../src/app/core/context/context.component.ts | 70 + .../dashboard/frontend/src/app/core/core.module.ts | 34 + .../src/app/core/error/error.component.html | 63 + .../src/app/core/error/error.component.scss | 18 + .../src/app/core/error/error.component.spec.ts | 49 + .../frontend/src/app/core/error/error.component.ts | 96 + .../dashboard/frontend/src/app/core/error/error.ts | 27 + .../blank-layout/blank-layout.component.html | 1 + .../blank-layout/blank-layout.component.scss | 0 .../blank-layout/blank-layout.component.spec.ts | 25 + .../layouts/blank-layout/blank-layout.component.ts | 8 + .../login-layout/login-layout.component.html | 34 + .../login-layout/login-layout.component.scss | 61 + .../login-layout/login-layout.component.spec.ts | 28 + .../layouts/login-layout/login-layout.component.ts | 14 + .../workbench-layout.component.html | 10 + .../workbench-layout.component.scss | 12 + .../workbench-layout.component.spec.ts | 35 + .../workbench-layout/workbench-layout.component.ts | 39 + .../app/core/navigation/about/about.component.html | 47 + .../app/core/navigation/about/about.component.scss | 43 + .../core/navigation/about/about.component.spec.ts | 60 + .../app/core/navigation/about/about.component.ts | 70 + .../administration/administration.component.html | 22 + .../administration/administration.component.scss | 0 .../administration.component.spec.ts | 25 + .../administration/administration.component.ts | 22 + .../navigation/api-docs/api-docs.component.html | 3 + .../navigation/api-docs/api-docs.component.scss | 7 + .../core/navigation/api-docs/api-docs.component.ts | 18 + .../breadcrumbs/breadcrumbs.component.html | 11 + .../breadcrumbs/breadcrumbs.component.scss | 12 + .../breadcrumbs/breadcrumbs.component.spec.ts | 131 + .../breadcrumbs/breadcrumbs.component.ts | 141 + .../dashboard-help/dashboard-help.component.html | 25 + .../dashboard-help/dashboard-help.component.scss | 0 .../dashboard-help.component.spec.ts | 27 + .../dashboard-help/dashboard-help.component.ts | 31 + .../navigation/identity/identity.component.html | 27 + .../navigation/identity/identity.component.scss | 0 .../navigation/identity/identity.component.spec.ts | 27 + .../core/navigation/identity/identity.component.ts | 27 + .../src/app/core/navigation/navigation.module.ts | 43 + .../navigation/navigation.component.html | 272 + .../navigation/navigation.component.scss | 263 + .../navigation/navigation.component.spec.ts | 237 + .../navigation/navigation/navigation.component.ts | 123 + .../notifications/notifications.component.html | 11 + .../notifications/notifications.component.scss | 27 + .../notifications/notifications.component.spec.ts | 58 + .../notifications/notifications.component.ts | 47 + .../frontend/src/app/shared/api/api-client.spec.ts | 11 + .../frontend/src/app/shared/api/api-client.ts | 5 + .../src/app/shared/api/auth.service.spec.ts | 57 + .../frontend/src/app/shared/api/auth.service.ts | 53 + .../src/app/shared/api/ceph-service.service.ts | 63 + .../src/app/shared/api/cephfs.service.spec.ts | 98 + .../frontend/src/app/shared/api/cephfs.service.ts | 76 + .../src/app/shared/api/cluster.service.spec.ts | 42 + .../frontend/src/app/shared/api/cluster.service.ts | 27 + .../app/shared/api/configuration.service.spec.ts | 99 + .../src/app/shared/api/configuration.service.ts | 59 + .../src/app/shared/api/crush-rule.service.spec.ts | 47 + .../src/app/shared/api/crush-rule.service.ts | 32 + .../shared/api/custom-login-banner.service.spec.ts | 35 + .../app/shared/api/custom-login-banner.service.ts | 15 + .../src/app/shared/api/daemon.service.spec.ts | 39 + .../frontend/src/app/shared/api/daemon.service.ts | 28 + .../api/erasure-code-profile.service.spec.ts | 55 + .../app/shared/api/erasure-code-profile.service.ts | 110 + .../src/app/shared/api/health.service.spec.ts | 40 + .../frontend/src/app/shared/api/health.service.ts | 17 + .../src/app/shared/api/host.service.spec.ts | 91 + .../frontend/src/app/shared/api/host.service.ts | 154 + .../src/app/shared/api/iscsi.service.spec.ts | 97 + .../frontend/src/app/shared/api/iscsi.service.ts | 60 + .../src/app/shared/api/logging.service.spec.ts | 39 + .../frontend/src/app/shared/api/logging.service.ts | 18 + .../src/app/shared/api/logs.service.spec.ts | 34 + .../frontend/src/app/shared/api/logs.service.ts | 17 + .../src/app/shared/api/mgr-module.service.spec.ts | 66 + .../src/app/shared/api/mgr-module.service.ts | 65 + .../src/app/shared/api/monitor.service.spec.ts | 34 + .../frontend/src/app/shared/api/monitor.service.ts | 13 + .../src/app/shared/api/motd.service.spec.ts | 34 + .../frontend/src/app/shared/api/motd.service.ts | 25 + .../src/app/shared/api/nfs.service.spec.ts | 74 + .../frontend/src/app/shared/api/nfs.service.ts | 108 + .../app/shared/api/orchestrator.service.spec.ts | 35 + .../src/app/shared/api/orchestrator.service.ts | 46 + .../src/app/shared/api/osd.service.spec.ts | 183 + .../frontend/src/app/shared/api/osd.service.ts | 190 + .../shared/api/performance-counter.service.spec.ts | 45 + .../app/shared/api/performance-counter.service.ts | 29 + .../src/app/shared/api/pool.service.spec.ts | 123 + .../frontend/src/app/shared/api/pool.service.ts | 74 + .../src/app/shared/api/prometheus.service.spec.ts | 247 + .../src/app/shared/api/prometheus.service.ts | 82 + .../app/shared/api/rbd-mirroring.service.spec.ts | 164 + .../src/app/shared/api/rbd-mirroring.service.ts | 114 + .../frontend/src/app/shared/api/rbd.model.ts | 30 + .../src/app/shared/api/rbd.service.spec.ts | 181 + .../frontend/src/app/shared/api/rbd.service.ts | 198 + .../src/app/shared/api/rgw-bucket.service.spec.ts | 102 + .../src/app/shared/api/rgw-bucket.service.ts | 128 + .../src/app/shared/api/rgw-daemon.service.spec.ts | 90 + .../src/app/shared/api/rgw-daemon.service.ts | 82 + .../src/app/shared/api/rgw-site.service.spec.ts | 43 + .../src/app/shared/api/rgw-site.service.ts | 38 + .../src/app/shared/api/rgw-user.service.spec.ts | 170 + .../src/app/shared/api/rgw-user.service.ts | 179 + .../src/app/shared/api/role.service.spec.ts | 75 + .../frontend/src/app/shared/api/role.service.ts | 49 + .../src/app/shared/api/scope.service.spec.ts | 34 + .../frontend/src/app/shared/api/scope.service.ts | 13 + .../src/app/shared/api/settings.service.spec.ts | 154 + .../src/app/shared/api/settings.service.ts | 77 + .../src/app/shared/api/telemetry.service.spec.ts | 58 + .../src/app/shared/api/telemetry.service.ts | 23 + .../src/app/shared/api/user.service.spec.ts | 104 + .../frontend/src/app/shared/api/user.service.ts | 62 + .../src/app/shared/classes/cd-helper.class.spec.ts | 66 + .../src/app/shared/classes/cd-helper.class.ts | 28 + .../classes/crush.node.selection.class.spec.ts | 220 + .../shared/classes/crush.node.selection.class.ts | 221 + .../frontend/src/app/shared/classes/css-helper.ts | 5 + .../app/shared/classes/list-with-details.class.ts | 29 + .../shared/classes/table-status-view-cache.spec.ts | 40 + .../app/shared/classes/table-status-view-cache.ts | 37 + .../src/app/shared/classes/table-status.spec.ts | 15 + .../src/app/shared/classes/table-status.ts | 3 + .../alert-panel/alert-panel.component.html | 42 + .../alert-panel/alert-panel.component.scss | 12 + .../alert-panel/alert-panel.component.spec.ts | 26 + .../alert-panel/alert-panel.component.ts | 70 + .../back-button/back-button.component.html | 5 + .../back-button/back-button.component.scss | 0 .../back-button/back-button.component.spec.ts | 25 + .../back-button/back-button.component.ts | 24 + .../src/app/shared/components/components.module.ts | 132 + .../config-option/config-option.component.html | 77 + .../config-option/config-option.component.scss | 10 + .../config-option/config-option.component.spec.ts | 295 + .../config-option/config-option.component.ts | 120 + .../config-option/config-option.model.ts | 12 + .../config-option/config-option.types.spec.ts | 272 + .../config-option/config-option.types.ts | 147 + .../confirmation-modal.component.html | 27 + .../confirmation-modal.component.scss | 0 .../confirmation-modal.component.spec.ts | 185 + .../confirmation-modal.component.ts | 65 + .../copy2clipboard-button.component.html | 7 + .../copy2clipboard-button.component.scss | 0 .../copy2clipboard-button.component.spec.ts | 65 + .../copy2clipboard-button.component.ts | 55 + .../critical-confirmation-modal.component.html | 55 + .../critical-confirmation-modal.component.scss | 11 + .../critical-confirmation-modal.component.spec.ts | 235 + .../critical-confirmation-modal.component.ts | 63 + .../custom-login-banner.component.html | 2 + .../custom-login-banner.component.scss | 5 + .../custom-login-banner.component.spec.ts | 25 + .../custom-login-banner.component.ts | 20 + .../date-time-picker.component.html | 13 + .../date-time-picker.component.scss | 0 .../date-time-picker.component.spec.ts | 58 + .../date-time-picker/date-time-picker.component.ts | 67 + .../app/shared/components/doc/doc.component.html | 2 + .../app/shared/components/doc/doc.component.scss | 0 .../shared/components/doc/doc.component.spec.ts | 27 + .../src/app/shared/components/doc/doc.component.ts | 28 + .../download-button/download-button.component.html | 23 + .../download-button/download-button.component.scss | 0 .../download-button.component.spec.ts | 39 + .../download-button/download-button.component.ts | 31 + .../form-button-panel.component.html | 11 + .../form-button-panel.component.scss | 0 .../form-button-panel.component.spec.ts | 25 + .../form-button-panel.component.ts | 59 + .../form-modal/form-modal.component.html | 69 + .../form-modal/form-modal.component.scss | 0 .../form-modal/form-modal.component.spec.ts | 149 + .../components/form-modal/form-modal.component.ts | 110 + .../components/grafana/grafana.component.html | 78 + .../components/grafana/grafana.component.scss | 33 + .../components/grafana/grafana.component.spec.ts | 81 + .../shared/components/grafana/grafana.component.ts | 201 + .../shared/components/helper/helper.component.html | 11 + .../shared/components/helper/helper.component.scss | 7 + .../components/helper/helper.component.spec.ts | 26 + .../shared/components/helper/helper.component.ts | 18 + .../language-selector.component.html | 17 + .../language-selector.component.scss | 0 .../language-selector.component.spec.ts | 85 + .../language-selector.component.ts | 40 + .../language-selector/supported-languages.enum.ts | 17 + .../loading-panel/loading-panel.component.html | 9 + .../loading-panel/loading-panel.component.scss | 0 .../loading-panel/loading-panel.component.spec.ts | 26 + .../loading-panel/loading-panel.component.ts | 12 + .../shared/components/modal/modal.component.html | 19 + .../shared/components/modal/modal.component.scss | 23 + .../components/modal/modal.component.spec.ts | 54 + .../app/shared/components/modal/modal.component.ts | 31 + .../app/shared/components/motd/motd.component.html | 8 + .../app/shared/components/motd/motd.component.scss | 0 .../shared/components/motd/motd.component.spec.ts | 26 + .../app/shared/components/motd/motd.component.ts | 33 + .../notifications-sidebar.component.html | 131 + .../notifications-sidebar.component.scss | 68 + .../notifications-sidebar.component.spec.ts | 194 + .../notifications-sidebar.component.ts | 167 + .../orchestrator-doc-panel.component.html | 10 + .../orchestrator-doc-panel.component.scss | 0 .../orchestrator-doc-panel.component.spec.ts | 29 + .../orchestrator-doc-panel.component.ts | 13 + .../pwd-expiration-notification.component.html | 16 + .../pwd-expiration-notification.component.scss | 3 + .../pwd-expiration-notification.component.spec.ts | 107 + .../pwd-expiration-notification.component.ts | 55 + .../refresh-selector.component.html | 19 + .../refresh-selector.component.scss | 0 .../refresh-selector.component.spec.ts | 27 + .../refresh-selector/refresh-selector.component.ts | 32 + .../select-badges/select-badges.component.html | 22 + .../select-badges/select-badges.component.scss | 9 + .../select-badges/select-badges.component.spec.ts | 57 + .../select-badges/select-badges.component.ts | 35 + .../components/select/select-messages.model.ts | 23 + .../components/select/select-option.model.ts | 13 + .../shared/components/select/select.component.html | 79 + .../shared/components/select/select.component.scss | 26 + .../components/select/select.component.spec.ts | 276 + .../shared/components/select/select.component.ts | 149 + .../components/sparkline/sparkline.component.html | 15 + .../components/sparkline/sparkline.component.scss | 5 + .../sparkline/sparkline.component.spec.ts | 52 + .../components/sparkline/sparkline.component.ts | 130 + .../submit-button/submit-button.component.html | 11 + .../submit-button/submit-button.component.scss | 0 .../submit-button/submit-button.component.spec.ts | 27 + .../submit-button/submit-button.component.ts | 99 + .../telemetry-notification.component.html | 12 + .../telemetry-notification.component.scss | 17 + .../telemetry-notification.component.spec.ts | 107 + .../telemetry-notification.component.ts | 62 + .../components/usage-bar/usage-bar.component.html | 27 + .../components/usage-bar/usage-bar.component.scss | 35 + .../usage-bar/usage-bar.component.spec.ts | 27 + .../components/usage-bar/usage-bar.component.ts | 43 + .../shared/components/wizard/wizard.component.html | 19 + .../shared/components/wizard/wizard.component.scss | 34 + .../components/wizard/wizard.component.spec.ts | 25 + .../shared/components/wizard/wizard.component.ts | 39 + .../src/app/shared/constants/app.constants.ts | 305 + .../src/app/shared/datatable/datatable.module.ts | 31 + .../table-actions/table-actions.component.html | 43 + .../table-actions/table-actions.component.scss | 8 + .../table-actions/table-actions.component.spec.ts | 213 + .../table-actions/table-actions.component.ts | 161 + .../table-key-value/table-key-value.component.html | 12 + .../table-key-value/table-key-value.component.scss | 0 .../table-key-value.component.spec.ts | 351 + .../table-key-value/table-key-value.component.ts | 224 + .../shared/datatable/table/table.component.html | 327 + .../shared/datatable/table/table.component.scss | 295 + .../shared/datatable/table/table.component.spec.ts | 799 + .../app/shared/datatable/table/table.component.ts | 927 + .../src/app/shared/decorators/cd-encode.spec.ts | 41 + .../src/app/shared/decorators/cd-encode.ts | 80 + .../shared/directives/autofocus.directive.spec.ts | 90 + .../app/shared/directives/autofocus.directive.ts | 28 + .../dimless-binary-per-second.directive.spec.ts | 12 + .../dimless-binary-per-second.directive.ts | 132 + .../directives/dimless-binary.directive.spec.ts | 12 + .../shared/directives/dimless-binary.directive.ts | 122 + .../src/app/shared/directives/directives.module.ts | 53 + .../form-input-disable.directive.spec.ts | 75 + .../directives/form-input-disable.directive.ts | 27 + .../directives/form-loading.directive.spec.ts | 89 + .../shared/directives/form-loading.directive.ts | 51 + .../shared/directives/form-scope.directive.spec.ts | 8 + .../app/shared/directives/form-scope.directive.ts | 8 + .../app/shared/directives/iops.directive.spec.ts | 8 + .../src/app/shared/directives/iops.directive.ts | 31 + .../directives/milliseconds.directive.spec.ts | 8 + .../shared/directives/milliseconds.directive.ts | 31 + .../cd-form-control.directive.spec.ts | 37 + .../cd-form-control.directive.ts | 82 + .../cd-form-group.directive.spec.ts | 37 + .../cd-form-group.directive.ts | 76 + .../cd-form-validation.directive.spec.ts | 35 + .../cd-form-validation.directive.ts | 62 + .../directives/password-button.directive.spec.ts | 8 + .../shared/directives/password-button.directive.ts | 45 + .../directives/stateful-tab.directive.spec.ts | 28 + .../shared/directives/stateful-tab.directive.ts | 31 + .../app/shared/directives/trim.directive.spec.ts | 50 + .../src/app/shared/directives/trim.directive.ts | 21 + .../src/app/shared/enum/cell-template.enum.ts | 54 + .../src/app/shared/enum/components.enum.ts | 9 + .../src/app/shared/enum/health-color.enum.ts | 5 + .../frontend/src/app/shared/enum/icons.enum.ts | 84 + .../src/app/shared/enum/notification-type.enum.ts | 5 + .../src/app/shared/enum/unix_errno.enum.ts | 4 + .../src/app/shared/enum/view-cache-status.enum.ts | 6 + .../src/app/shared/forms/cd-form-builder.spec.ts | 33 + .../src/app/shared/forms/cd-form-builder.ts | 20 + .../src/app/shared/forms/cd-form-group.spec.ts | 184 + .../frontend/src/app/shared/forms/cd-form-group.ts | 75 + .../frontend/src/app/shared/forms/cd-form.spec.ts | 32 + .../frontend/src/app/shared/forms/cd-form.ts | 26 + .../src/app/shared/forms/cd-validators.spec.ts | 906 + .../frontend/src/app/shared/forms/cd-validators.ts | 612 + .../src/app/shared/models/alertmanager-silence.ts | 23 + .../frontend/src/app/shared/models/breadcrumbs.ts | 59 + .../shared/models/cd-form-modal-field-config.ts | 32 + .../src/app/shared/models/cd-notification.spec.ts | 95 + .../src/app/shared/models/cd-notification.ts | 48 + .../shared/models/cd-pwd-expiration-settings.ts | 11 + .../app/shared/models/cd-pwd-policy-settings.ts | 23 + .../src/app/shared/models/cd-table-action.ts | 44 + .../app/shared/models/cd-table-column-filter.ts | 7 + .../models/cd-table-column-filters-change.ts | 22 + .../src/app/shared/models/cd-table-column.ts | 38 + .../shared/models/cd-table-fetch-data-context.ts | 44 + .../src/app/shared/models/cd-table-paging.ts | 20 + .../src/app/shared/models/cd-table-selection.ts | 45 + .../src/app/shared/models/cd-user-config.ts | 11 + .../app/shared/models/cephfs-directory-models.ts | 21 + .../src/app/shared/models/chart-tooltip.ts | 115 + .../src/app/shared/models/configuration.ts | 43 + .../frontend/src/app/shared/models/credentials.ts | 4 + .../frontend/src/app/shared/models/crush-node.ts | 17 + .../frontend/src/app/shared/models/crush-rule.ts | 18 + .../frontend/src/app/shared/models/crush-step.ts | 7 + .../src/app/shared/models/daemon.interface.ts | 12 + .../frontend/src/app/shared/models/devices.ts | 25 + .../src/app/shared/models/erasure-code-profile.ts | 17 + .../src/app/shared/models/executing-task.ts | 6 + .../src/app/shared/models/finished-task.ts | 15 + .../frontend/src/app/shared/models/flag.ts | 8 + .../frontend/src/app/shared/models/image-spec.ts | 25 + .../shared/models/inventory-device-type.model.ts | 9 + .../src/app/shared/models/login-response.ts | 7 + .../src/app/shared/models/mirroring-summary.ts | 5 + .../src/app/shared/models/orchestrator.enum.ts | 25 + .../app/shared/models/orchestrator.interface.ts | 9 + .../app/shared/models/osd-deployment-options.ts | 24 + .../frontend/src/app/shared/models/osd-settings.ts | 4 + .../src/app/shared/models/permission.spec.ts | 62 + .../frontend/src/app/shared/models/permissions.ts | 50 + .../src/app/shared/models/pool-form-info.ts | 20 + .../src/app/shared/models/prometheus-alerts.ts | 84 + .../src/app/shared/models/service.interface.ts | 45 + .../frontend/src/app/shared/models/smart.ts | 253 + .../src/app/shared/models/summary.model.ts | 15 + .../src/app/shared/models/task-exception.ts | 9 + .../frontend/src/app/shared/models/task.ts | 10 + .../frontend/src/app/shared/models/wizard-steps.ts | 4 + .../src/app/shared/pipes/array.pipe.spec.ts | 21 + .../frontend/src/app/shared/pipes/array.pipe.ts | 26 + .../src/app/shared/pipes/boolean-text.pipe.spec.ts | 37 + .../src/app/shared/pipes/boolean-text.pipe.ts | 14 + .../src/app/shared/pipes/boolean.pipe.spec.ts | 57 + .../frontend/src/app/shared/pipes/boolean.pipe.ts | 26 + .../src/app/shared/pipes/cd-date.pipe.spec.ts | 24 + .../frontend/src/app/shared/pipes/cd-date.pipe.ts | 20 + .../shared/pipes/ceph-release-name.pipe.spec.ts | 28 + .../src/app/shared/pipes/ceph-release-name.pipe.ts | 24 + .../shared/pipes/ceph-short-version.pipe.spec.ts | 21 + .../app/shared/pipes/ceph-short-version.pipe.ts | 18 + .../shared/pipes/dimless-binary-per-second.pipe.ts | 24 + .../app/shared/pipes/dimless-binary.pipe.spec.ts | 56 + .../src/app/shared/pipes/dimless-binary.pipe.ts | 24 + .../src/app/shared/pipes/dimless.pipe.spec.ts | 56 + .../frontend/src/app/shared/pipes/dimless.pipe.ts | 14 + .../src/app/shared/pipes/duration.pipe.spec.ts | 17 + .../frontend/src/app/shared/pipes/duration.pipe.ts | 37 + .../src/app/shared/pipes/empty.pipe.spec.ts | 18 + .../frontend/src/app/shared/pipes/empty.pipe.ts | 12 + .../src/app/shared/pipes/encode-uri.pipe.spec.ts | 13 + .../src/app/shared/pipes/encode-uri.pipe.ts | 10 + .../src/app/shared/pipes/filter.pipe.spec.ts | 54 + .../frontend/src/app/shared/pipes/filter.pipe.ts | 25 + .../src/app/shared/pipes/health-color.pipe.spec.ts | 47 + .../src/app/shared/pipes/health-color.pipe.ts | 17 + .../src/app/shared/pipes/iops.pipe.spec.ts | 8 + .../frontend/src/app/shared/pipes/iops.pipe.ts | 10 + .../app/shared/pipes/iscsi-backstore.pipe.spec.ts | 17 + .../src/app/shared/pipes/iscsi-backstore.pipe.ts | 15 + .../src/app/shared/pipes/join.pipe.spec.ts | 13 + .../frontend/src/app/shared/pipes/join.pipe.ts | 10 + .../src/app/shared/pipes/log-priority.pipe.spec.ts | 32 + .../src/app/shared/pipes/log-priority.pipe.ts | 20 + .../frontend/src/app/shared/pipes/map.pipe.spec.ts | 25 + .../frontend/src/app/shared/pipes/map.pipe.ts | 15 + .../src/app/shared/pipes/milliseconds.pipe.spec.ts | 8 + .../src/app/shared/pipes/milliseconds.pipe.ts | 10 + .../app/shared/pipes/not-available.pipe.spec.ts | 30 + .../src/app/shared/pipes/not-available.pipe.ts | 15 + .../src/app/shared/pipes/ordinal.pipe.spec.ts | 8 + .../frontend/src/app/shared/pipes/ordinal.pipe.ts | 25 + .../frontend/src/app/shared/pipes/pipes.module.ts | 125 + .../pipes/rbd-configuration-source.pipe.spec.ts | 22 + .../shared/pipes/rbd-configuration-source.pipe.ts | 15 + .../app/shared/pipes/relative-date.pipe.spec.ts | 44 + .../src/app/shared/pipes/relative-date.pipe.ts | 57 + .../src/app/shared/pipes/round.pipe.spec.ts | 13 + .../frontend/src/app/shared/pipes/round.pipe.ts | 12 + .../app/shared/pipes/sanitize-html.pipe.spec.ts | 26 + .../src/app/shared/pipes/sanitize-html.pipe.ts | 13 + .../app/shared/pipes/search-highlight.pipe.spec.ts | 41 + .../src/app/shared/pipes/search-highlight.pipe.ts | 26 + .../src/app/shared/pipes/truncate.pipe.spec.ts | 21 + .../frontend/src/app/shared/pipes/truncate.pipe.ts | 16 + .../src/app/shared/pipes/upper-first.pipe.spec.ts | 17 + .../src/app/shared/pipes/upper-first.pipe.ts | 12 + .../rxjs/operators/page-visibilty.operator.ts | 20 + .../services/api-interceptor.service.spec.ts | 227 + .../app/shared/services/api-interceptor.service.ts | 133 + .../app/shared/services/auth-guard.service.spec.ts | 54 + .../src/app/shared/services/auth-guard.service.ts | 29 + .../shared/services/auth-storage.service.spec.ts | 47 + .../app/shared/services/auth-storage.service.ts | 59 + .../services/cd-table-server-side.service.spec.ts | 16 + .../services/cd-table-server-side.service.ts | 14 + .../services/change-password-guard.service.spec.ts | 68 + .../services/change-password-guard.service.ts | 42 + .../src/app/shared/services/device.service.spec.ts | 92 + .../src/app/shared/services/device.service.ts | 57 + .../src/app/shared/services/doc.service.spec.ts | 75 + .../src/app/shared/services/doc.service.ts | 65 + .../app/shared/services/favicon.service.spec.ts | 23 + .../src/app/shared/services/favicon.service.ts | 79 + .../services/feature-toggles-guard.service.spec.ts | 72 + .../services/feature-toggles-guard.service.ts | 30 + .../services/feature-toggles.service.spec.ts | 54 + .../app/shared/services/feature-toggles.service.ts | 37 + .../app/shared/services/formatter.service.spec.ts | 90 + .../src/app/shared/services/formatter.service.ts | 77 + .../shared/services/js-error-handler.service.ts | 33 + .../app/shared/services/language.service.spec.ts | 34 + .../src/app/shared/services/language.service.ts | 23 + .../src/app/shared/services/modal.service.spec.ts | 59 + .../src/app/shared/services/modal.service.ts | 28 + .../services/module-status-guard.service.spec.ts | 102 + .../shared/services/module-status-guard.service.ts | 101 + .../services/motd-notification.service.spec.ts | 117 + .../shared/services/motd-notification.service.ts | 84 + .../shared/services/ngzone-scheduler.service.ts | 48 + .../shared/services/no-sso-guard.service.spec.ts | 49 + .../app/shared/services/no-sso-guard.service.ts | 28 + .../shared/services/notification.service.spec.ts | 285 + .../app/shared/services/notification.service.ts | 237 + .../services/password-policy.service.spec.ts | 208 + .../app/shared/services/password-policy.service.ts | 65 + .../services/prometheus-alert-formatter.spec.ts | 95 + .../shared/services/prometheus-alert-formatter.ts | 74 + .../services/prometheus-alert.service.spec.ts | 214 + .../shared/services/prometheus-alert.service.ts | 100 + .../prometheus-notification.service.spec.ts | 227 + .../services/prometheus-notification.service.ts | 51 + .../prometheus-silence-matcher.service.spec.ts | 133 + .../services/prometheus-silence-matcher.service.ts | 78 + .../services/rbd-configuration.service.spec.ts | 45 + .../shared/services/rbd-configuration.service.ts | 144 + .../services/refresh-interval.service.spec.ts | 52 + .../shared/services/refresh-interval.service.ts | 46 + .../app/shared/services/summary.service.spec.ts | 179 + .../src/app/shared/services/summary.service.ts | 89 + .../app/shared/services/task-list.service.spec.ts | 133 + .../src/app/shared/services/task-list.service.ts | 111 + .../shared/services/task-manager.service.spec.ts | 72 + .../app/shared/services/task-manager.service.ts | 59 + .../shared/services/task-message.service.spec.ts | 312 + .../app/shared/services/task-message.service.ts | 424 + .../shared/services/task-wrapper.service.spec.ts | 98 + .../app/shared/services/task-wrapper.service.ts | 68 + .../telemetry-notification.service.spec.ts | 33 + .../services/telemetry-notification.service.ts | 16 + .../services/text-to-download.service.spec.ts | 20 + .../shared/services/text-to-download.service.ts | 12 + .../app/shared/services/time-diff.service.spec.ts | 71 + .../src/app/shared/services/time-diff.service.ts | 55 + .../src/app/shared/services/timer.service.spec.ts | 68 + .../src/app/shared/services/timer.service.ts | 29 + .../shared/services/url-builder.service.spec.ts | 37 + .../src/app/shared/services/url-builder.service.ts | 50 + .../shared/services/wizard-steps.service.spec.ts | 16 + .../app/shared/services/wizard-steps.service.ts | 58 + .../frontend/src/app/shared/shared.module.ts | 19 + .../mgr/dashboard/frontend/src/assets/.gitkeep | 0 .../assets/Ceph_Ceph_Logo_with_text_red_white.svg | 69 + .../src/assets/Ceph_Ceph_Logo_with_text_white.svg | 69 + .../dashboard/frontend/src/assets/Ceph_Logo.svg | 71 + .../frontend/src/assets/ceph_background.gif | Bin 0 -> 98115 bytes .../mgr/dashboard/frontend/src/assets/loading.gif | Bin 0 -> 35386 bytes .../dashboard/frontend/src/assets/logo-mini.png | Bin 0 -> 1811 bytes .../frontend/src/assets/prometheus_logo.svg | 50 + .../frontend/src/environments/environment.tpl.ts | 10 + src/pybind/mgr/dashboard/frontend/src/favicon.ico | Bin 0 -> 1150 bytes src/pybind/mgr/dashboard/frontend/src/index.html | 24 + .../mgr/dashboard/frontend/src/jestGlobalMocks.ts | 7 + .../dashboard/frontend/src/locale/messages.cs.xlf | 14162 ++++++++++ .../frontend/src/locale/messages.de-DE.xlf | 14885 ++++++++++ .../frontend/src/locale/messages.es-ES.xlf | 14886 ++++++++++ .../frontend/src/locale/messages.fr-FR.xlf | 14885 ++++++++++ .../frontend/src/locale/messages.id-ID.xlf | 14069 +++++++++ .../frontend/src/locale/messages.it-IT.xlf | 14885 ++++++++++ .../frontend/src/locale/messages.ja-JP.xlf | 14882 ++++++++++ .../frontend/src/locale/messages.ko-KR.xlf | 14562 ++++++++++ .../frontend/src/locale/messages.pl-PL.xlf | 14066 +++++++++ .../frontend/src/locale/messages.pt-BR.xlf | 14885 ++++++++++ .../frontend/src/locale/messages.zh-CN.xlf | 14881 ++++++++++ .../frontend/src/locale/messages.zh-TW.xlf | 14884 ++++++++++ src/pybind/mgr/dashboard/frontend/src/main.ts | 23 + src/pybind/mgr/dashboard/frontend/src/polyfills.ts | 54 + src/pybind/mgr/dashboard/frontend/src/setupJest.ts | 11 + src/pybind/mgr/dashboard/frontend/src/styles.scss | 138 + .../frontend/src/styles/_chart-tooltip.scss | 59 + .../frontend/src/styles/bootstrap-extends.scss | 129 + .../frontend/src/styles/ceph-custom/_basics.scss | 131 + .../frontend/src/styles/ceph-custom/_buttons.scss | 80 + .../frontend/src/styles/ceph-custom/_dropdown.scss | 35 + .../frontend/src/styles/ceph-custom/_forms.scss | 86 + .../frontend/src/styles/ceph-custom/_grid.scss | 6 + .../frontend/src/styles/ceph-custom/_icons.scss | 16 + .../frontend/src/styles/ceph-custom/_index.scss | 5 + .../frontend/src/styles/ceph-custom/_navs.scss | 5 + .../frontend/src/styles/ceph-custom/_toast.scss | 30 + .../src/styles/defaults/_bootstrap-defaults.scss | 111 + .../frontend/src/styles/defaults/_functions.scss | 5 + .../frontend/src/styles/defaults/_index.scss | 4 + .../frontend/src/styles/defaults/_mixins.scss | 34 + .../frontend/src/styles/vendor/_index.scss | 22 + .../src/styles/vendor/_style-overrides.scss | 4 + .../frontend/src/styles/vendor/_variables.scss | 17 + .../frontend/src/testing/activated-route-stub.ts | 26 + .../frontend/src/testing/unit-test-helper.ts | 692 + src/pybind/mgr/dashboard/frontend/src/typings.d.ts | 5 + .../mgr/dashboard/frontend/tsconfig.app.json | 14 + src/pybind/mgr/dashboard/frontend/tsconfig.json | 42 + .../mgr/dashboard/frontend/tsconfig.spec.json | 21 + src/pybind/mgr/dashboard/frontend/tslint.json | 118 + src/pybind/mgr/dashboard/grafana.py | 137 + src/pybind/mgr/dashboard/module.py | 637 + src/pybind/mgr/dashboard/openapi.yaml | 10395 +++++++ src/pybind/mgr/dashboard/plugins/__init__.py | 72 + src/pybind/mgr/dashboard/plugins/debug.py | 99 + .../mgr/dashboard/plugins/feature_toggles.py | 163 + src/pybind/mgr/dashboard/plugins/interfaces.py | 81 + src/pybind/mgr/dashboard/plugins/lru_cache.py | 44 + src/pybind/mgr/dashboard/plugins/motd.py | 98 + src/pybind/mgr/dashboard/plugins/pluggy.py | 116 + src/pybind/mgr/dashboard/plugins/plugin.py | 41 + src/pybind/mgr/dashboard/plugins/ttl_cache.py | 57 + src/pybind/mgr/dashboard/requirements-extra.txt | 1 + src/pybind/mgr/dashboard/requirements-lint.txt | 10 + src/pybind/mgr/dashboard/requirements-test.txt | 3 + src/pybind/mgr/dashboard/requirements.txt | 12 + src/pybind/mgr/dashboard/rest_client.py | 559 + .../mgr/dashboard/run-backend-api-request.sh | 24 + src/pybind/mgr/dashboard/run-backend-api-tests.sh | 178 + .../mgr/dashboard/run-backend-rook-api-request.sh | 40 + src/pybind/mgr/dashboard/run-frontend-e2e-tests.sh | 119 + src/pybind/mgr/dashboard/run-frontend-unittests.sh | 50 + src/pybind/mgr/dashboard/security.py | 61 + src/pybind/mgr/dashboard/services/__init__.py | 2 + .../mgr/dashboard/services/access_control.py | 953 + src/pybind/mgr/dashboard/services/auth.py | 215 + src/pybind/mgr/dashboard/services/ceph_service.py | 423 + src/pybind/mgr/dashboard/services/cephfs.py | 263 + src/pybind/mgr/dashboard/services/cluster.py | 35 + src/pybind/mgr/dashboard/services/exception.py | 118 + src/pybind/mgr/dashboard/services/iscsi_cli.py | 59 + src/pybind/mgr/dashboard/services/iscsi_client.py | 259 + src/pybind/mgr/dashboard/services/iscsi_config.py | 112 + src/pybind/mgr/dashboard/services/orchestrator.py | 232 + src/pybind/mgr/dashboard/services/osd.py | 25 + src/pybind/mgr/dashboard/services/progress.py | 92 + src/pybind/mgr/dashboard/services/rbd.py | 580 + src/pybind/mgr/dashboard/services/rgw_client.py | 764 + src/pybind/mgr/dashboard/services/sso.py | 257 + src/pybind/mgr/dashboard/services/tcmu_service.py | 108 + src/pybind/mgr/dashboard/settings.py | 255 + src/pybind/mgr/dashboard/tests/__init__.py | 366 + src/pybind/mgr/dashboard/tests/helper.py | 56 + .../mgr/dashboard/tests/test_access_control.py | 871 + .../mgr/dashboard/tests/test_api_auditing.py | 93 + src/pybind/mgr/dashboard/tests/test_auth.py | 66 + .../mgr/dashboard/tests/test_ceph_service.py | 170 + src/pybind/mgr/dashboard/tests/test_cephfs.py | 42 + src/pybind/mgr/dashboard/tests/test_controllers.py | 191 + src/pybind/mgr/dashboard/tests/test_daemon.py | 41 + src/pybind/mgr/dashboard/tests/test_docs.py | 125 + .../dashboard/tests/test_erasure_code_profile.py | 29 + src/pybind/mgr/dashboard/tests/test_exceptions.py | 161 + .../mgr/dashboard/tests/test_feature_toggles.py | 65 + src/pybind/mgr/dashboard/tests/test_grafana.py | 133 + src/pybind/mgr/dashboard/tests/test_home.py | 74 + src/pybind/mgr/dashboard/tests/test_host.py | 509 + src/pybind/mgr/dashboard/tests/test_iscsi.py | 1008 + src/pybind/mgr/dashboard/tests/test_nfs.py | 240 + .../mgr/dashboard/tests/test_notification.py | 137 + .../mgr/dashboard/tests/test_orchestrator.py | 40 + src/pybind/mgr/dashboard/tests/test_osd.py | 434 + .../mgr/dashboard/tests/test_plugin_debug.py | 38 + src/pybind/mgr/dashboard/tests/test_pool.py | 121 + src/pybind/mgr/dashboard/tests/test_prometheus.py | 131 + .../mgr/dashboard/tests/test_rbd_mirroring.py | 195 + src/pybind/mgr/dashboard/tests/test_rbd_service.py | 180 + src/pybind/mgr/dashboard/tests/test_rest_client.py | 110 + src/pybind/mgr/dashboard/tests/test_rest_tasks.py | 92 + src/pybind/mgr/dashboard/tests/test_rgw.py | 225 + src/pybind/mgr/dashboard/tests/test_rgw_client.py | 355 + src/pybind/mgr/dashboard/tests/test_settings.py | 208 + src/pybind/mgr/dashboard/tests/test_ssl.py | 28 + src/pybind/mgr/dashboard/tests/test_sso.py | 153 + src/pybind/mgr/dashboard/tests/test_task.py | 433 + src/pybind/mgr/dashboard/tests/test_tools.py | 211 + src/pybind/mgr/dashboard/tests/test_versioning.py | 79 + src/pybind/mgr/dashboard/tools.py | 841 + src/pybind/mgr/dashboard/tox.ini | 176 + src/pybind/mgr/devicehealth/__init__.py | 2 + src/pybind/mgr/devicehealth/module.py | 726 + src/pybind/mgr/diskprediction_local/__init__.py | 1 + .../models/prophetstor/config.json | 77 + .../models/prophetstor/svm_1.pkl | Bin 0 -> 281292 bytes .../models/prophetstor/svm_10.pkl | Bin 0 -> 217792 bytes .../models/prophetstor/svm_104.pkl | Bin 0 -> 492492 bytes .../models/prophetstor/svm_105.pkl | Bin 0 -> 217192 bytes .../models/prophetstor/svm_109.pkl | Bin 0 -> 256392 bytes .../models/prophetstor/svm_112.pkl | Bin 0 -> 499492 bytes .../models/prophetstor/svm_114.pkl | Bin 0 -> 276492 bytes .../models/prophetstor/svm_115.pkl | Bin 0 -> 509592 bytes .../models/prophetstor/svm_118.pkl | Bin 0 -> 315192 bytes .../models/prophetstor/svm_119.pkl | Bin 0 -> 485992 bytes .../models/prophetstor/svm_12.pkl | Bin 0 -> 275692 bytes .../models/prophetstor/svm_120.pkl | Bin 0 -> 307592 bytes .../models/prophetstor/svm_123.pkl | Bin 0 -> 246792 bytes .../models/prophetstor/svm_124.pkl | Bin 0 -> 310292 bytes .../models/prophetstor/svm_125.pkl | Bin 0 -> 452492 bytes .../models/prophetstor/svm_128.pkl | Bin 0 -> 550492 bytes .../models/prophetstor/svm_131.pkl | Bin 0 -> 493192 bytes .../models/prophetstor/svm_134.pkl | Bin 0 -> 266692 bytes .../models/prophetstor/svm_138.pkl | Bin 0 -> 488292 bytes .../models/prophetstor/svm_14.pkl | Bin 0 -> 244892 bytes .../models/prophetstor/svm_141.pkl | Bin 0 -> 422368 bytes .../models/prophetstor/svm_145.pkl | Bin 0 -> 359512 bytes .../models/prophetstor/svm_151.pkl | Bin 0 -> 305944 bytes .../models/prophetstor/svm_16.pkl | Bin 0 -> 308192 bytes .../models/prophetstor/svm_161.pkl | Bin 0 -> 305188 bytes .../models/prophetstor/svm_168.pkl | Bin 0 -> 301516 bytes .../models/prophetstor/svm_169.pkl | Bin 0 -> 363400 bytes .../models/prophetstor/svm_174.pkl | Bin 0 -> 323764 bytes .../models/prophetstor/svm_18.pkl | Bin 0 -> 312692 bytes .../models/prophetstor/svm_182.pkl | Bin 0 -> 354652 bytes .../models/prophetstor/svm_185.pkl | Bin 0 -> 317176 bytes .../models/prophetstor/svm_186.pkl | Bin 0 -> 276352 bytes .../models/prophetstor/svm_195.pkl | Bin 0 -> 489544 bytes .../models/prophetstor/svm_201.pkl | Bin 0 -> 307888 bytes .../models/prophetstor/svm_204.pkl | Bin 0 -> 567088 bytes .../models/prophetstor/svm_206.pkl | Bin 0 -> 474856 bytes .../models/prophetstor/svm_208.pkl | Bin 0 -> 283588 bytes .../models/prophetstor/svm_210.pkl | Bin 0 -> 617200 bytes .../models/prophetstor/svm_212.pkl | Bin 0 -> 345148 bytes .../models/prophetstor/svm_213.pkl | Bin 0 -> 357568 bytes .../models/prophetstor/svm_219.pkl | Bin 0 -> 342232 bytes .../models/prophetstor/svm_221.pkl | Bin 0 -> 365128 bytes .../models/prophetstor/svm_222.pkl | Bin 0 -> 314800 bytes .../models/prophetstor/svm_223.pkl | Bin 0 -> 342124 bytes .../models/prophetstor/svm_225.pkl | Bin 0 -> 329812 bytes .../models/prophetstor/svm_227.pkl | Bin 0 -> 296440 bytes .../models/prophetstor/svm_229.pkl | Bin 0 -> 572380 bytes .../models/prophetstor/svm_230.pkl | Bin 0 -> 251188 bytes .../models/prophetstor/svm_234.pkl | Bin 0 -> 277972 bytes .../models/prophetstor/svm_235.pkl | Bin 0 -> 243736 bytes .../models/prophetstor/svm_236.pkl | Bin 0 -> 377872 bytes .../models/prophetstor/svm_239.pkl | Bin 0 -> 571732 bytes .../models/prophetstor/svm_243.pkl | Bin 0 -> 534148 bytes .../models/prophetstor/svm_27.pkl | Bin 0 -> 504592 bytes .../models/prophetstor/svm_3.pkl | Bin 0 -> 557192 bytes .../models/prophetstor/svm_33.pkl | Bin 0 -> 547392 bytes .../models/prophetstor/svm_36.pkl | Bin 0 -> 516692 bytes .../models/prophetstor/svm_44.pkl | Bin 0 -> 546592 bytes .../models/prophetstor/svm_50.pkl | Bin 0 -> 448292 bytes .../models/prophetstor/svm_57.pkl | Bin 0 -> 328292 bytes .../models/prophetstor/svm_59.pkl | Bin 0 -> 494292 bytes .../models/prophetstor/svm_6.pkl | Bin 0 -> 314092 bytes .../models/prophetstor/svm_61.pkl | Bin 0 -> 499492 bytes .../models/prophetstor/svm_62.pkl | Bin 0 -> 483492 bytes .../models/prophetstor/svm_67.pkl | Bin 0 -> 492592 bytes .../models/prophetstor/svm_69.pkl | Bin 0 -> 288292 bytes .../models/prophetstor/svm_71.pkl | Bin 0 -> 228792 bytes .../models/prophetstor/svm_72.pkl | Bin 0 -> 489492 bytes .../models/prophetstor/svm_78.pkl | Bin 0 -> 491392 bytes .../models/prophetstor/svm_79.pkl | Bin 0 -> 284992 bytes .../models/prophetstor/svm_82.pkl | Bin 0 -> 255292 bytes .../models/prophetstor/svm_85.pkl | Bin 0 -> 522092 bytes .../models/prophetstor/svm_88.pkl | Bin 0 -> 502392 bytes .../models/prophetstor/svm_93.pkl | Bin 0 -> 302592 bytes .../models/prophetstor/svm_97.pkl | Bin 0 -> 272392 bytes .../diskprediction_local/models/redhat/config.json | 4 + .../models/redhat/hgst_predictor.pkl | Bin 0 -> 2860606 bytes .../models/redhat/hgst_scaler.pkl | Bin 0 -> 1865 bytes .../models/redhat/seagate_predictor.pkl | Bin 0 -> 37062936 bytes .../models/redhat/seagate_scaler.pkl | Bin 0 -> 1481 bytes src/pybind/mgr/diskprediction_local/module.py | 319 + src/pybind/mgr/diskprediction_local/predictor.py | 466 + .../mgr/diskprediction_local/requirements.txt | 3 + src/pybind/mgr/hello/__init__.py | 1 + src/pybind/mgr/hello/module.py | 147 + src/pybind/mgr/influx/__init__.py | 1 + src/pybind/mgr/influx/module.py | 490 + src/pybind/mgr/insights/__init__.py | 6 + src/pybind/mgr/insights/health.py | 190 + src/pybind/mgr/insights/module.py | 327 + src/pybind/mgr/insights/tests/__init__.py | 0 src/pybind/mgr/insights/tests/test_health.py | 273 + src/pybind/mgr/iostat/__init__.py | 1 + src/pybind/mgr/iostat/module.py | 60 + src/pybind/mgr/k8sevents/README.md | 81 + src/pybind/mgr/k8sevents/__init__.py | 1 + src/pybind/mgr/k8sevents/module.py | 1455 + src/pybind/mgr/k8sevents/rbac_sample.yaml | 45 + src/pybind/mgr/localpool/__init__.py | 1 + src/pybind/mgr/localpool/module.py | 137 + src/pybind/mgr/mds_autoscaler/__init__.py | 6 + src/pybind/mgr/mds_autoscaler/module.py | 98 + src/pybind/mgr/mds_autoscaler/tests/__init__.py | 0 .../mgr/mds_autoscaler/tests/test_autoscaler.py | 88 + src/pybind/mgr/mgr_module.py | 2006 ++ src/pybind/mgr/mgr_util.py | 813 + src/pybind/mgr/mirroring/__init__.py | 1 + src/pybind/mgr/mirroring/fs/__init__.py | 0 src/pybind/mgr/mirroring/fs/blocklist.py | 10 + src/pybind/mgr/mirroring/fs/dir_map/__init__.py | 0 src/pybind/mgr/mirroring/fs/dir_map/create.py | 23 + src/pybind/mgr/mirroring/fs/dir_map/load.py | 74 + src/pybind/mgr/mirroring/fs/dir_map/policy.py | 380 + .../mgr/mirroring/fs/dir_map/state_transition.py | 94 + src/pybind/mgr/mirroring/fs/dir_map/update.py | 151 + src/pybind/mgr/mirroring/fs/exception.py | 3 + src/pybind/mgr/mirroring/fs/notify.py | 121 + src/pybind/mgr/mirroring/fs/snapshot_mirror.py | 792 + src/pybind/mgr/mirroring/fs/utils.py | 152 + src/pybind/mgr/mirroring/module.py | 103 + src/pybind/mgr/nfs/__init__.py | 7 + src/pybind/mgr/nfs/cluster.py | 258 + src/pybind/mgr/nfs/exception.py | 32 + src/pybind/mgr/nfs/export.py | 814 + src/pybind/mgr/nfs/export_utils.py | 521 + src/pybind/mgr/nfs/module.py | 154 + src/pybind/mgr/nfs/tests/__init__.py | 0 src/pybind/mgr/nfs/tests/test_nfs.py | 1036 + src/pybind/mgr/nfs/utils.py | 59 + src/pybind/mgr/orchestrator/README.md | 14 + src/pybind/mgr/orchestrator/__init__.py | 20 + src/pybind/mgr/orchestrator/_interface.py | 1527 + src/pybind/mgr/orchestrator/module.py | 1471 + src/pybind/mgr/orchestrator/tests/__init__.py | 0 .../mgr/orchestrator/tests/test_orchestrator.py | 220 + src/pybind/mgr/osd_perf_query/__init__.py | 1 + src/pybind/mgr/osd_perf_query/module.py | 196 + src/pybind/mgr/osd_support/__init__.py | 1 + src/pybind/mgr/osd_support/module.py | 19 + src/pybind/mgr/pg_autoscaler/__init__.py | 6 + src/pybind/mgr/pg_autoscaler/module.py | 802 + src/pybind/mgr/pg_autoscaler/tests/__init__.py | 0 .../tests/test_cal_final_pg_target.py | 676 + .../mgr/pg_autoscaler/tests/test_cal_ratio.py | 37 + .../pg_autoscaler/tests/test_overlapping_roots.py | 479 + src/pybind/mgr/progress/__init__.py | 7 + src/pybind/mgr/progress/module.py | 873 + src/pybind/mgr/progress/test_progress.py | 174 + src/pybind/mgr/prometheus/__init__.py | 2 + src/pybind/mgr/prometheus/module.py | 1889 ++ src/pybind/mgr/prometheus/test_module.py | 93 + src/pybind/mgr/rbd_support/__init__.py | 1 + src/pybind/mgr/rbd_support/common.py | 34 + .../mgr/rbd_support/mirror_snapshot_schedule.py | 595 + src/pybind/mgr/rbd_support/module.py | 204 + src/pybind/mgr/rbd_support/perf.py | 457 + src/pybind/mgr/rbd_support/schedule.py | 520 + src/pybind/mgr/rbd_support/task.py | 832 + src/pybind/mgr/rbd_support/trash_purge_schedule.py | 287 + src/pybind/mgr/requirements.txt | 12 + src/pybind/mgr/restful/__init__.py | 1 + src/pybind/mgr/restful/api/__init__.py | 39 + src/pybind/mgr/restful/api/config.py | 86 + src/pybind/mgr/restful/api/crush.py | 26 + src/pybind/mgr/restful/api/doc.py | 15 + src/pybind/mgr/restful/api/mon.py | 40 + src/pybind/mgr/restful/api/osd.py | 135 + src/pybind/mgr/restful/api/perf.py | 27 + src/pybind/mgr/restful/api/pool.py | 140 + src/pybind/mgr/restful/api/request.py | 93 + src/pybind/mgr/restful/api/server.py | 35 + src/pybind/mgr/restful/common.py | 156 + src/pybind/mgr/restful/context.py | 2 + src/pybind/mgr/restful/decorators.py | 82 + src/pybind/mgr/restful/hooks.py | 11 + src/pybind/mgr/restful/module.py | 615 + src/pybind/mgr/rook/.gitignore | 1 + src/pybind/mgr/rook/CMakeLists.txt | 15 + src/pybind/mgr/rook/__init__.py | 2 + src/pybind/mgr/rook/generate_rook_ceph_client.sh | 14 + src/pybind/mgr/rook/module.py | 512 + src/pybind/mgr/rook/requirements.txt | 2 + .../.github/workflows/generate.yml | 21 + src/pybind/mgr/rook/rook-client-python/.gitignore | 11 + src/pybind/mgr/rook/rook-client-python/README.md | 75 + src/pybind/mgr/rook/rook-client-python/conftest.py | 11 + src/pybind/mgr/rook/rook-client-python/generate.sh | 37 + .../rook-client-python/generate_model_classes.py | 298 + src/pybind/mgr/rook/rook-client-python/mypy.ini | 7 + .../mgr/rook/rook-client-python/requirements.txt | 7 + .../rook-client-python/rook-python-client-demo.gif | Bin 0 -> 119572 bytes .../rook-client-python/rook_client/__init__.py | 1 + .../rook/rook-client-python/rook_client/_helper.py | 108 + .../rook_client/cassandra/__init__.py | 0 .../rook_client/cassandra/cluster.py | 308 + .../rook_client/ceph/__init__.py | 0 .../rook_client/ceph/cephclient.py | 95 + .../rook_client/ceph/cephcluster.py | 1119 + .../rook_client/ceph/cephfilesystem.py | 370 + .../rook-client-python/rook_client/ceph/cephnfs.py | 206 + .../rook_client/ceph/cephobjectstore.py | 405 + .../rook_client/edgefs/__init__.py | 0 .../rook_client/edgefs/cluster.py | 285 + .../rook-client-python/rook_client/edgefs/isgw.py | 161 + .../rook-client-python/rook_client/edgefs/nfs.py | 95 + .../rook-client-python/rook_client/edgefs/s3.py | 95 + .../rook-client-python/rook_client/edgefs/s3x.py | 95 + .../rook-client-python/rook_client/edgefs/swift.py | 95 + .../rook_client/tests/__init__.py | 0 .../rook_client/tests/test_README.py | 29 + .../rook_client/tests/test_examples.py | 51 + .../rook_client/tests/test_properties.py | 13 + src/pybind/mgr/rook/rook-client-python/setup.py | 17 + src/pybind/mgr/rook/rook-client-python/tox.ini | 23 + src/pybind/mgr/rook/rook_client/__init__.py | 1 + src/pybind/mgr/rook/rook_client/_helper.py | 108 + src/pybind/mgr/rook/rook_cluster.py | 797 + src/pybind/mgr/selftest/__init__.py | 3 + src/pybind/mgr/selftest/module.py | 490 + src/pybind/mgr/snap_schedule/.gitignore | 1 + src/pybind/mgr/snap_schedule/__init__.py | 11 + src/pybind/mgr/snap_schedule/fs/__init__.py | 0 src/pybind/mgr/snap_schedule/fs/schedule.py | 468 + src/pybind/mgr/snap_schedule/fs/schedule_client.py | 421 + src/pybind/mgr/snap_schedule/module.py | 245 + src/pybind/mgr/snap_schedule/requirements.txt | 1 + src/pybind/mgr/snap_schedule/tests/__init__.py | 0 src/pybind/mgr/snap_schedule/tests/conftest.py | 34 + src/pybind/mgr/snap_schedule/tests/fs/__init__.py | 0 .../mgr/snap_schedule/tests/fs/test_schedule.py | 256 + .../snap_schedule/tests/fs/test_schedule_client.py | 37 + src/pybind/mgr/snap_schedule/tox.ini | 19 + src/pybind/mgr/stats/__init__.py | 1 + src/pybind/mgr/stats/fs/__init__.py | 0 src/pybind/mgr/stats/fs/perf_stats.py | 567 + src/pybind/mgr/stats/module.py | 41 + src/pybind/mgr/status/__init__.py | 1 + src/pybind/mgr/status/module.py | 360 + src/pybind/mgr/telegraf/__init__.py | 1 + src/pybind/mgr/telegraf/basesocket.py | 45 + src/pybind/mgr/telegraf/module.py | 300 + src/pybind/mgr/telegraf/protocol.py | 44 + src/pybind/mgr/telegraf/utils.py | 20 + src/pybind/mgr/telemetry/__init__.py | 1 + src/pybind/mgr/telemetry/module.py | 877 + src/pybind/mgr/test_orchestrator/README.md | 16 + src/pybind/mgr/test_orchestrator/__init__.py | 1 + src/pybind/mgr/test_orchestrator/dummy_data.json | 463 + src/pybind/mgr/test_orchestrator/module.py | 306 + src/pybind/mgr/tests/__init__.py | 229 + src/pybind/mgr/tests/test_tls.py | 35 + src/pybind/mgr/tox.ini | 140 + src/pybind/mgr/volumes/__init__.py | 2 + src/pybind/mgr/volumes/fs/__init__.py | 0 src/pybind/mgr/volumes/fs/async_cloner.py | 413 + src/pybind/mgr/volumes/fs/async_job.py | 302 + src/pybind/mgr/volumes/fs/exception.py | 63 + src/pybind/mgr/volumes/fs/fs_util.py | 201 + src/pybind/mgr/volumes/fs/operations/__init__.py | 0 src/pybind/mgr/volumes/fs/operations/access.py | 139 + .../mgr/volumes/fs/operations/clone_index.py | 98 + src/pybind/mgr/volumes/fs/operations/group.py | 301 + src/pybind/mgr/volumes/fs/operations/index.py | 23 + src/pybind/mgr/volumes/fs/operations/lock.py | 43 + src/pybind/mgr/volumes/fs/operations/pin_util.py | 34 + .../mgr/volumes/fs/operations/rankevicter.py | 114 + src/pybind/mgr/volumes/fs/operations/resolver.py | 26 + .../mgr/volumes/fs/operations/snapshot_util.py | 30 + src/pybind/mgr/volumes/fs/operations/subvolume.py | 74 + src/pybind/mgr/volumes/fs/operations/template.py | 191 + src/pybind/mgr/volumes/fs/operations/trash.py | 145 + .../mgr/volumes/fs/operations/versions/__init__.py | 112 + .../fs/operations/versions/auth_metadata.py | 208 + .../fs/operations/versions/metadata_manager.py | 200 + .../mgr/volumes/fs/operations/versions/op_sm.py | 114 + .../fs/operations/versions/subvolume_attrs.py | 61 + .../fs/operations/versions/subvolume_base.py | 449 + .../volumes/fs/operations/versions/subvolume_v1.py | 904 + .../volumes/fs/operations/versions/subvolume_v2.py | 394 + src/pybind/mgr/volumes/fs/operations/volume.py | 198 + src/pybind/mgr/volumes/fs/purge_queue.py | 109 + src/pybind/mgr/volumes/fs/vol_spec.py | 46 + src/pybind/mgr/volumes/fs/volume.py | 999 + src/pybind/mgr/volumes/module.py | 828 + src/pybind/mgr/zabbix/__init__.py | 1 + src/pybind/mgr/zabbix/module.py | 490 + src/pybind/mgr/zabbix/zabbix_template.xml | 3249 +++ 1723 files changed, 414596 insertions(+) create mode 100644 src/pybind/mgr/.gitignore create mode 100644 src/pybind/mgr/.pylintrc create mode 100644 src/pybind/mgr/CMakeLists.txt create mode 100644 src/pybind/mgr/alerts/__init__.py create mode 100644 src/pybind/mgr/alerts/module.py create mode 100644 src/pybind/mgr/balancer/__init__.py create mode 100644 src/pybind/mgr/balancer/module.py create mode 100644 src/pybind/mgr/ceph_module.pyi create mode 100644 src/pybind/mgr/cephadm/.gitignore create mode 100644 src/pybind/mgr/cephadm/HACKING.rst create mode 100644 src/pybind/mgr/cephadm/Vagrantfile create mode 100644 src/pybind/mgr/cephadm/__init__.py create mode 100644 src/pybind/mgr/cephadm/autotune.py create mode 100644 src/pybind/mgr/cephadm/ceph.repo create mode 100644 src/pybind/mgr/cephadm/configchecks.py create mode 100644 src/pybind/mgr/cephadm/inventory.py create mode 100644 src/pybind/mgr/cephadm/migrations.py create mode 100644 src/pybind/mgr/cephadm/module.py create mode 100644 src/pybind/mgr/cephadm/offline_watcher.py create mode 100644 src/pybind/mgr/cephadm/registry.py create mode 100644 src/pybind/mgr/cephadm/remotes.py create mode 100644 src/pybind/mgr/cephadm/schedule.py create mode 100644 src/pybind/mgr/cephadm/serve.py create mode 100644 src/pybind/mgr/cephadm/services/__init__.py create mode 100644 src/pybind/mgr/cephadm/services/cephadmservice.py create mode 100644 src/pybind/mgr/cephadm/services/container.py create mode 100644 src/pybind/mgr/cephadm/services/exporter.py create mode 100644 src/pybind/mgr/cephadm/services/ingress.py create mode 100644 src/pybind/mgr/cephadm/services/iscsi.py create mode 100644 src/pybind/mgr/cephadm/services/monitoring.py create mode 100644 src/pybind/mgr/cephadm/services/nfs.py create mode 100644 src/pybind/mgr/cephadm/services/osd.py create mode 100644 src/pybind/mgr/cephadm/template.py create mode 100644 src/pybind/mgr/cephadm/templates/blink_device_light_cmd.j2 create mode 100644 src/pybind/mgr/cephadm/templates/services/alertmanager/alertmanager.yml.j2 create mode 100644 src/pybind/mgr/cephadm/templates/services/grafana/ceph-dashboard.yml.j2 create mode 100644 src/pybind/mgr/cephadm/templates/services/grafana/grafana.ini.j2 create mode 100644 src/pybind/mgr/cephadm/templates/services/ingress/haproxy.cfg.j2 create mode 100644 src/pybind/mgr/cephadm/templates/services/ingress/keepalived.conf.j2 create mode 100644 src/pybind/mgr/cephadm/templates/services/iscsi/iscsi-gateway.cfg.j2 create mode 100644 src/pybind/mgr/cephadm/templates/services/nfs/ganesha.conf.j2 create mode 100644 src/pybind/mgr/cephadm/templates/services/prometheus/prometheus.yml.j2 create mode 100644 src/pybind/mgr/cephadm/tests/__init__.py create mode 100644 src/pybind/mgr/cephadm/tests/conftest.py create mode 100644 src/pybind/mgr/cephadm/tests/fixtures.py create mode 100644 src/pybind/mgr/cephadm/tests/test_autotune.py create mode 100644 src/pybind/mgr/cephadm/tests/test_cephadm.py create mode 100644 src/pybind/mgr/cephadm/tests/test_completion.py create mode 100644 src/pybind/mgr/cephadm/tests/test_configchecks.py create mode 100644 src/pybind/mgr/cephadm/tests/test_facts.py create mode 100644 src/pybind/mgr/cephadm/tests/test_migration.py create mode 100644 src/pybind/mgr/cephadm/tests/test_osd_removal.py create mode 100644 src/pybind/mgr/cephadm/tests/test_scheduling.py create mode 100644 src/pybind/mgr/cephadm/tests/test_services.py create mode 100644 src/pybind/mgr/cephadm/tests/test_spec.py create mode 100644 src/pybind/mgr/cephadm/tests/test_template.py create mode 100644 src/pybind/mgr/cephadm/tests/test_upgrade.py create mode 100644 src/pybind/mgr/cephadm/upgrade.py create mode 100644 src/pybind/mgr/cephadm/utils.py create mode 100644 src/pybind/mgr/cephadm/vagrant.config.example.json create mode 100644 src/pybind/mgr/cli_api/__init__.py create mode 100755 src/pybind/mgr/cli_api/module.py create mode 100644 src/pybind/mgr/cli_api/tests/__init__.py create mode 100644 src/pybind/mgr/cli_api/tests/test_cli_api.py create mode 100644 src/pybind/mgr/crash/__init__.py create mode 100644 src/pybind/mgr/crash/module.py create mode 100644 src/pybind/mgr/dashboard/.coveragerc create mode 100644 src/pybind/mgr/dashboard/.editorconfig create mode 100644 src/pybind/mgr/dashboard/.gitignore create mode 100644 src/pybind/mgr/dashboard/.pylintrc create mode 100644 src/pybind/mgr/dashboard/CMakeLists.txt create mode 100644 src/pybind/mgr/dashboard/HACKING.rst create mode 100644 src/pybind/mgr/dashboard/README.rst create mode 100644 src/pybind/mgr/dashboard/__init__.py create mode 100644 src/pybind/mgr/dashboard/api/__init__.py create mode 100644 src/pybind/mgr/dashboard/api/doc.py create mode 100644 src/pybind/mgr/dashboard/awsauth.py create mode 100644 src/pybind/mgr/dashboard/cherrypy_backports.py create mode 100755 src/pybind/mgr/dashboard/ci/cephadm/bootstrap-cluster.sh create mode 100755 src/pybind/mgr/dashboard/ci/cephadm/ceph_cluster.yml create mode 100755 src/pybind/mgr/dashboard/ci/cephadm/run-cephadm-e2e-tests.sh create mode 100755 src/pybind/mgr/dashboard/ci/cephadm/start-cluster.sh create mode 100644 src/pybind/mgr/dashboard/ci/check_grafana_dashboards.py create mode 100644 src/pybind/mgr/dashboard/constraints.txt create mode 100644 src/pybind/mgr/dashboard/controllers/__init__.py create mode 100644 src/pybind/mgr/dashboard/controllers/_api_router.py create mode 100644 src/pybind/mgr/dashboard/controllers/_auth.py create mode 100644 src/pybind/mgr/dashboard/controllers/_base_controller.py create mode 100644 src/pybind/mgr/dashboard/controllers/_docs.py create mode 100644 src/pybind/mgr/dashboard/controllers/_endpoint.py create mode 100644 src/pybind/mgr/dashboard/controllers/_helpers.py create mode 100644 src/pybind/mgr/dashboard/controllers/_permissions.py create mode 100644 src/pybind/mgr/dashboard/controllers/_rest_controller.py create mode 100644 src/pybind/mgr/dashboard/controllers/_router.py create mode 100644 src/pybind/mgr/dashboard/controllers/_task.py create mode 100644 src/pybind/mgr/dashboard/controllers/_ui_router.py create mode 100644 src/pybind/mgr/dashboard/controllers/_version.py create mode 100644 src/pybind/mgr/dashboard/controllers/auth.py create mode 100644 src/pybind/mgr/dashboard/controllers/cephfs.py create mode 100644 src/pybind/mgr/dashboard/controllers/cluster.py create mode 100644 src/pybind/mgr/dashboard/controllers/cluster_configuration.py create mode 100644 src/pybind/mgr/dashboard/controllers/crush_rule.py create mode 100644 src/pybind/mgr/dashboard/controllers/daemon.py create mode 100644 src/pybind/mgr/dashboard/controllers/docs.py create mode 100644 src/pybind/mgr/dashboard/controllers/erasure_code_profile.py create mode 100644 src/pybind/mgr/dashboard/controllers/frontend_logging.py create mode 100644 src/pybind/mgr/dashboard/controllers/grafana.py create mode 100644 src/pybind/mgr/dashboard/controllers/health.py create mode 100644 src/pybind/mgr/dashboard/controllers/home.py create mode 100644 src/pybind/mgr/dashboard/controllers/host.py create mode 100644 src/pybind/mgr/dashboard/controllers/iscsi.py create mode 100644 src/pybind/mgr/dashboard/controllers/logs.py create mode 100644 src/pybind/mgr/dashboard/controllers/mgr_modules.py create mode 100644 src/pybind/mgr/dashboard/controllers/monitor.py create mode 100644 src/pybind/mgr/dashboard/controllers/nfs.py create mode 100644 src/pybind/mgr/dashboard/controllers/orchestrator.py create mode 100644 src/pybind/mgr/dashboard/controllers/osd.py create mode 100644 src/pybind/mgr/dashboard/controllers/perf_counters.py create mode 100644 src/pybind/mgr/dashboard/controllers/pool.py create mode 100644 src/pybind/mgr/dashboard/controllers/prometheus.py create mode 100644 src/pybind/mgr/dashboard/controllers/rbd.py create mode 100644 src/pybind/mgr/dashboard/controllers/rbd_mirroring.py create mode 100644 src/pybind/mgr/dashboard/controllers/rgw.py create mode 100644 src/pybind/mgr/dashboard/controllers/role.py create mode 100644 src/pybind/mgr/dashboard/controllers/saml2.py create mode 100644 src/pybind/mgr/dashboard/controllers/service.py create mode 100644 src/pybind/mgr/dashboard/controllers/settings.py create mode 100644 src/pybind/mgr/dashboard/controllers/summary.py create mode 100644 src/pybind/mgr/dashboard/controllers/task.py create mode 100644 src/pybind/mgr/dashboard/controllers/telemetry.py create mode 100644 src/pybind/mgr/dashboard/controllers/user.py create mode 100644 src/pybind/mgr/dashboard/exceptions.py create mode 100644 src/pybind/mgr/dashboard/frontend/.browserslistrc create mode 100644 src/pybind/mgr/dashboard/frontend/.editorconfig create mode 100644 src/pybind/mgr/dashboard/frontend/.gherkin-lintrc create mode 100644 src/pybind/mgr/dashboard/frontend/.gitignore create mode 100644 src/pybind/mgr/dashboard/frontend/.htmllintrc create mode 100644 src/pybind/mgr/dashboard/frontend/.npmrc create mode 100644 src/pybind/mgr/dashboard/frontend/.prettierignore create mode 100644 src/pybind/mgr/dashboard/frontend/.prettierrc create mode 100644 src/pybind/mgr/dashboard/frontend/.stylelintrc create mode 100644 src/pybind/mgr/dashboard/frontend/angular.json create mode 100644 src/pybind/mgr/dashboard/frontend/applitools.config.js create mode 100644 src/pybind/mgr/dashboard/frontend/babel.config.js create mode 100755 src/pybind/mgr/dashboard/frontend/cd.js create mode 100644 src/pybind/mgr/dashboard/frontend/cypress.json create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/fixtures/block-rbd-status.json create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/fixtures/nfs-ganesha-status.json create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/fixtures/orchestrator/inventory.json create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/fixtures/orchestrator/services.json create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/fixtures/rgw-status.json create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/block/images.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/block/images.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/block/iscsi.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/block/iscsi.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/block/mirroring.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/block/mirroring.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/configuration.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/configuration.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/create-cluster.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/crush-map.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/crush-map.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/inventory.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/logs.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/logs.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/mgr-modules.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/mgr-modules.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/monitors.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/monitors.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/services.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/common/01-global.feature.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/common/create-cluster/create-cluster.feature.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/common/grafana.feature.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/common/urls.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/filesystems/filesystems.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/filesystems/filesystems.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/01-hosts.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/03-inventory.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/04-osds.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/05-services.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/grafana/grafana.feature create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/01-create-cluster-welcome.feature create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/02-create-cluster-add-host.feature create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/03-create-cluster-create-services.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/04-create-cluster-create-osds.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/05-create-cluster-review.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/06-cluster-check.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/07-osds.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/08-hosts.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/09-services.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/10-nfs-exports.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/nfs/nfs-export.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/page-helper.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/pools/pools.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/pools/pools.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/buckets.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/buckets.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/daemons.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/daemons.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/ui/api-docs.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/ui/api-docs.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/ui/language.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/ui/language.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/ui/login.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/ui/login.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/ui/navigation.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/ui/navigation.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.e2e-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.po.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/visualTests/dashboard.vrt-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/visualTests/login.vrt-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/plugins/index.js create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/support/commands.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/support/eyes-index.d.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/support/index.ts create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/tsconfig.json create mode 100644 src/pybind/mgr/dashboard/frontend/dist/en-US/117.9781bbf8cc6a4aaa7e8e.js create mode 100644 src/pybind/mgr/dashboard/frontend/dist/en-US/281.0d0cd268ddc6a6760dd4.js create mode 100644 src/pybind/mgr/dashboard/frontend/dist/en-US/3rdpartylicenses.txt create mode 100644 src/pybind/mgr/dashboard/frontend/dist/en-US/483.f42c1d67e206231ecdac.js create mode 100644 src/pybind/mgr/dashboard/frontend/dist/en-US/Ceph_Logo.487a0001b327fa7f5232.svg create mode 100644 src/pybind/mgr/dashboard/frontend/dist/en-US/assets/Ceph_Ceph_Logo_with_text_red_white.svg create mode 100644 src/pybind/mgr/dashboard/frontend/dist/en-US/assets/Ceph_Ceph_Logo_with_text_white.svg create mode 100644 src/pybind/mgr/dashboard/frontend/dist/en-US/assets/Ceph_Logo.svg create mode 100644 src/pybind/mgr/dashboard/frontend/dist/en-US/assets/ceph_background.gif create mode 100755 src/pybind/mgr/dashboard/frontend/dist/en-US/assets/loading.gif create mode 100644 src/pybind/mgr/dashboard/frontend/dist/en-US/assets/logo-mini.png create mode 100644 src/pybind/mgr/dashboard/frontend/dist/en-US/assets/prometheus_logo.svg create mode 100644 src/pybind/mgr/dashboard/frontend/dist/en-US/ceph_background.e82dd79127290ddbe8cb.gif create mode 100644 src/pybind/mgr/dashboard/frontend/dist/en-US/favicon.ico create mode 100644 src/pybind/mgr/dashboard/frontend/dist/en-US/forkawesome-webfont.2dfb5f36fc148e26e398.woff create mode 100644 src/pybind/mgr/dashboard/frontend/dist/en-US/forkawesome-webfont.7c20758e3e7c7dff7c8d.woff2 create mode 100644 src/pybind/mgr/dashboard/frontend/dist/en-US/forkawesome-webfont.86541105409e56d17291.svg create mode 100644 src/pybind/mgr/dashboard/frontend/dist/en-US/forkawesome-webfont.e182ad6df04f9177b326.eot create mode 100644 src/pybind/mgr/dashboard/frontend/dist/en-US/forkawesome-webfont.ee4d8bfd0af89fc714a2.ttf create mode 100644 src/pybind/mgr/dashboard/frontend/dist/en-US/index.html create mode 100644 src/pybind/mgr/dashboard/frontend/dist/en-US/main.c3b711a3156fe72f66f4.js create mode 100644 src/pybind/mgr/dashboard/frontend/dist/en-US/polyfills.2068f3f22a496426465b.js create mode 100644 src/pybind/mgr/dashboard/frontend/dist/en-US/prometheus_logo.8b3183e5a2db0e87bb2b.svg create mode 100644 src/pybind/mgr/dashboard/frontend/dist/en-US/runtime.dfeb6a20b4d203b567dc.js create mode 100644 src/pybind/mgr/dashboard/frontend/dist/en-US/scripts.6bda3fa7e09a87cd4228.js create mode 100644 src/pybind/mgr/dashboard/frontend/dist/en-US/styles.f05c06a6a64f4730faae.css create mode 100644 src/pybind/mgr/dashboard/frontend/html-linter.config.json create mode 100644 src/pybind/mgr/dashboard/frontend/i18n.config.json create mode 100644 src/pybind/mgr/dashboard/frontend/ngcc.config.js create mode 100644 src/pybind/mgr/dashboard/frontend/package-lock.json create mode 100644 src/pybind/mgr/dashboard/frontend/package.json create mode 100644 src/pybind/mgr/dashboard/frontend/proxy.conf.json.sample create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/app.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/app.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/app.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/app.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/app.module.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-setting/iscsi-setting.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-setting/iscsi-setting.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-setting/iscsi-setting.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-setting/iscsi-setting.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-tabs/iscsi-tabs.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-tabs/iscsi-tabs.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-tabs/iscsi-tabs.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-tabs/iscsi-tabs.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-details/iscsi-target-details.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-details/iscsi-target-details.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-details/iscsi-target-details.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-details/iscsi-target-details.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-discovery-modal/iscsi-target-discovery-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-discovery-modal/iscsi-target-discovery-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-discovery-modal/iscsi-target-discovery-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-discovery-modal/iscsi-target-discovery-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-image-settings-modal/iscsi-target-image-settings-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-image-settings-modal/iscsi-target-image-settings-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-image-settings-modal/iscsi-target-image-settings-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-image-settings-modal/iscsi-target-image-settings-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-iqn-settings-modal/iscsi-target-iqn-settings-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-iqn-settings-modal/iscsi-target-iqn-settings-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-iqn-settings-modal/iscsi-target-iqn-settings-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-iqn-settings-modal/iscsi-target-iqn-settings-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi/iscsi.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi/iscsi.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi/iscsi.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi/iscsi.component.ts create mode 100755 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/bootstrap-create-modal/bootstrap-create-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/bootstrap-create-modal/bootstrap-create-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/bootstrap-create-modal/bootstrap-create-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/bootstrap-create-modal/bootstrap-create-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/bootstrap-import-modal/bootstrap-import-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/bootstrap-import-modal/bootstrap-import-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/bootstrap-import-modal/bootstrap-import-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/bootstrap-import-modal/bootstrap-import-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/daemon-list/daemon-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/daemon-list/daemon-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/daemon-list/daemon-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/daemon-list/daemon-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/image-list/image-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/image-list/image-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/image-list/image-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/image-list/image-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/mirror-health-color.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/mirror-health-color.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/mirroring.module.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/overview/overview.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/overview/overview.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/overview/overview.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/overview/overview.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/pool-edit-mode-modal/pool-edit-mode-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/pool-edit-mode-modal/pool-edit-mode-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/pool-edit-mode-modal/pool-edit-mode-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/pool-edit-mode-modal/pool-edit-mode-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/pool-edit-mode-modal/pool-edit-mode-response.model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/pool-edit-peer-modal/pool-edit-peer-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/pool-edit-peer-modal/pool-edit-peer-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/pool-edit-peer-modal/pool-edit-peer-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/pool-edit-peer-modal/pool-edit-peer-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/pool-edit-peer-modal/pool-edit-peer-response.model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/pool-list/pool-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/pool-list/pool-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/pool-list/pool-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/pool-list/pool-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-configuration-form/rbd-configuration-form.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-configuration-form/rbd-configuration-form.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-configuration-form/rbd-configuration-form.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-configuration-form/rbd-configuration-form.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-configuration-list/rbd-configuration-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-configuration-list/rbd-configuration-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-configuration-list/rbd-configuration-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-configuration-list/rbd-configuration-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-details/rbd-details.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-details/rbd-details.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-details/rbd-details.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-details/rbd-details.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-feature.interface.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form-clone-request.model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form-copy-request.model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form-create-request.model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form-edit-request.model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form-mode.enum.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form-response.model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-parent.model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-namespace-form/rbd-namespace-form-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-namespace-form/rbd-namespace-form-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-namespace-form/rbd-namespace-form-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-namespace-form/rbd-namespace-form-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-namespace-list/rbd-namespace-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-namespace-list/rbd-namespace-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-namespace-list/rbd-namespace-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-namespace-list/rbd-namespace-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-performance/rbd-performance.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-performance/rbd-performance.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-performance/rbd-performance.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-performance/rbd-performance.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-form/rbd-snapshot-form-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-form/rbd-snapshot-form-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-form/rbd-snapshot-form-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-form/rbd-snapshot-form-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-actions.model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot.model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-tabs/rbd-tabs.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-tabs/rbd-tabs.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-tabs/rbd-tabs.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-tabs/rbd-tabs.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-list/rbd-trash-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-list/rbd-trash-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-list/rbd-trash-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-list/rbd-trash-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-move-modal/rbd-trash-move-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-move-modal/rbd-trash-move-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-move-modal/rbd-trash-move-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-move-modal/rbd-trash-move-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-purge-modal/rbd-trash-purge-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-purge-modal/rbd-trash-purge-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-purge-modal/rbd-trash-purge-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-purge-modal/rbd-trash-purge-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-restore-modal/rbd-trash-restore-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-restore-modal/rbd-trash-restore-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-restore-modal/rbd-trash-restore-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-restore-modal/rbd-trash-restore-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/ceph.module.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-chart/cephfs-chart.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-chart/cephfs-chart.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-chart/cephfs-chart.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-chart/cephfs-chart.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-clients/cephfs-clients.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-clients/cephfs-clients.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-clients/cephfs-clients.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-clients/cephfs-clients.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-detail/cephfs-detail.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-detail/cephfs-detail.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-detail/cephfs-detail.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-detail/cephfs-detail.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-directories/cephfs-directories.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-directories/cephfs-directories.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-directories/cephfs-directories.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-directories/cephfs-directories.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-list/cephfs-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-list/cephfs-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-list/cephfs-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-list/cephfs-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-tabs/cephfs-tabs.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-tabs/cephfs-tabs.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-tabs/cephfs-tabs.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-tabs/cephfs-tabs.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs.module.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts create mode 100755 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-details/configuration-details.component.html create mode 100755 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-details/configuration-details.component.scss create mode 100755 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-details/configuration-details.component.spec.ts create mode 100755 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-details/configuration-details.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form-create-request.model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster-review.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster-review.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster-review.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster-review.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/crushmap/crushmap.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/crushmap/crushmap.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/crushmap/crushmap.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/crushmap/crushmap.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/fixtures/host_list_response.json create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-details/host-details.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-details/host-details.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-details/host-details.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-details/host-details.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-form/host-form.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-form/host-form.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-form/host-form.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-form/host-form.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/fixtures/inventory_list_response.json create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/inventory-device.model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/inventory-devices.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/inventory-devices.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/inventory-devices.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/inventory-devices.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-host.model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/logs/logs.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/logs/logs.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/logs/logs.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/logs/logs.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-details/mgr-module-details.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-details/mgr-module-details.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-details/mgr-module-details.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-details/mgr-module-details.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-form/mgr-module-form.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-form/mgr-module-form.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-form/mgr-module-form.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-form/mgr-module-form.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-list/mgr-module-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-list/mgr-module-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-list/mgr-module-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-list/mgr-module-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-modules.module.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/monitor/monitor.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/monitor/monitor.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/monitor/monitor.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/monitor/monitor.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-creation-preview-modal/osd-creation-preview-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-creation-preview-modal/osd-creation-preview-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-creation-preview-modal/osd-creation-preview-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-creation-preview-modal/osd-creation-preview-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-details/osd-details.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-details/osd-details.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-details/osd-details.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-details/osd-details.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-groups/devices-selection-change-event.interface.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-groups/devices-selection-clear-event.interface.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-groups/osd-devices-selection-groups.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-groups/osd-devices-selection-groups.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-groups/osd-devices-selection-groups.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-groups/osd-devices-selection-groups.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-modal/osd-devices-selection-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-modal/osd-devices-selection-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-modal/osd-devices-selection-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-modal/osd-devices-selection-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-flags-indiv-modal/osd-flags-indiv-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-flags-indiv-modal/osd-flags-indiv-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-flags-indiv-modal/osd-flags-indiv-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-flags-indiv-modal/osd-flags-indiv-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-flags-modal/osd-flags-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-flags-modal/osd-flags-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-flags-modal/osd-flags-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-flags-modal/osd-flags-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-form/drive-group.model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-form/osd-feature.interface.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-form/osd-form.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-form/osd-form.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-form/osd-form.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-form/osd-form.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/fixtures/osd_list_response.json create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.options.ts create mode 100755 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.html create mode 100755 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.scss create mode 100755 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.spec.ts create mode 100755 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-reweight-modal/osd-reweight-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-reweight-modal/osd-reweight-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-reweight-modal/osd-reweight-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-reweight-modal/osd-reweight-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-scrub-modal/osd-scrub-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-scrub-modal/osd-scrub-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-scrub-modal/osd-scrub-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-scrub-modal/osd-scrub-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-list-helper.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-tabs/prometheus-tabs.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-tabs/prometheus-tabs.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-tabs/prometheus-tabs.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-tabs/prometheus-tabs.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/rules-list/rules-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/rules-list/rules-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/rules-list/rules-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/rules-list/rules-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-form/silence-form.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-form/silence-form.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-form/silence-form.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-form/silence-form.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-list/silence-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-list/silence-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-list/silence-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-list/silence-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-matcher-modal/silence-matcher-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-matcher-modal/silence-matcher-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-matcher-modal/silence-matcher-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-matcher-modal/silence-matcher-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/placement.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/placement.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-daemon-list/service-daemon-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-daemon-list/service-daemon-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-daemon-list/service-daemon-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-daemon-list/service-daemon-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-details/service-details.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-details/service-details.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-details/service-details.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-details/service-details.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/services.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/services.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/services.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/services.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard.module.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard/dashboard.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard/dashboard.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard/dashboard.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard/dashboard.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/info-card/info-card-popover.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/info-card/info-card.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/info-card/info-card.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/info-card/info-card.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/info-card/info-card.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/info-group/info-group.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/info-group/info-group.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/info-group/info-group.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/info-group/info-group.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mds-summary.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mds-summary.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mgr-summary.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mgr-summary.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mon-summary.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/mon-summary.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/osd-summary.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/osd-summary.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/models/nfs.fsal.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-details/nfs-details.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-details/nfs-details.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-details/nfs-details.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-details/nfs-details.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form-client/nfs-form-client.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form-client/nfs-form-client.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form-client/nfs-form-client.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form-client/nfs-form-client.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-list/nfs-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-list/nfs-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-list/nfs-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-list/nfs-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs.module.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/performance-counter/performance-counter.module.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/performance-counter/performance-counter/performance-counter.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/performance-counter/performance-counter/performance-counter.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/performance-counter/performance-counter/performance-counter.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/performance-counter/performance-counter/performance-counter.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/performance-counter/table-performance-counter/table-performance-counter.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/performance-counter/table-performance-counter/table-performance-counter.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/performance-counter/table-performance-counter/table-performance-counter.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/performance-counter/table-performance-counter/table-performance-counter.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/crush-rule-form-modal/crush-rule-form-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/crush-rule-form-modal/crush-rule-form-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/crush-rule-form-modal/crush-rule-form-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/crush-rule-form-modal/crush-rule-form-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/erasure-code-profile-form/erasure-code-profile-form-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/erasure-code-profile-form/erasure-code-profile-form-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/erasure-code-profile-form/erasure-code-profile-form-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/erasure-code-profile-form/erasure-code-profile-form-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-details/pool-details.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-details/pool-details.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-details/pool-details.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-details/pool-details.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form-data.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-stat.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool.module.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-bucket-mfa-delete.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-bucket-versioning.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-daemon.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-user-capabilities.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-user-capability.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-user-s3-key.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-user-subuser.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-user-swift-key.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-capability-modal/rgw-user-capability-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-capability-modal/rgw-user-capability-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-capability-modal/rgw-user-capability-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-capability-modal/rgw-user-capability-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-s3-key-modal/rgw-user-s3-key-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-s3-key-modal/rgw-user-s3-key-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-s3-key-modal/rgw-user-s3-key-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-s3-key-modal/rgw-user-s3-key-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-subuser-modal/rgw-user-subuser-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-subuser-modal/rgw-user-subuser-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-subuser-modal/rgw-user-subuser-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-subuser-modal/rgw-user-subuser-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-swift-key-modal/rgw-user-swift-key-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-swift-key-modal/rgw-user-swift-key-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-swift-key-modal/rgw-user-swift-key-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-swift-key-modal/rgw-user-swift-key-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/ceph-shared.module.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/device-list/device-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/device-list/device-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/device-list/device-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/device-list/device-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/pg-category.model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/pg-category.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/pg-category.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/fixtures/smart_data_version_1_0_ata_response.json create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/fixtures/smart_data_version_1_0_nvme_response.json create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/fixtures/smart_data_version_1_0_scsi_response.json create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/smart-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/smart-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/smart-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/smart-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/auth.module.ts create mode 100755 src/pybind/mgr/dashboard/frontend/src/app/core/auth/login-password-form/login-password-form.component.html create mode 100755 src/pybind/mgr/dashboard/frontend/src/app/core/auth/login-password-form/login-password-form.component.scss create mode 100755 src/pybind/mgr/dashboard/frontend/src/app/core/auth/login-password-form/login-password-form.component.spec.ts create mode 100755 src/pybind/mgr/dashboard/frontend/src/app/core/auth/login-password-form/login-password-form.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-details/role-details.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-details/role-details.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-details/role-details.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-details/role-details.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-form/role-form-mode.enum.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-form/role-form.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-form/role-form.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-form/role-form.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-form/role-form.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-form/role-form.model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-list/role-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-list/role-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-list/role-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-list/role-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form-mode.enum.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form-role.model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-list/user-list.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-list/user-list.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-list/user-list.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-list/user-list.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-form/user-password-form.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-form/user-password-form.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-form/user-password-form.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-form/user-password-form.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-tabs/user-tabs.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-tabs/user-tabs.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-tabs/user-tabs.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-tabs/user-tabs.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/context/context.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/context/context.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/context/context.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/context/context.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/core.module.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/error/error.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/error/error.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/error/error.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/error/error.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/error/error.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/layouts/blank-layout/blank-layout.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/layouts/blank-layout/blank-layout.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/layouts/blank-layout/blank-layout.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/layouts/blank-layout/blank-layout.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/layouts/login-layout/login-layout.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/layouts/login-layout/login-layout.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/layouts/login-layout/login-layout.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/layouts/login-layout/login-layout.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/about/about.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/about/about.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/about/about.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/about/about.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/administration/administration.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/administration/administration.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/administration/administration.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/administration/administration.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/api-docs/api-docs.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/api-docs/api-docs.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/api-docs/api-docs.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/breadcrumbs/breadcrumbs.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/breadcrumbs/breadcrumbs.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/breadcrumbs/breadcrumbs.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/breadcrumbs/breadcrumbs.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/dashboard-help/dashboard-help.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/dashboard-help/dashboard-help.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/dashboard-help/dashboard-help.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/dashboard-help/dashboard-help.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/identity/identity.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/identity/identity.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/identity/identity.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/identity/identity.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation.module.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notifications/notifications.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notifications/notifications.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notifications/notifications.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notifications/notifications.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/api-client.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/api-client.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/auth.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/auth.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/ceph-service.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/cluster.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/cluster.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/configuration.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/configuration.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/crush-rule.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/crush-rule.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/custom-login-banner.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/custom-login-banner.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/daemon.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/daemon.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/erasure-code-profile.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/erasure-code-profile.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/health.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/health.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/host.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/host.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/iscsi.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/iscsi.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/logging.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/logging.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/logs.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/logs.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/mgr-module.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/mgr-module.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/monitor.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/monitor.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/motd.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/motd.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/nfs.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/nfs.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/orchestrator.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/orchestrator.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/osd.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/osd.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/performance-counter.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/performance-counter.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/pool.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/pool.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/prometheus.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/prometheus.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/rbd-mirroring.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/rbd-mirroring.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/rbd.model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/rbd.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/rbd.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-daemon.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-daemon.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-site.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-site.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/role.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/role.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/scope.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/scope.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/settings.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/settings.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/telemetry.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/telemetry.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/user.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/user.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/classes/cd-helper.class.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/classes/cd-helper.class.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/classes/crush.node.selection.class.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/classes/crush.node.selection.class.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/classes/css-helper.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/classes/list-with-details.class.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/classes/table-status-view-cache.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/classes/table-status-view-cache.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/classes/table-status.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/classes/table-status.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/back-button/back-button.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/back-button/back-button.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/back-button/back-button.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/back-button/back-button.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/config-option/config-option.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/config-option/config-option.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/config-option/config-option.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/config-option/config-option.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/config-option/config-option.model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/config-option/config-option.types.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/config-option/config-option.types.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/confirmation-modal/confirmation-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/confirmation-modal/confirmation-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/confirmation-modal/confirmation-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/confirmation-modal/confirmation-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/copy2clipboard-button/copy2clipboard-button.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/copy2clipboard-button/copy2clipboard-button.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/copy2clipboard-button/copy2clipboard-button.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/copy2clipboard-button/copy2clipboard-button.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/custom-login-banner/custom-login-banner.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/custom-login-banner/custom-login-banner.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/custom-login-banner/custom-login-banner.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/custom-login-banner/custom-login-banner.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/date-time-picker/date-time-picker.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/date-time-picker/date-time-picker.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/date-time-picker/date-time-picker.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/date-time-picker/date-time-picker.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/doc/doc.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/doc/doc.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/doc/doc.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/doc/doc.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/download-button/download-button.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/download-button/download-button.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/download-button/download-button.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/download-button/download-button.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/form-button-panel/form-button-panel.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/form-button-panel/form-button-panel.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/form-button-panel/form-button-panel.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/form-button-panel/form-button-panel.component.ts create mode 100755 src/pybind/mgr/dashboard/frontend/src/app/shared/components/form-modal/form-modal.component.html create mode 100755 src/pybind/mgr/dashboard/frontend/src/app/shared/components/form-modal/form-modal.component.scss create mode 100755 src/pybind/mgr/dashboard/frontend/src/app/shared/components/form-modal/form-modal.component.spec.ts create mode 100755 src/pybind/mgr/dashboard/frontend/src/app/shared/components/form-modal/form-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/grafana/grafana.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/grafana/grafana.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/grafana/grafana.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/grafana/grafana.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/helper/helper.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/helper/helper.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/helper/helper.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/helper/helper.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/language-selector/language-selector.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/language-selector/language-selector.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/language-selector/language-selector.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/language-selector/language-selector.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/language-selector/supported-languages.enum.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/loading-panel/loading-panel.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/loading-panel/loading-panel.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/loading-panel/loading-panel.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/loading-panel/loading-panel.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/modal/modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/modal/modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/modal/modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/modal/modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/motd/motd.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/motd/motd.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/motd/motd.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/motd/motd.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/orchestrator-doc-panel/orchestrator-doc-panel.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/orchestrator-doc-panel/orchestrator-doc-panel.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/orchestrator-doc-panel/orchestrator-doc-panel.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/orchestrator-doc-panel/orchestrator-doc-panel.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/pwd-expiration-notification/pwd-expiration-notification.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/pwd-expiration-notification/pwd-expiration-notification.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/pwd-expiration-notification/pwd-expiration-notification.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/pwd-expiration-notification/pwd-expiration-notification.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/refresh-selector/refresh-selector.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/refresh-selector/refresh-selector.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/refresh-selector/refresh-selector.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/refresh-selector/refresh-selector.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/select-badges/select-badges.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/select-badges/select-badges.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/select-badges/select-badges.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/select-badges/select-badges.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/select/select-messages.model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/select/select-option.model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/select/select.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/select/select.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/select/select.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/select/select.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/sparkline/sparkline.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/sparkline/sparkline.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/sparkline/sparkline.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/sparkline/sparkline.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/submit-button/submit-button.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/submit-button/submit-button.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/submit-button/submit-button.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/submit-button/submit-button.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/usage-bar/usage-bar.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/usage-bar/usage-bar.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/usage-bar/usage-bar.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/usage-bar/usage-bar.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/wizard/wizard.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/wizard/wizard.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/wizard/wizard.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/wizard/wizard.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/constants/app.constants.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/datatable.module.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-actions/table-actions.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-actions/table-actions.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-actions/table-actions.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-actions/table-actions.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-key-value/table-key-value.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-key-value/table-key-value.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-key-value/table-key-value.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-key-value/table-key-value.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/decorators/cd-encode.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/decorators/cd-encode.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/autofocus.directive.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/autofocus.directive.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/dimless-binary-per-second.directive.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/dimless-binary-per-second.directive.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/dimless-binary.directive.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/dimless-binary.directive.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/directives.module.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/form-input-disable.directive.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/form-input-disable.directive.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/form-loading.directive.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/form-loading.directive.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/form-scope.directive.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/form-scope.directive.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/iops.directive.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/iops.directive.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/milliseconds.directive.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/milliseconds.directive.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/ng-bootstrap-form-validation/cd-form-control.directive.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/ng-bootstrap-form-validation/cd-form-control.directive.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/ng-bootstrap-form-validation/cd-form-group.directive.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/ng-bootstrap-form-validation/cd-form-group.directive.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/ng-bootstrap-form-validation/cd-form-validation.directive.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/ng-bootstrap-form-validation/cd-form-validation.directive.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/password-button.directive.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/password-button.directive.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/stateful-tab.directive.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/stateful-tab.directive.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/trim.directive.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/trim.directive.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/enum/cell-template.enum.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/enum/components.enum.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/enum/health-color.enum.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/enum/icons.enum.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/enum/notification-type.enum.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/enum/unix_errno.enum.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/enum/view-cache-status.enum.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-form-builder.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-form-builder.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-form-group.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-form-group.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-form.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-form.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/alertmanager-silence.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/breadcrumbs.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-form-modal-field-config.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-notification.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-notification.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-pwd-expiration-settings.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-pwd-policy-settings.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-table-action.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-table-column-filter.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-table-column-filters-change.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-table-column.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-table-fetch-data-context.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-table-paging.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-table-selection.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-user-config.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/cephfs-directory-models.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/chart-tooltip.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/configuration.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/credentials.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/crush-node.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/crush-rule.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/crush-step.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/daemon.interface.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/devices.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/erasure-code-profile.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/executing-task.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/finished-task.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/flag.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/image-spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/inventory-device-type.model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/login-response.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/mirroring-summary.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/orchestrator.enum.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/orchestrator.interface.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/osd-deployment-options.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/osd-settings.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/permission.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/permissions.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/pool-form-info.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/prometheus-alerts.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/service.interface.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/smart.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/summary.model.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/task-exception.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/task.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/wizard-steps.ts create mode 100755 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/array.pipe.spec.ts create mode 100755 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/array.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/boolean-text.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/boolean-text.pipe.ts create mode 100755 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/boolean.pipe.spec.ts create mode 100755 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/boolean.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/cd-date.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/cd-date.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/ceph-release-name.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/ceph-release-name.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/ceph-short-version.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/ceph-short-version.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/dimless-binary-per-second.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/dimless-binary.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/dimless-binary.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/dimless.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/dimless.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/duration.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/duration.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/empty.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/empty.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/encode-uri.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/encode-uri.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/filter.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/filter.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/health-color.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/health-color.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/iops.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/iops.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/iscsi-backstore.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/iscsi-backstore.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/join.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/join.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/log-priority.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/log-priority.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/map.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/map.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/milliseconds.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/milliseconds.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/not-available.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/not-available.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/ordinal.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/ordinal.pipe.ts create mode 100755 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/pipes.module.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/rbd-configuration-source.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/rbd-configuration-source.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/relative-date.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/relative-date.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/round.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/round.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/sanitize-html.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/sanitize-html.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/search-highlight.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/search-highlight.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/truncate.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/truncate.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/upper-first.pipe.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/upper-first.pipe.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/rxjs/operators/page-visibilty.operator.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/api-interceptor.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/api-interceptor.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-guard.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-guard.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-storage.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-storage.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/cd-table-server-side.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/cd-table-server-side.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/change-password-guard.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/change-password-guard.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/device.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/device.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/doc.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/doc.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/favicon.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/favicon.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/feature-toggles-guard.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/feature-toggles-guard.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/feature-toggles.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/feature-toggles.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/formatter.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/formatter.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/js-error-handler.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/language.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/language.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/modal.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/modal.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/module-status-guard.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/module-status-guard.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/motd-notification.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/motd-notification.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/ngzone-scheduler.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/no-sso-guard.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/no-sso-guard.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/password-policy.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/password-policy.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/prometheus-alert-formatter.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/prometheus-alert-formatter.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/prometheus-alert.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/prometheus-alert.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/prometheus-notification.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/prometheus-notification.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/prometheus-silence-matcher.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/prometheus-silence-matcher.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/rbd-configuration.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/rbd-configuration.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/refresh-interval.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/refresh-interval.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/summary.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/summary.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-list.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-list.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-manager.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-manager.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-message.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-message.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-wrapper.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-wrapper.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/telemetry-notification.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/telemetry-notification.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/text-to-download.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/text-to-download.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/time-diff.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/time-diff.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/timer.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/timer.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/url-builder.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/url-builder.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/wizard-steps.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/wizard-steps.service.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/shared.module.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/assets/.gitkeep create mode 100644 src/pybind/mgr/dashboard/frontend/src/assets/Ceph_Ceph_Logo_with_text_red_white.svg create mode 100644 src/pybind/mgr/dashboard/frontend/src/assets/Ceph_Ceph_Logo_with_text_white.svg create mode 100644 src/pybind/mgr/dashboard/frontend/src/assets/Ceph_Logo.svg create mode 100644 src/pybind/mgr/dashboard/frontend/src/assets/ceph_background.gif create mode 100755 src/pybind/mgr/dashboard/frontend/src/assets/loading.gif create mode 100644 src/pybind/mgr/dashboard/frontend/src/assets/logo-mini.png create mode 100644 src/pybind/mgr/dashboard/frontend/src/assets/prometheus_logo.svg create mode 100644 src/pybind/mgr/dashboard/frontend/src/environments/environment.tpl.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/favicon.ico create mode 100644 src/pybind/mgr/dashboard/frontend/src/index.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/jestGlobalMocks.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/locale/messages.cs.xlf create mode 100644 src/pybind/mgr/dashboard/frontend/src/locale/messages.de-DE.xlf create mode 100644 src/pybind/mgr/dashboard/frontend/src/locale/messages.es-ES.xlf create mode 100644 src/pybind/mgr/dashboard/frontend/src/locale/messages.fr-FR.xlf create mode 100644 src/pybind/mgr/dashboard/frontend/src/locale/messages.id-ID.xlf create mode 100644 src/pybind/mgr/dashboard/frontend/src/locale/messages.it-IT.xlf create mode 100644 src/pybind/mgr/dashboard/frontend/src/locale/messages.ja-JP.xlf create mode 100644 src/pybind/mgr/dashboard/frontend/src/locale/messages.ko-KR.xlf create mode 100644 src/pybind/mgr/dashboard/frontend/src/locale/messages.pl-PL.xlf create mode 100644 src/pybind/mgr/dashboard/frontend/src/locale/messages.pt-BR.xlf create mode 100644 src/pybind/mgr/dashboard/frontend/src/locale/messages.zh-CN.xlf create mode 100644 src/pybind/mgr/dashboard/frontend/src/locale/messages.zh-TW.xlf create mode 100644 src/pybind/mgr/dashboard/frontend/src/main.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/polyfills.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/setupJest.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/styles.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/styles/_chart-tooltip.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/styles/bootstrap-extends.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/styles/ceph-custom/_basics.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/styles/ceph-custom/_buttons.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/styles/ceph-custom/_dropdown.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/styles/ceph-custom/_forms.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/styles/ceph-custom/_grid.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/styles/ceph-custom/_icons.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/styles/ceph-custom/_index.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/styles/ceph-custom/_navs.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/styles/ceph-custom/_toast.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/styles/defaults/_bootstrap-defaults.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/styles/defaults/_functions.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/styles/defaults/_index.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/styles/defaults/_mixins.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/styles/vendor/_index.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/styles/vendor/_style-overrides.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/styles/vendor/_variables.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/testing/activated-route-stub.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/testing/unit-test-helper.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/typings.d.ts create mode 100644 src/pybind/mgr/dashboard/frontend/tsconfig.app.json create mode 100644 src/pybind/mgr/dashboard/frontend/tsconfig.json create mode 100644 src/pybind/mgr/dashboard/frontend/tsconfig.spec.json create mode 100644 src/pybind/mgr/dashboard/frontend/tslint.json create mode 100644 src/pybind/mgr/dashboard/grafana.py create mode 100644 src/pybind/mgr/dashboard/module.py create mode 100644 src/pybind/mgr/dashboard/openapi.yaml create mode 100644 src/pybind/mgr/dashboard/plugins/__init__.py create mode 100644 src/pybind/mgr/dashboard/plugins/debug.py create mode 100644 src/pybind/mgr/dashboard/plugins/feature_toggles.py create mode 100644 src/pybind/mgr/dashboard/plugins/interfaces.py create mode 100644 src/pybind/mgr/dashboard/plugins/lru_cache.py create mode 100644 src/pybind/mgr/dashboard/plugins/motd.py create mode 100644 src/pybind/mgr/dashboard/plugins/pluggy.py create mode 100644 src/pybind/mgr/dashboard/plugins/plugin.py create mode 100644 src/pybind/mgr/dashboard/plugins/ttl_cache.py create mode 100644 src/pybind/mgr/dashboard/requirements-extra.txt create mode 100644 src/pybind/mgr/dashboard/requirements-lint.txt create mode 100644 src/pybind/mgr/dashboard/requirements-test.txt create mode 100644 src/pybind/mgr/dashboard/requirements.txt create mode 100644 src/pybind/mgr/dashboard/rest_client.py create mode 100755 src/pybind/mgr/dashboard/run-backend-api-request.sh create mode 100755 src/pybind/mgr/dashboard/run-backend-api-tests.sh create mode 100755 src/pybind/mgr/dashboard/run-backend-rook-api-request.sh create mode 100755 src/pybind/mgr/dashboard/run-frontend-e2e-tests.sh create mode 100755 src/pybind/mgr/dashboard/run-frontend-unittests.sh create mode 100644 src/pybind/mgr/dashboard/security.py create mode 100644 src/pybind/mgr/dashboard/services/__init__.py create mode 100644 src/pybind/mgr/dashboard/services/access_control.py create mode 100644 src/pybind/mgr/dashboard/services/auth.py create mode 100644 src/pybind/mgr/dashboard/services/ceph_service.py create mode 100644 src/pybind/mgr/dashboard/services/cephfs.py create mode 100644 src/pybind/mgr/dashboard/services/cluster.py create mode 100644 src/pybind/mgr/dashboard/services/exception.py create mode 100644 src/pybind/mgr/dashboard/services/iscsi_cli.py create mode 100644 src/pybind/mgr/dashboard/services/iscsi_client.py create mode 100644 src/pybind/mgr/dashboard/services/iscsi_config.py create mode 100644 src/pybind/mgr/dashboard/services/orchestrator.py create mode 100644 src/pybind/mgr/dashboard/services/osd.py create mode 100644 src/pybind/mgr/dashboard/services/progress.py create mode 100644 src/pybind/mgr/dashboard/services/rbd.py create mode 100644 src/pybind/mgr/dashboard/services/rgw_client.py create mode 100644 src/pybind/mgr/dashboard/services/sso.py create mode 100644 src/pybind/mgr/dashboard/services/tcmu_service.py create mode 100644 src/pybind/mgr/dashboard/settings.py create mode 100644 src/pybind/mgr/dashboard/tests/__init__.py create mode 100644 src/pybind/mgr/dashboard/tests/helper.py create mode 100644 src/pybind/mgr/dashboard/tests/test_access_control.py create mode 100644 src/pybind/mgr/dashboard/tests/test_api_auditing.py create mode 100644 src/pybind/mgr/dashboard/tests/test_auth.py create mode 100644 src/pybind/mgr/dashboard/tests/test_ceph_service.py create mode 100644 src/pybind/mgr/dashboard/tests/test_cephfs.py create mode 100644 src/pybind/mgr/dashboard/tests/test_controllers.py create mode 100644 src/pybind/mgr/dashboard/tests/test_daemon.py create mode 100644 src/pybind/mgr/dashboard/tests/test_docs.py create mode 100644 src/pybind/mgr/dashboard/tests/test_erasure_code_profile.py create mode 100644 src/pybind/mgr/dashboard/tests/test_exceptions.py create mode 100644 src/pybind/mgr/dashboard/tests/test_feature_toggles.py create mode 100644 src/pybind/mgr/dashboard/tests/test_grafana.py create mode 100644 src/pybind/mgr/dashboard/tests/test_home.py create mode 100644 src/pybind/mgr/dashboard/tests/test_host.py create mode 100644 src/pybind/mgr/dashboard/tests/test_iscsi.py create mode 100644 src/pybind/mgr/dashboard/tests/test_nfs.py create mode 100644 src/pybind/mgr/dashboard/tests/test_notification.py create mode 100644 src/pybind/mgr/dashboard/tests/test_orchestrator.py create mode 100644 src/pybind/mgr/dashboard/tests/test_osd.py create mode 100644 src/pybind/mgr/dashboard/tests/test_plugin_debug.py create mode 100644 src/pybind/mgr/dashboard/tests/test_pool.py create mode 100644 src/pybind/mgr/dashboard/tests/test_prometheus.py create mode 100644 src/pybind/mgr/dashboard/tests/test_rbd_mirroring.py create mode 100644 src/pybind/mgr/dashboard/tests/test_rbd_service.py create mode 100644 src/pybind/mgr/dashboard/tests/test_rest_client.py create mode 100644 src/pybind/mgr/dashboard/tests/test_rest_tasks.py create mode 100644 src/pybind/mgr/dashboard/tests/test_rgw.py create mode 100644 src/pybind/mgr/dashboard/tests/test_rgw_client.py create mode 100644 src/pybind/mgr/dashboard/tests/test_settings.py create mode 100644 src/pybind/mgr/dashboard/tests/test_ssl.py create mode 100644 src/pybind/mgr/dashboard/tests/test_sso.py create mode 100644 src/pybind/mgr/dashboard/tests/test_task.py create mode 100644 src/pybind/mgr/dashboard/tests/test_tools.py create mode 100644 src/pybind/mgr/dashboard/tests/test_versioning.py create mode 100644 src/pybind/mgr/dashboard/tools.py create mode 100644 src/pybind/mgr/dashboard/tox.ini create mode 100644 src/pybind/mgr/devicehealth/__init__.py create mode 100644 src/pybind/mgr/devicehealth/module.py create mode 100644 src/pybind/mgr/diskprediction_local/__init__.py create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/config.json create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_1.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_10.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_104.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_105.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_109.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_112.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_114.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_115.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_118.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_119.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_12.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_120.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_123.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_124.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_125.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_128.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_131.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_134.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_138.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_14.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_141.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_145.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_151.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_16.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_161.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_168.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_169.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_174.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_18.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_182.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_185.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_186.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_195.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_201.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_204.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_206.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_208.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_210.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_212.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_213.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_219.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_221.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_222.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_223.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_225.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_227.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_229.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_230.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_234.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_235.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_236.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_239.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_243.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_27.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_3.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_33.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_36.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_44.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_50.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_57.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_59.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_6.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_61.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_62.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_67.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_69.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_71.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_72.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_78.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_79.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_82.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_85.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_88.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_93.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/prophetstor/svm_97.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/redhat/config.json create mode 100644 src/pybind/mgr/diskprediction_local/models/redhat/hgst_predictor.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/redhat/hgst_scaler.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/redhat/seagate_predictor.pkl create mode 100644 src/pybind/mgr/diskprediction_local/models/redhat/seagate_scaler.pkl create mode 100644 src/pybind/mgr/diskprediction_local/module.py create mode 100644 src/pybind/mgr/diskprediction_local/predictor.py create mode 100644 src/pybind/mgr/diskprediction_local/requirements.txt create mode 100644 src/pybind/mgr/hello/__init__.py create mode 100644 src/pybind/mgr/hello/module.py create mode 100644 src/pybind/mgr/influx/__init__.py create mode 100644 src/pybind/mgr/influx/module.py create mode 100644 src/pybind/mgr/insights/__init__.py create mode 100644 src/pybind/mgr/insights/health.py create mode 100644 src/pybind/mgr/insights/module.py create mode 100644 src/pybind/mgr/insights/tests/__init__.py create mode 100644 src/pybind/mgr/insights/tests/test_health.py create mode 100644 src/pybind/mgr/iostat/__init__.py create mode 100644 src/pybind/mgr/iostat/module.py create mode 100644 src/pybind/mgr/k8sevents/README.md create mode 100644 src/pybind/mgr/k8sevents/__init__.py create mode 100644 src/pybind/mgr/k8sevents/module.py create mode 100644 src/pybind/mgr/k8sevents/rbac_sample.yaml create mode 100644 src/pybind/mgr/localpool/__init__.py create mode 100644 src/pybind/mgr/localpool/module.py create mode 100644 src/pybind/mgr/mds_autoscaler/__init__.py create mode 100644 src/pybind/mgr/mds_autoscaler/module.py create mode 100644 src/pybind/mgr/mds_autoscaler/tests/__init__.py create mode 100644 src/pybind/mgr/mds_autoscaler/tests/test_autoscaler.py create mode 100644 src/pybind/mgr/mgr_module.py create mode 100644 src/pybind/mgr/mgr_util.py create mode 100644 src/pybind/mgr/mirroring/__init__.py create mode 100644 src/pybind/mgr/mirroring/fs/__init__.py create mode 100644 src/pybind/mgr/mirroring/fs/blocklist.py create mode 100644 src/pybind/mgr/mirroring/fs/dir_map/__init__.py create mode 100644 src/pybind/mgr/mirroring/fs/dir_map/create.py create mode 100644 src/pybind/mgr/mirroring/fs/dir_map/load.py create mode 100644 src/pybind/mgr/mirroring/fs/dir_map/policy.py create mode 100644 src/pybind/mgr/mirroring/fs/dir_map/state_transition.py create mode 100644 src/pybind/mgr/mirroring/fs/dir_map/update.py create mode 100644 src/pybind/mgr/mirroring/fs/exception.py create mode 100644 src/pybind/mgr/mirroring/fs/notify.py create mode 100644 src/pybind/mgr/mirroring/fs/snapshot_mirror.py create mode 100644 src/pybind/mgr/mirroring/fs/utils.py create mode 100644 src/pybind/mgr/mirroring/module.py create mode 100644 src/pybind/mgr/nfs/__init__.py create mode 100644 src/pybind/mgr/nfs/cluster.py create mode 100644 src/pybind/mgr/nfs/exception.py create mode 100644 src/pybind/mgr/nfs/export.py create mode 100644 src/pybind/mgr/nfs/export_utils.py create mode 100644 src/pybind/mgr/nfs/module.py create mode 100644 src/pybind/mgr/nfs/tests/__init__.py create mode 100644 src/pybind/mgr/nfs/tests/test_nfs.py create mode 100644 src/pybind/mgr/nfs/utils.py create mode 100644 src/pybind/mgr/orchestrator/README.md create mode 100644 src/pybind/mgr/orchestrator/__init__.py create mode 100644 src/pybind/mgr/orchestrator/_interface.py create mode 100644 src/pybind/mgr/orchestrator/module.py create mode 100644 src/pybind/mgr/orchestrator/tests/__init__.py create mode 100644 src/pybind/mgr/orchestrator/tests/test_orchestrator.py create mode 100644 src/pybind/mgr/osd_perf_query/__init__.py create mode 100644 src/pybind/mgr/osd_perf_query/module.py create mode 100644 src/pybind/mgr/osd_support/__init__.py create mode 100644 src/pybind/mgr/osd_support/module.py create mode 100644 src/pybind/mgr/pg_autoscaler/__init__.py create mode 100644 src/pybind/mgr/pg_autoscaler/module.py create mode 100644 src/pybind/mgr/pg_autoscaler/tests/__init__.py create mode 100644 src/pybind/mgr/pg_autoscaler/tests/test_cal_final_pg_target.py create mode 100644 src/pybind/mgr/pg_autoscaler/tests/test_cal_ratio.py create mode 100644 src/pybind/mgr/pg_autoscaler/tests/test_overlapping_roots.py create mode 100644 src/pybind/mgr/progress/__init__.py create mode 100644 src/pybind/mgr/progress/module.py create mode 100644 src/pybind/mgr/progress/test_progress.py create mode 100644 src/pybind/mgr/prometheus/__init__.py create mode 100644 src/pybind/mgr/prometheus/module.py create mode 100644 src/pybind/mgr/prometheus/test_module.py create mode 100644 src/pybind/mgr/rbd_support/__init__.py create mode 100644 src/pybind/mgr/rbd_support/common.py create mode 100644 src/pybind/mgr/rbd_support/mirror_snapshot_schedule.py create mode 100644 src/pybind/mgr/rbd_support/module.py create mode 100644 src/pybind/mgr/rbd_support/perf.py create mode 100644 src/pybind/mgr/rbd_support/schedule.py create mode 100644 src/pybind/mgr/rbd_support/task.py create mode 100644 src/pybind/mgr/rbd_support/trash_purge_schedule.py create mode 100644 src/pybind/mgr/requirements.txt create mode 100644 src/pybind/mgr/restful/__init__.py create mode 100644 src/pybind/mgr/restful/api/__init__.py create mode 100644 src/pybind/mgr/restful/api/config.py create mode 100644 src/pybind/mgr/restful/api/crush.py create mode 100644 src/pybind/mgr/restful/api/doc.py create mode 100644 src/pybind/mgr/restful/api/mon.py create mode 100644 src/pybind/mgr/restful/api/osd.py create mode 100644 src/pybind/mgr/restful/api/perf.py create mode 100644 src/pybind/mgr/restful/api/pool.py create mode 100644 src/pybind/mgr/restful/api/request.py create mode 100644 src/pybind/mgr/restful/api/server.py create mode 100644 src/pybind/mgr/restful/common.py create mode 100644 src/pybind/mgr/restful/context.py create mode 100644 src/pybind/mgr/restful/decorators.py create mode 100644 src/pybind/mgr/restful/hooks.py create mode 100644 src/pybind/mgr/restful/module.py create mode 100644 src/pybind/mgr/rook/.gitignore create mode 100644 src/pybind/mgr/rook/CMakeLists.txt create mode 100644 src/pybind/mgr/rook/__init__.py create mode 100755 src/pybind/mgr/rook/generate_rook_ceph_client.sh create mode 100644 src/pybind/mgr/rook/module.py create mode 100644 src/pybind/mgr/rook/requirements.txt create mode 100644 src/pybind/mgr/rook/rook-client-python/.github/workflows/generate.yml create mode 100644 src/pybind/mgr/rook/rook-client-python/.gitignore create mode 100644 src/pybind/mgr/rook/rook-client-python/README.md create mode 100644 src/pybind/mgr/rook/rook-client-python/conftest.py create mode 100755 src/pybind/mgr/rook/rook-client-python/generate.sh create mode 100644 src/pybind/mgr/rook/rook-client-python/generate_model_classes.py create mode 100644 src/pybind/mgr/rook/rook-client-python/mypy.ini create mode 100644 src/pybind/mgr/rook/rook-client-python/requirements.txt create mode 100644 src/pybind/mgr/rook/rook-client-python/rook-python-client-demo.gif create mode 100644 src/pybind/mgr/rook/rook-client-python/rook_client/__init__.py create mode 100644 src/pybind/mgr/rook/rook-client-python/rook_client/_helper.py create mode 100644 src/pybind/mgr/rook/rook-client-python/rook_client/cassandra/__init__.py create mode 100644 src/pybind/mgr/rook/rook-client-python/rook_client/cassandra/cluster.py create mode 100644 src/pybind/mgr/rook/rook-client-python/rook_client/ceph/__init__.py create mode 100644 src/pybind/mgr/rook/rook-client-python/rook_client/ceph/cephclient.py create mode 100644 src/pybind/mgr/rook/rook-client-python/rook_client/ceph/cephcluster.py create mode 100644 src/pybind/mgr/rook/rook-client-python/rook_client/ceph/cephfilesystem.py create mode 100644 src/pybind/mgr/rook/rook-client-python/rook_client/ceph/cephnfs.py create mode 100644 src/pybind/mgr/rook/rook-client-python/rook_client/ceph/cephobjectstore.py create mode 100644 src/pybind/mgr/rook/rook-client-python/rook_client/edgefs/__init__.py create mode 100644 src/pybind/mgr/rook/rook-client-python/rook_client/edgefs/cluster.py create mode 100644 src/pybind/mgr/rook/rook-client-python/rook_client/edgefs/isgw.py create mode 100644 src/pybind/mgr/rook/rook-client-python/rook_client/edgefs/nfs.py create mode 100644 src/pybind/mgr/rook/rook-client-python/rook_client/edgefs/s3.py create mode 100644 src/pybind/mgr/rook/rook-client-python/rook_client/edgefs/s3x.py create mode 100644 src/pybind/mgr/rook/rook-client-python/rook_client/edgefs/swift.py create mode 100644 src/pybind/mgr/rook/rook-client-python/rook_client/tests/__init__.py create mode 100644 src/pybind/mgr/rook/rook-client-python/rook_client/tests/test_README.py create mode 100644 src/pybind/mgr/rook/rook-client-python/rook_client/tests/test_examples.py create mode 100644 src/pybind/mgr/rook/rook-client-python/rook_client/tests/test_properties.py create mode 100644 src/pybind/mgr/rook/rook-client-python/setup.py create mode 100644 src/pybind/mgr/rook/rook-client-python/tox.ini create mode 100644 src/pybind/mgr/rook/rook_client/__init__.py create mode 100644 src/pybind/mgr/rook/rook_client/_helper.py create mode 100644 src/pybind/mgr/rook/rook_cluster.py create mode 100644 src/pybind/mgr/selftest/__init__.py create mode 100644 src/pybind/mgr/selftest/module.py create mode 100644 src/pybind/mgr/snap_schedule/.gitignore create mode 100644 src/pybind/mgr/snap_schedule/__init__.py create mode 100644 src/pybind/mgr/snap_schedule/fs/__init__.py create mode 100644 src/pybind/mgr/snap_schedule/fs/schedule.py create mode 100644 src/pybind/mgr/snap_schedule/fs/schedule_client.py create mode 100644 src/pybind/mgr/snap_schedule/module.py create mode 100644 src/pybind/mgr/snap_schedule/requirements.txt create mode 100644 src/pybind/mgr/snap_schedule/tests/__init__.py create mode 100644 src/pybind/mgr/snap_schedule/tests/conftest.py create mode 100644 src/pybind/mgr/snap_schedule/tests/fs/__init__.py create mode 100644 src/pybind/mgr/snap_schedule/tests/fs/test_schedule.py create mode 100644 src/pybind/mgr/snap_schedule/tests/fs/test_schedule_client.py create mode 100644 src/pybind/mgr/snap_schedule/tox.ini create mode 100644 src/pybind/mgr/stats/__init__.py create mode 100644 src/pybind/mgr/stats/fs/__init__.py create mode 100644 src/pybind/mgr/stats/fs/perf_stats.py create mode 100644 src/pybind/mgr/stats/module.py create mode 100644 src/pybind/mgr/status/__init__.py create mode 100644 src/pybind/mgr/status/module.py create mode 100644 src/pybind/mgr/telegraf/__init__.py create mode 100644 src/pybind/mgr/telegraf/basesocket.py create mode 100644 src/pybind/mgr/telegraf/module.py create mode 100644 src/pybind/mgr/telegraf/protocol.py create mode 100644 src/pybind/mgr/telegraf/utils.py create mode 100644 src/pybind/mgr/telemetry/__init__.py create mode 100644 src/pybind/mgr/telemetry/module.py create mode 100644 src/pybind/mgr/test_orchestrator/README.md create mode 100644 src/pybind/mgr/test_orchestrator/__init__.py create mode 100644 src/pybind/mgr/test_orchestrator/dummy_data.json create mode 100644 src/pybind/mgr/test_orchestrator/module.py create mode 100644 src/pybind/mgr/tests/__init__.py create mode 100644 src/pybind/mgr/tests/test_tls.py create mode 100644 src/pybind/mgr/tox.ini create mode 100644 src/pybind/mgr/volumes/__init__.py create mode 100644 src/pybind/mgr/volumes/fs/__init__.py create mode 100644 src/pybind/mgr/volumes/fs/async_cloner.py create mode 100644 src/pybind/mgr/volumes/fs/async_job.py create mode 100644 src/pybind/mgr/volumes/fs/exception.py create mode 100644 src/pybind/mgr/volumes/fs/fs_util.py create mode 100644 src/pybind/mgr/volumes/fs/operations/__init__.py create mode 100644 src/pybind/mgr/volumes/fs/operations/access.py create mode 100644 src/pybind/mgr/volumes/fs/operations/clone_index.py create mode 100644 src/pybind/mgr/volumes/fs/operations/group.py create mode 100644 src/pybind/mgr/volumes/fs/operations/index.py create mode 100644 src/pybind/mgr/volumes/fs/operations/lock.py create mode 100644 src/pybind/mgr/volumes/fs/operations/pin_util.py create mode 100644 src/pybind/mgr/volumes/fs/operations/rankevicter.py create mode 100644 src/pybind/mgr/volumes/fs/operations/resolver.py create mode 100644 src/pybind/mgr/volumes/fs/operations/snapshot_util.py create mode 100644 src/pybind/mgr/volumes/fs/operations/subvolume.py create mode 100644 src/pybind/mgr/volumes/fs/operations/template.py create mode 100644 src/pybind/mgr/volumes/fs/operations/trash.py create mode 100644 src/pybind/mgr/volumes/fs/operations/versions/__init__.py create mode 100644 src/pybind/mgr/volumes/fs/operations/versions/auth_metadata.py create mode 100644 src/pybind/mgr/volumes/fs/operations/versions/metadata_manager.py create mode 100644 src/pybind/mgr/volumes/fs/operations/versions/op_sm.py create mode 100644 src/pybind/mgr/volumes/fs/operations/versions/subvolume_attrs.py create mode 100644 src/pybind/mgr/volumes/fs/operations/versions/subvolume_base.py create mode 100644 src/pybind/mgr/volumes/fs/operations/versions/subvolume_v1.py create mode 100644 src/pybind/mgr/volumes/fs/operations/versions/subvolume_v2.py create mode 100644 src/pybind/mgr/volumes/fs/operations/volume.py create mode 100644 src/pybind/mgr/volumes/fs/purge_queue.py create mode 100644 src/pybind/mgr/volumes/fs/vol_spec.py create mode 100644 src/pybind/mgr/volumes/fs/volume.py create mode 100644 src/pybind/mgr/volumes/module.py create mode 100644 src/pybind/mgr/zabbix/__init__.py create mode 100644 src/pybind/mgr/zabbix/module.py create mode 100644 src/pybind/mgr/zabbix/zabbix_template.xml (limited to 'src/pybind/mgr') diff --git a/src/pybind/mgr/.gitignore b/src/pybind/mgr/.gitignore new file mode 100644 index 000000000..642616e09 --- /dev/null +++ b/src/pybind/mgr/.gitignore @@ -0,0 +1,17 @@ +proxy.conf.json + +# tox related +.coverage* +htmlcov +.tox +coverage.xml +junit*xml +.cache + +# IDE +.vscode +*.egg +.env + +# virtualenv +venv diff --git a/src/pybind/mgr/.pylintrc b/src/pybind/mgr/.pylintrc new file mode 100644 index 000000000..8cab074d9 --- /dev/null +++ b/src/pybind/mgr/.pylintrc @@ -0,0 +1,593 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-whitelist= + +# Specify a score threshold to be exceeded before program exits with error. +fail-under=10.0 + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=print-statement, + parameter-unpacking, + unpacking-in-except, + old-raise-syntax, + backtick, + long-suffix, + old-ne-operator, + old-octal-literal, + import-star-module-level, + non-ascii-bytes-literal, + raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + apply-builtin, + basestring-builtin, + buffer-builtin, + cmp-builtin, + coerce-builtin, + execfile-builtin, + file-builtin, + long-builtin, + raw_input-builtin, + reduce-builtin, + standarderror-builtin, + unicode-builtin, + xrange-builtin, + coerce-method, + delslice-method, + getslice-method, + setslice-method, + no-absolute-import, + old-division, + dict-iter-method, + dict-view-method, + next-method-called, + metaclass-assignment, + indexing-exception, + raising-string, + reload-builtin, + oct-method, + hex-method, + nonzero-method, + cmp-method, + input-builtin, + round-builtin, + intern-builtin, + unichr-builtin, + map-builtin-not-iterating, + zip-builtin-not-iterating, + range-builtin-not-iterating, + filter-builtin-not-iterating, + using-cmp-argument, + eq-without-hash, + div-method, + idiv-method, + rdiv-method, + exception-message-attribute, + invalid-str-codec, + sys-max-int, + bad-python3-import, + deprecated-string-function, + deprecated-str-translate-call, + deprecated-itertools-function, + deprecated-types-field, + next-method-defined, + dict-items-not-iterating, + dict-keys-not-iterating, + dict-values-not-iterating, + deprecated-operator-function, + deprecated-urllib-function, + xreadlines-attribute, + deprecated-sys-function, + exception-escape, + comprehension-escape + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'error', 'warning', 'refactor', and 'convention' +# which contain the number of messages in each category, as well as 'statement' +# which is the total number of statements analyzed. This score is used by the +# global evaluation report (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +#notes-rgx= + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: en_AG (hunspell), en_AU +# (hunspell), en_BS (hunspell), en_BW (hunspell), en_BZ (hunspell), en_CA +# (hunspell), en_DK (hunspell), en_GB (hunspell), en_GH (hunspell), en_HK +# (hunspell), en_IE (hunspell), en_IN (hunspell), en_JM (hunspell), en_MW +# (hunspell), en_NA (hunspell), en_NG (hunspell), en_NZ (hunspell), en_PH +# (hunspell), en_SG (hunspell), en_TT (hunspell), en_US (hunspell), en_ZA +# (hunspell), en_ZM (hunspell), en_ZW (hunspell). +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[DESIGN] + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled). +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled). +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/src/pybind/mgr/CMakeLists.txt b/src/pybind/mgr/CMakeLists.txt new file mode 100644 index 000000000..4b915219a --- /dev/null +++ b/src/pybind/mgr/CMakeLists.txt @@ -0,0 +1,34 @@ +if(WITH_MGR_DASHBOARD_FRONTEND) + add_subdirectory(dashboard) +endif() +if(WITH_MGR_ROOK_CLIENT) + add_subdirectory(rook) +endif() +if(WITH_TESTS) + include(AddCephTest) + add_tox_test(mgr ${CMAKE_CURRENT_SOURCE_DIR} TOX_ENVS py3 mypy flake8 jinjalint) +endif() + +# Location needs to match default setting for mgr_module_path, currently: +# OPTION(mgr_module_path, OPT_STR, CEPH_PKGLIBDIR "/mgr") +install(DIRECTORY + ${CMAKE_CURRENT_SOURCE_DIR} + DESTINATION ${CEPH_INSTALL_DATADIR} + REGEX "CMakeLists.txt" EXCLUDE + REGEX "\\.gitignore" EXCLUDE + REGEX ".*\\.pyi" EXCLUDE + REGEX "hello/.*" EXCLUDE + REGEX "cli_api/.*" EXCLUDE + REGEX "tests/.*" EXCLUDE + REGEX "rook/rook-client-python.*" EXCLUDE + REGEX "osd_perf_query/.*" EXCLUDE + REGEX "tox.ini" EXCLUDE + REGEX "requirements.*\.txt" EXCLUDE + REGEX "constraints.*\.txt" EXCLUDE + REGEX "node_modules" EXCLUDE + REGEX "cypress.*" EXCLUDE + REGEX "\.coveragerc" EXCLUDE + REGEX "\.editorconfig" EXCLUDE + REGEX "\..*lintrc" EXCLUDE + REGEX "\.browserslistrc" EXCLUDE + REGEX "\.prettier*" EXCLUDE) diff --git a/src/pybind/mgr/alerts/__init__.py b/src/pybind/mgr/alerts/__init__.py new file mode 100644 index 000000000..e4c185ca9 --- /dev/null +++ b/src/pybind/mgr/alerts/__init__.py @@ -0,0 +1 @@ +from .module import Alerts diff --git a/src/pybind/mgr/alerts/module.py b/src/pybind/mgr/alerts/module.py new file mode 100644 index 000000000..3d299f0d4 --- /dev/null +++ b/src/pybind/mgr/alerts/module.py @@ -0,0 +1,264 @@ + +""" +A simple cluster health alerting module. +""" + +from mgr_module import MgrModule, HandleCommandResult +from email.utils import formatdate, make_msgid +from threading import Event +import errno +import json +import smtplib + +class Alerts(MgrModule): + COMMANDS = [ + { + "cmd": "alerts send", + "desc": "(re)send alerts immediately", + "perm": "r" + }, + ] + + MODULE_OPTIONS = [ + { + 'name': 'interval', + 'type': 'secs', + 'default': 60, + 'desc': 'How frequently to reexamine health status', + 'runtime': True, + }, + # smtp + { + 'name': 'smtp_host', + 'default': '', + 'desc': 'SMTP server', + 'runtime': True, + }, + { + 'name': 'smtp_destination', + 'default': '', + 'desc': 'Email address to send alerts to', + 'runtime': True, + }, + { + 'name': 'smtp_port', + 'type': 'int', + 'default': 465, + 'desc': 'SMTP port', + 'runtime': True, + }, + { + 'name': 'smtp_ssl', + 'type': 'bool', + 'default': True, + 'desc': 'Use SSL to connect to SMTP server', + 'runtime': True, + }, + { + 'name': 'smtp_user', + 'default': '', + 'desc': 'User to authenticate as', + 'runtime': True, + }, + { + 'name': 'smtp_password', + 'default': '', + 'desc': 'Password to authenticate with', + 'runtime': True, + }, + { + 'name': 'smtp_sender', + 'default': '', + 'desc': 'SMTP envelope sender', + 'runtime': True, + }, + { + 'name': 'smtp_from_name', + 'default': 'Ceph', + 'desc': 'Email From: name', + 'runtime': True, + }, + ] + + # These are "native" Ceph options that this module cares about. + NATIVE_OPTIONS = [ + ] + + def __init__(self, *args, **kwargs): + super(Alerts, self).__init__(*args, **kwargs) + + # set up some members to enable the serve() method and shutdown() + self.run = True + self.event = Event() + + # ensure config options members are initialized; see config_notify() + self.config_notify() + + self.log.info("Init") + + + def config_notify(self): + """ + This method is called whenever one of our config options is changed. + """ + # This is some boilerplate that stores MODULE_OPTIONS in a class + # member, so that, for instance, the 'emphatic' option is always + # available as 'self.emphatic'. + for opt in self.MODULE_OPTIONS: + setattr(self, + opt['name'], + self.get_module_option(opt['name'])) + self.log.debug(' mgr option %s = %s', + opt['name'], getattr(self, opt['name'])) + # Do the same for the native options. + for opt in self.NATIVE_OPTIONS: + setattr(self, + opt, + self.get_ceph_option(opt)) + self.log.debug(' native option %s = %s', opt, getattr(self, opt)) + + def handle_command(self, inbuf, cmd): + ret = 0 + out = '' + err = '' + if cmd['prefix'] == 'alerts send': + status = json.loads(self.get('health')['json']) + self._send_alert(status, {}) + return HandleCommandResult( + retval=ret, # exit code + stdout=out, # stdout + stderr=err) + + def _diff(self, last, new): + d = {} + for code, alert in new.get('checks', {}).items(): + self.log.debug('new code %s alert %s' % (code, alert)) + if code not in last.get('checks', {}): + if 'new' not in d: + d['new'] = {} + d['new'][code] = alert + elif alert['summary'].get('count', 0) > \ + last['checks'][code]['summary'].get('count', 0): + if 'updated' not in d: + d['updated'] = {} + d['updated'][code] = alert + for code, alert in last.get('checks', {}).items(): + self.log.debug('old code %s alert %s' % (code, alert)) + if code not in new.get('checks', {}): + if 'cleared' not in d: + d['cleared'] = {} + d['cleared'][code] = alert + return d + + def _send_alert(self, status, diff): + checks = {} + if self.smtp_host: + r = self._send_alert_smtp(status, diff) + if r: + for code, alert in r.items(): + checks[code] = alert + else: + self.log.warning('Alert is not sent because smtp_host is not configured') + self.set_health_checks(checks) + + def serve(self): + """ + This method is called by the mgr when the module starts and can be + used for any background activity. + """ + self.log.info("Starting") + last_status = {} + while self.run: + # Do some useful background work here. + new_status = json.loads(self.get('health')['json']) + if new_status != last_status: + self.log.debug('last_status %s' % last_status) + self.log.debug('new_status %s' % new_status) + diff = self._diff(last_status, + new_status) + self.log.debug('diff %s' % diff) + if diff: + self._send_alert(new_status, diff) + last_status = new_status + + self.log.debug('Sleeping for %d seconds', self.interval) + ret = self.event.wait(self.interval) + self.event.clear() + + def shutdown(self): + """ + This method is called by the mgr when the module needs to shut + down (i.e., when the serve() function needs to exit). + """ + self.log.info('Stopping') + self.run = False + self.event.set() + + # SMTP + def _smtp_format_alert(self, code, alert): + r = '[{sev}] {code}: {summary}\n'.format( + code=code, + sev=alert['severity'].split('_')[1], + summary=alert['summary']['message']) + for detail in alert['detail']: + r += ' {message}\n'.format( + message=detail['message']) + return r + + def _send_alert_smtp(self, status, diff): + # message + self.log.debug('_send_alert_smtp') + message = ('From: {from_name} <{sender}>\n' + 'Subject: {status}\n' + 'To: {target}\n' + 'Message-Id: {message_id}\n' + 'Date: {date}\n' + '\n' + '{status}\n'.format( + sender=self.smtp_sender, + from_name=self.smtp_from_name, + status=status['status'], + target=self.smtp_destination, + message_id=make_msgid(), + date=formatdate())) + + if 'new' in diff: + message += ('\n--- New ---\n') + for code, alert in diff['new'].items(): + message += self._smtp_format_alert(code, alert) + if 'updated' in diff: + message += ('\n--- Updated ---\n') + for code, alert in diff['updated'].items(): + message += self._smtp_format_alert(code, alert) + if 'cleared' in diff: + message += ('\n--- Cleared ---\n') + for code, alert in diff['cleared'].items(): + message += self._smtp_format_alert(code, alert) + + message += ('\n\n=== Full health status ===\n') + for code, alert in status['checks'].items(): + message += self._smtp_format_alert(code, alert) + + self.log.debug('message: %s' % message) + + # send + try: + if self.smtp_ssl: + server = smtplib.SMTP_SSL(self.smtp_host, self.smtp_port) + else: + server = smtplib.SMTP(self.smtp_host, self.smtp_port) + if self.smtp_password: + server.login(self.smtp_user, self.smtp_password) + server.sendmail(self.smtp_sender, self.smtp_destination, message) + server.quit() + except Exception as e: + return { + 'ALERTS_SMTP_ERROR': { + 'severity': 'warning', + 'summary': 'unable to send alert email', + 'count': 1, + 'detail': [ str(e) ] + } + } + self.log.debug('Sent email to %s' % self.smtp_destination) + return None diff --git a/src/pybind/mgr/balancer/__init__.py b/src/pybind/mgr/balancer/__init__.py new file mode 100644 index 000000000..8f210ac92 --- /dev/null +++ b/src/pybind/mgr/balancer/__init__.py @@ -0,0 +1 @@ +from .module import Module diff --git a/src/pybind/mgr/balancer/module.py b/src/pybind/mgr/balancer/module.py new file mode 100644 index 000000000..9f0ebb681 --- /dev/null +++ b/src/pybind/mgr/balancer/module.py @@ -0,0 +1,1404 @@ +""" +Balance PG distribution across OSDs. +""" + +import copy +import errno +import json +import math +import random +import time +from mgr_module import MgrModule, CommandResult +from threading import Event +from mgr_module import CRUSHMap +import datetime + +TIME_FORMAT = '%Y-%m-%d_%H:%M:%S' + +class MappingState: + def __init__(self, osdmap, raw_pg_stats, raw_pool_stats, desc=''): + self.desc = desc + self.osdmap = osdmap + self.osdmap_dump = self.osdmap.dump() + self.crush = osdmap.get_crush() + self.crush_dump = self.crush.dump() + self.raw_pg_stats = raw_pg_stats + self.raw_pool_stats = raw_pool_stats + self.pg_stat = { + i['pgid']: i['stat_sum'] for i in raw_pg_stats.get('pg_stats', []) + } + osd_poolids = [p['pool'] for p in self.osdmap_dump.get('pools', [])] + pg_poolids = [p['poolid'] for p in raw_pool_stats.get('pool_stats', [])] + self.poolids = set(osd_poolids) & set(pg_poolids) + self.pg_up = {} + self.pg_up_by_poolid = {} + for poolid in self.poolids: + self.pg_up_by_poolid[poolid] = osdmap.map_pool_pgs_up(poolid) + for a,b in self.pg_up_by_poolid[poolid].items(): + self.pg_up[a] = b + + def calc_misplaced_from(self, other_ms): + num = len(other_ms.pg_up) + misplaced = 0 + for pgid, before in other_ms.pg_up.items(): + if before != self.pg_up.get(pgid, []): + misplaced += 1 + if num > 0: + return float(misplaced) / float(num) + return 0.0 + +class Plan(object): + def __init__(self, name, mode, osdmap, pools): + self.name = name + self.mode = mode + self.osdmap = osdmap + self.osdmap_dump = osdmap.dump() + self.pools = pools + self.osd_weights = {} + self.compat_ws = {} + self.inc = osdmap.new_incremental() + self.pg_status = {} + + def dump(self) -> str: + return json.dumps(self.inc.dump(), indent=4, sort_keys=True) + + def show(self) -> str: + return 'upmap plan' + + +class MsPlan(Plan): + """ + Plan with a preloaded MappingState member. + """ + def __init__(self, name, mode, ms, pools): + super(MsPlan, self).__init__(name, mode, ms.osdmap, pools) + self.initial = ms + + def final_state(self): + self.inc.set_osd_reweights(self.osd_weights) + self.inc.set_crush_compat_weight_set_weights(self.compat_ws) + return MappingState(self.initial.osdmap.apply_incremental(self.inc), + self.initial.raw_pg_stats, + self.initial.raw_pool_stats, + 'plan %s final' % self.name) + + def show(self) -> str: + ls = [] + ls.append('# starting osdmap epoch %d' % self.initial.osdmap.get_epoch()) + ls.append('# starting crush version %d' % + self.initial.osdmap.get_crush_version()) + ls.append('# mode %s' % self.mode) + if len(self.compat_ws) and \ + not CRUSHMap.have_default_choose_args(self.initial.crush_dump): + ls.append('ceph osd crush weight-set create-compat') + for osd, weight in self.compat_ws.items(): + ls.append('ceph osd crush weight-set reweight-compat %s %f' % + (osd, weight)) + for osd, weight in self.osd_weights.items(): + ls.append('ceph osd reweight osd.%d %f' % (osd, weight)) + incdump = self.inc.dump() + for pgid in incdump.get('old_pg_upmap_items', []): + ls.append('ceph osd rm-pg-upmap-items %s' % pgid) + for item in incdump.get('new_pg_upmap_items', []): + osdlist = [] + for m in item['mappings']: + osdlist += [m['from'], m['to']] + ls.append('ceph osd pg-upmap-items %s %s' % + (item['pgid'], ' '.join([str(a) for a in osdlist]))) + return '\n'.join(ls) + + +class Eval: + def __init__(self, ms): + self.ms = ms + self.root_ids = {} # root name -> id + self.pool_name = {} # pool id -> pool name + self.pool_id = {} # pool name -> id + self.pool_roots = {} # pool name -> root name + self.root_pools = {} # root name -> pools + self.target_by_root = {} # root name -> target weight map + self.count_by_pool = {} + self.count_by_root = {} + self.actual_by_pool = {} # pool -> by_* -> actual weight map + self.actual_by_root = {} # pool -> by_* -> actual weight map + self.total_by_pool = {} # pool -> by_* -> total + self.total_by_root = {} # root -> by_* -> total + self.stats_by_pool = {} # pool -> by_* -> stddev or avg -> value + self.stats_by_root = {} # root -> by_* -> stddev or avg -> value + + self.score_by_pool = {} + self.score_by_root = {} + + self.score = 0.0 + + def show(self, verbose=False): + if verbose: + r = self.ms.desc + '\n' + r += 'target_by_root %s\n' % self.target_by_root + r += 'actual_by_pool %s\n' % self.actual_by_pool + r += 'actual_by_root %s\n' % self.actual_by_root + r += 'count_by_pool %s\n' % self.count_by_pool + r += 'count_by_root %s\n' % self.count_by_root + r += 'total_by_pool %s\n' % self.total_by_pool + r += 'total_by_root %s\n' % self.total_by_root + r += 'stats_by_root %s\n' % self.stats_by_root + r += 'score_by_pool %s\n' % self.score_by_pool + r += 'score_by_root %s\n' % self.score_by_root + else: + r = self.ms.desc + ' ' + r += 'score %f (lower is better)\n' % self.score + return r + + def calc_stats(self, count, target, total): + num = max(len(target), 1) + r = {} + for t in ('pgs', 'objects', 'bytes'): + if total[t] == 0: + r[t] = { + 'max': 0, + 'min': 0, + 'avg': 0, + 'stddev': 0, + 'sum_weight': 0, + 'score': 0, + } + continue + + avg = float(total[t]) / float(num) + dev = 0.0 + + # score is a measure of how uneven the data distribution is. + # score lies between [0, 1), 0 means perfect distribution. + score = 0.0 + sum_weight = 0.0 + + for k, v in count[t].items(): + # adjust/normalize by weight + if target[k]: + adjusted = float(v) / target[k] / float(num) + else: + adjusted = 0.0 + + # Overweighted devices and their weights are factors to calculate reweight_urgency. + # One 10% underfilled device with 5 2% overfilled devices, is arguably a better + # situation than one 10% overfilled with 5 2% underfilled devices + if adjusted > avg: + ''' + F(x) = 2*phi(x) - 1, where phi(x) = cdf of standard normal distribution + x = (adjusted - avg)/avg. + Since, we're considering only over-weighted devices, x >= 0, and so phi(x) lies in [0.5, 1). + To bring range of F(x) in range [0, 1), we need to make the above modification. + + In general, we need to use a function F(x), where x = (adjusted - avg)/avg + 1. which is bounded between 0 and 1, so that ultimately reweight_urgency will also be bounded. + 2. A larger value of x, should imply more urgency to reweight. + 3. Also, the difference between F(x) when x is large, should be minimal. + 4. The value of F(x) should get close to 1 (highest urgency to reweight) with steeply. + + Could have used F(x) = (1 - e^(-x)). But that had slower convergence to 1, compared to the one currently in use. + + cdf of standard normal distribution: https://stackoverflow.com/a/29273201 + ''' + score += target[k] * (math.erf(((adjusted - avg)/avg) / math.sqrt(2.0))) + sum_weight += target[k] + dev += (avg - adjusted) * (avg - adjusted) + stddev = math.sqrt(dev / float(max(num - 1, 1))) + score = score / max(sum_weight, 1) + r[t] = { + 'max': max(count[t].values()), + 'min': min(count[t].values()), + 'avg': avg, + 'stddev': stddev, + 'sum_weight': sum_weight, + 'score': score, + } + return r + +class Module(MgrModule): + MODULE_OPTIONS = [ + { + 'name': 'active', + 'type': 'bool', + 'default': True, + 'desc': 'automatically balance PGs across cluster', + 'runtime': True, + }, + { + 'name': 'begin_time', + 'type': 'str', + 'default': '0000', + 'desc': 'beginning time of day to automatically balance', + 'long_desc': 'This is a time of day in the format HHMM.', + 'runtime': True, + }, + { + 'name': 'end_time', + 'type': 'str', + 'default': '2400', + 'desc': 'ending time of day to automatically balance', + 'long_desc': 'This is a time of day in the format HHMM.', + 'runtime': True, + }, + { + 'name': 'begin_weekday', + 'type': 'uint', + 'default': 0, + 'min': 0, + 'max': 7, + 'desc': 'Restrict automatic balancing to this day of the week or later', + 'long_desc': '0 or 7 = Sunday, 1 = Monday, etc.', + 'runtime': True, + }, + { + 'name': 'end_weekday', + 'type': 'uint', + 'default': 7, + 'min': 0, + 'max': 7, + 'desc': 'Restrict automatic balancing to days of the week earlier than this', + 'long_desc': '0 or 7 = Sunday, 1 = Monday, etc.', + 'runtime': True, + }, + { + 'name': 'crush_compat_max_iterations', + 'type': 'uint', + 'default': 25, + 'min': 1, + 'max': 250, + 'desc': 'maximum number of iterations to attempt optimization', + 'runtime': True, + }, + { + 'name': 'crush_compat_metrics', + 'type': 'str', + 'default': 'pgs,objects,bytes', + 'desc': 'metrics with which to calculate OSD utilization', + 'long_desc': 'Value is a list of one or more of "pgs", "objects", or "bytes", and indicates which metrics to use to balance utilization.', + 'runtime': True, + }, + { + 'name': 'crush_compat_step', + 'type': 'float', + 'default': .5, + 'min': .001, + 'max': .999, + 'desc': 'aggressiveness of optimization', + 'long_desc': '.99 is very aggressive, .01 is less aggressive', + 'runtime': True, + }, + { + 'name': 'min_score', + 'type': 'float', + 'default': 0, + 'desc': 'minimum score, below which no optimization is attempted', + 'runtime': True, + }, + { + 'name': 'mode', + 'desc': 'Balancer mode', + 'default': 'upmap', + 'enum_allowed': ['none', 'crush-compat', 'upmap'], + 'runtime': True, + }, + { + 'name': 'sleep_interval', + 'type': 'secs', + 'default': 60, + 'desc': 'how frequently to wake up and attempt optimization', + 'runtime': True, + }, + { + 'name': 'upmap_max_optimizations', + 'type': 'uint', + 'default': 10, + 'desc': 'maximum upmap optimizations to make per attempt', + 'runtime': True, + }, + { + 'name': 'upmap_max_deviation', + 'type': 'int', + 'default': 5, + 'min': 1, + 'desc': 'deviation below which no optimization is attempted', + 'long_desc': 'If the number of PGs are within this count then no optimization is attempted', + 'runtime': True, + }, + { + 'name': 'pool_ids', + 'type': 'str', + 'default': '', + 'desc': 'pools which the automatic balancing will be limited to', + 'runtime': True, + }, + ] + + COMMANDS = [ + { + "cmd": "balancer status", + "desc": "Show balancer status", + "perm": "r", + }, + { + "cmd": "balancer mode name=mode,type=CephChoices,strings=none|crush-compat|upmap", + "desc": "Set balancer mode", + "perm": "rw", + }, + { + "cmd": "balancer on", + "desc": "Enable automatic balancing", + "perm": "rw", + }, + { + "cmd": "balancer off", + "desc": "Disable automatic balancing", + "perm": "rw", + }, + { + "cmd": "balancer pool ls", + "desc": "List automatic balancing pools. " + "Note that empty list means all existing pools will be automatic balancing targets, " + "which is the default behaviour of balancer.", + "perm": "r", + }, + { + "cmd": "balancer pool add name=pools,type=CephString,n=N", + "desc": "Enable automatic balancing for specific pools", + "perm": "rw", + }, + { + "cmd": "balancer pool rm name=pools,type=CephString,n=N", + "desc": "Disable automatic balancing for specific pools", + "perm": "rw", + }, + { + "cmd": "balancer eval name=option,type=CephString,req=false", + "desc": "Evaluate data distribution for the current cluster or specific pool or specific plan", + "perm": "r", + }, + { + "cmd": "balancer eval-verbose name=option,type=CephString,req=false", + "desc": "Evaluate data distribution for the current cluster or specific pool or specific plan (verbosely)", + "perm": "r", + }, + { + "cmd": "balancer optimize name=plan,type=CephString name=pools,type=CephString,n=N,req=false", + "desc": "Run optimizer to create a new plan", + "perm": "rw", + }, + { + "cmd": "balancer show name=plan,type=CephString", + "desc": "Show details of an optimization plan", + "perm": "r", + }, + { + "cmd": "balancer rm name=plan,type=CephString", + "desc": "Discard an optimization plan", + "perm": "rw", + }, + { + "cmd": "balancer reset", + "desc": "Discard all optimization plans", + "perm": "rw", + }, + { + "cmd": "balancer dump name=plan,type=CephString", + "desc": "Show an optimization plan", + "perm": "r", + }, + { + "cmd": "balancer ls", + "desc": "List all plans", + "perm": "r", + }, + { + "cmd": "balancer execute name=plan,type=CephString", + "desc": "Execute an optimization plan", + "perm": "rw", + }, + ] + active = False + run = True + plans = {} + mode = '' + optimizing = False + last_optimize_started = '' + last_optimize_duration = '' + optimize_result = '' + success_string = 'Optimization plan created successfully' + in_progress_string = 'in progress' + + def __init__(self, *args, **kwargs): + super(Module, self).__init__(*args, **kwargs) + self.event = Event() + + def handle_command(self, inbuf, command): + self.log.warning("Handling command: '%s'" % str(command)) + if command['prefix'] == 'balancer status': + s = { + 'plans': list(self.plans.keys()), + 'active': self.active, + 'last_optimize_started': self.last_optimize_started, + 'last_optimize_duration': self.last_optimize_duration, + 'optimize_result': self.optimize_result, + 'mode': self.get_module_option('mode'), + } + return (0, json.dumps(s, indent=4, sort_keys=True), '') + elif command['prefix'] == 'balancer mode': + if command['mode'] == 'upmap': + min_compat_client = self.get_osdmap().dump().get('require_min_compat_client', '') + if min_compat_client < 'luminous': # works well because version is alphabetized.. + warn = 'min_compat_client "%s" ' \ + '< "luminous", which is required for pg-upmap. ' \ + 'Try "ceph osd set-require-min-compat-client luminous" ' \ + 'before enabling this mode' % min_compat_client + return (-errno.EPERM, '', warn) + elif command['mode'] == 'crush-compat': + ms = MappingState(self.get_osdmap(), + self.get("pg_stats"), + self.get("pool_stats"), + 'initialize compat weight-set') + self.get_compat_weight_set_weights(ms) # ignore error + self.set_module_option('mode', command['mode']) + return (0, '', '') + elif command['prefix'] == 'balancer on': + if not self.active: + self.set_module_option('active', 'true') + self.active = True + self.event.set() + return (0, '', '') + elif command['prefix'] == 'balancer off': + if self.active: + self.set_module_option('active', 'false') + self.active = False + self.event.set() + return (0, '', '') + elif command['prefix'] == 'balancer pool ls': + pool_ids = self.get_module_option('pool_ids') + if pool_ids == '': + return (0, '', '') + pool_ids = pool_ids.split(',') + pool_ids = [int(p) for p in pool_ids] + pool_name_by_id = dict((p['pool'], p['pool_name']) for p in self.get_osdmap().dump().get('pools', [])) + should_prune = False + final_ids = [] + final_names = [] + for p in pool_ids: + if p in pool_name_by_id: + final_ids.append(p) + final_names.append(pool_name_by_id[p]) + else: + should_prune = True + if should_prune: # some pools were gone, prune + self.set_module_option('pool_ids', ','.join(final_ids)) + return (0, json.dumps(sorted(final_names), indent=4, sort_keys=True), '') + elif command['prefix'] == 'balancer pool add': + raw_names = command['pools'] + pool_id_by_name = dict((p['pool_name'], p['pool']) for p in self.get_osdmap().dump().get('pools', [])) + invalid_names = [p for p in raw_names if p not in pool_id_by_name] + if invalid_names: + return (-errno.EINVAL, '', 'pool(s) %s not found' % invalid_names) + to_add = [str(pool_id_by_name[p]) for p in raw_names if p in pool_id_by_name] + existing = self.get_module_option('pool_ids') + final = to_add + if existing != '': + existing = existing.split(',') + final = set(to_add) | set(existing) + self.set_module_option('pool_ids', ','.join(final)) + return (0, '', '') + elif command['prefix'] == 'balancer pool rm': + raw_names = command['pools'] + existing = self.get_module_option('pool_ids') + if existing == '': # for idempotence + return (0, '', '') + existing = existing.split(',') + osdmap = self.get_osdmap() + pool_ids = [str(p['pool']) for p in osdmap.dump().get('pools', [])] + pool_id_by_name = dict((p['pool_name'], p['pool']) for p in osdmap.dump().get('pools', [])) + final = [p for p in existing if p in pool_ids] + to_delete = [str(pool_id_by_name[p]) for p in raw_names if p in pool_id_by_name] + final = set(final) - set(to_delete) + self.set_module_option('pool_ids', ','.join(final)) + return (0, '', '') + elif command['prefix'] == 'balancer eval' or command['prefix'] == 'balancer eval-verbose': + verbose = command['prefix'] == 'balancer eval-verbose' + pools = [] + if 'option' in command: + plan = self.plans.get(command['option']) + if not plan: + # not a plan, does it look like a pool? + osdmap = self.get_osdmap() + valid_pool_names = [p['pool_name'] for p in osdmap.dump().get('pools', [])] + option = command['option'] + if option not in valid_pool_names: + return (-errno.EINVAL, '', 'option "%s" not a plan or a pool' % option) + pools.append(option) + ms = MappingState(osdmap, self.get("pg_stats"), self.get("pool_stats"), 'pool "%s"' % option) + else: + pools = plan.pools + if plan.mode == 'upmap': + # Note that for upmap, to improve the efficiency, + # we use a basic version of Plan without keeping the obvious + # *redundant* MS member. + # Hence ms might not be accurate here since we are basically + # using an old snapshotted osdmap vs a fresh copy of pg_stats. + # It should not be a big deal though.. + ms = MappingState(plan.osdmap, + self.get("pg_stats"), + self.get("pool_stats"), + 'plan "%s"' % plan.name) + else: + ms = plan.final_state() + else: + ms = MappingState(self.get_osdmap(), + self.get("pg_stats"), + self.get("pool_stats"), + 'current cluster') + return (0, self.evaluate(ms, pools, verbose=verbose), '') + elif command['prefix'] == 'balancer optimize': + # The GIL can be release by the active balancer, so disallow when active + if self.active: + return (-errno.EINVAL, '', 'Balancer enabled, disable to optimize manually') + if self.optimizing: + return (-errno.EINVAL, '', 'Balancer finishing up....try again') + pools = [] + if 'pools' in command: + pools = command['pools'] + osdmap = self.get_osdmap() + valid_pool_names = [p['pool_name'] for p in osdmap.dump().get('pools', [])] + invalid_pool_names = [] + for p in pools: + if p not in valid_pool_names: + invalid_pool_names.append(p) + if len(invalid_pool_names): + return (-errno.EINVAL, '', 'pools %s not found' % invalid_pool_names) + plan = self.plan_create(command['plan'], osdmap, pools) + self.last_optimize_started = time.asctime(time.localtime()) + self.optimize_result = self.in_progress_string + start = time.time() + r, detail = self.optimize(plan) + end = time.time() + self.last_optimize_duration = str(datetime.timedelta(seconds=(end - start))) + if r == 0: + # Add plan if an optimization was created + self.optimize_result = self.success_string + self.plans[command['plan']] = plan + else: + self.optimize_result = detail + return (r, '', detail) + elif command['prefix'] == 'balancer rm': + self.plan_rm(command['plan']) + return (0, '', '') + elif command['prefix'] == 'balancer reset': + self.plans = {} + return (0, '', '') + elif command['prefix'] == 'balancer ls': + return (0, json.dumps([p for p in self.plans], indent=4, sort_keys=True), '') + elif command['prefix'] == 'balancer dump': + plan = self.plans.get(command['plan']) + if not plan: + return (-errno.ENOENT, '', 'plan %s not found' % command['plan']) + return (0, plan.dump(), '') + elif command['prefix'] == 'balancer show': + plan = self.plans.get(command['plan']) + if not plan: + return (-errno.ENOENT, '', 'plan %s not found' % command['plan']) + return (0, plan.show(), '') + elif command['prefix'] == 'balancer execute': + # The GIL can be release by the active balancer, so disallow when active + if self.active: + return (-errno.EINVAL, '', 'Balancer enabled, disable to execute a plan') + if self.optimizing: + return (-errno.EINVAL, '', 'Balancer finishing up....try again') + plan = self.plans.get(command['plan']) + if not plan: + return (-errno.ENOENT, '', 'plan %s not found' % command['plan']) + r, detail = self.execute(plan) + self.plan_rm(command['plan']) + return (r, '', detail) + else: + return (-errno.EINVAL, '', + "Command not found '{0}'".format(command['prefix'])) + + def shutdown(self): + self.log.info('Stopping') + self.run = False + self.event.set() + + def time_permit(self): + local_time = time.localtime() + time_of_day = time.strftime('%H%M', local_time) + weekday = (local_time.tm_wday + 1) % 7 # be compatible with C + permit = False + + begin_time = self.get_module_option('begin_time') + end_time = self.get_module_option('end_time') + if begin_time <= end_time: + permit = begin_time <= time_of_day < end_time + else: + permit = time_of_day >= begin_time or time_of_day < end_time + if not permit: + self.log.debug("should run between %s - %s, now %s, skipping", + begin_time, end_time, time_of_day) + return False + + begin_weekday = self.get_module_option('begin_weekday') + end_weekday = self.get_module_option('end_weekday') + if begin_weekday <= end_weekday: + permit = begin_weekday <= weekday < end_weekday + else: + permit = weekday >= begin_weekday or weekday < end_weekday + if not permit: + self.log.debug("should run between weekday %d - %d, now %d, skipping", + begin_weekday, end_weekday, weekday) + return False + + return True + + def serve(self): + self.log.info('Starting') + while self.run: + self.active = self.get_module_option('active') + sleep_interval = self.get_module_option('sleep_interval') + self.log.debug('Waking up [%s, now %s]', + "active" if self.active else "inactive", + time.strftime(TIME_FORMAT, time.localtime())) + if self.active and self.time_permit(): + self.log.debug('Running') + name = 'auto_%s' % time.strftime(TIME_FORMAT, time.gmtime()) + osdmap = self.get_osdmap() + allow = self.get_module_option('pool_ids') + final = [] + if allow != '': + allow = allow.split(',') + valid = [str(p['pool']) for p in osdmap.dump().get('pools', [])] + final = set(allow) & set(valid) + if set(allow) - set(valid): # some pools were gone, prune + self.set_module_option('pool_ids', ','.join(final)) + pool_name_by_id = dict((p['pool'], p['pool_name']) for p in osdmap.dump().get('pools', [])) + final = [int(p) for p in final] + final = [pool_name_by_id[p] for p in final if p in pool_name_by_id] + plan = self.plan_create(name, osdmap, final) + self.optimizing = True + self.last_optimize_started = time.asctime(time.localtime()) + self.optimize_result = self.in_progress_string + start = time.time() + r, detail = self.optimize(plan) + end = time.time() + self.last_optimize_duration = str(datetime.timedelta(seconds=(end - start))) + if r == 0: + self.optimize_result = self.success_string + self.execute(plan) + else: + self.optimize_result = detail + self.optimizing = False + self.log.debug('Sleeping for %d', sleep_interval) + self.event.wait(sleep_interval) + self.event.clear() + + def plan_create(self, name, osdmap, pools): + mode = self.get_module_option('mode') + if mode == 'upmap': + # drop unnecessary MS member for upmap mode. + # this way we could effectively eliminate the usage of a + # complete pg_stats, which can become horribly inefficient + # as pg_num grows.. + plan = Plan(name, mode, osdmap, pools) + else: + plan = MsPlan(name, + mode, + MappingState(osdmap, + self.get("pg_stats"), + self.get("pool_stats"), + 'plan %s initial' % name), + pools) + return plan + + def plan_rm(self, name): + if name in self.plans: + del self.plans[name] + + def calc_eval(self, ms, pools): + pe = Eval(ms) + pool_rule = {} + pool_info = {} + for p in ms.osdmap_dump.get('pools',[]): + if len(pools) and p['pool_name'] not in pools: + continue + # skip dead or not-yet-ready pools too + if p['pool'] not in ms.poolids: + continue + pe.pool_name[p['pool']] = p['pool_name'] + pe.pool_id[p['pool_name']] = p['pool'] + pool_rule[p['pool_name']] = p['crush_rule'] + pe.pool_roots[p['pool_name']] = [] + pool_info[p['pool_name']] = p + if len(pool_info) == 0: + return pe + self.log.debug('pool_name %s' % pe.pool_name) + self.log.debug('pool_id %s' % pe.pool_id) + self.log.debug('pools %s' % pools) + self.log.debug('pool_rule %s' % pool_rule) + + osd_weight = { a['osd']: a['weight'] + for a in ms.osdmap_dump.get('osds',[]) if a['weight'] > 0 } + + # get expected distributions by root + actual_by_root = {} + rootids = ms.crush.find_takes() + roots = [] + for rootid in rootids: + ls = ms.osdmap.get_pools_by_take(rootid) + want = [] + # find out roots associating with pools we are passed in + for candidate in ls: + if candidate in pe.pool_name: + want.append(candidate) + if len(want) == 0: + continue + root = ms.crush.get_item_name(rootid) + pe.root_pools[root] = [] + for poolid in want: + pe.pool_roots[pe.pool_name[poolid]].append(root) + pe.root_pools[root].append(pe.pool_name[poolid]) + pe.root_ids[root] = rootid + roots.append(root) + weight_map = ms.crush.get_take_weight_osd_map(rootid) + adjusted_map = { + osd: cw * osd_weight[osd] + for osd,cw in weight_map.items() if osd in osd_weight and cw > 0 + } + sum_w = sum(adjusted_map.values()) + assert len(adjusted_map) == 0 or sum_w > 0 + pe.target_by_root[root] = { osd: w / sum_w + for osd,w in adjusted_map.items() } + actual_by_root[root] = { + 'pgs': {}, + 'objects': {}, + 'bytes': {}, + } + for osd in pe.target_by_root[root]: + actual_by_root[root]['pgs'][osd] = 0 + actual_by_root[root]['objects'][osd] = 0 + actual_by_root[root]['bytes'][osd] = 0 + pe.total_by_root[root] = { + 'pgs': 0, + 'objects': 0, + 'bytes': 0, + } + self.log.debug('pool_roots %s' % pe.pool_roots) + self.log.debug('root_pools %s' % pe.root_pools) + self.log.debug('target_by_root %s' % pe.target_by_root) + + # pool and root actual + for pool, pi in pool_info.items(): + poolid = pi['pool'] + pm = ms.pg_up_by_poolid[poolid] + pgs = 0 + objects = 0 + bytes = 0 + pgs_by_osd = {} + objects_by_osd = {} + bytes_by_osd = {} + for pgid, up in pm.items(): + for osd in [int(osd) for osd in up]: + if osd == CRUSHMap.ITEM_NONE: + continue + if osd not in pgs_by_osd: + pgs_by_osd[osd] = 0 + objects_by_osd[osd] = 0 + bytes_by_osd[osd] = 0 + pgs_by_osd[osd] += 1 + objects_by_osd[osd] += ms.pg_stat[pgid]['num_objects'] + bytes_by_osd[osd] += ms.pg_stat[pgid]['num_bytes'] + # pick a root to associate this pg instance with. + # note that this is imprecise if the roots have + # overlapping children. + # FIXME: divide bytes by k for EC pools. + for root in pe.pool_roots[pool]: + if osd in pe.target_by_root[root]: + actual_by_root[root]['pgs'][osd] += 1 + actual_by_root[root]['objects'][osd] += ms.pg_stat[pgid]['num_objects'] + actual_by_root[root]['bytes'][osd] += ms.pg_stat[pgid]['num_bytes'] + pgs += 1 + objects += ms.pg_stat[pgid]['num_objects'] + bytes += ms.pg_stat[pgid]['num_bytes'] + pe.total_by_root[root]['pgs'] += 1 + pe.total_by_root[root]['objects'] += ms.pg_stat[pgid]['num_objects'] + pe.total_by_root[root]['bytes'] += ms.pg_stat[pgid]['num_bytes'] + break + pe.count_by_pool[pool] = { + 'pgs': { + k: v + for k, v in pgs_by_osd.items() + }, + 'objects': { + k: v + for k, v in objects_by_osd.items() + }, + 'bytes': { + k: v + for k, v in bytes_by_osd.items() + }, + } + pe.actual_by_pool[pool] = { + 'pgs': { + k: float(v) / float(max(pgs, 1)) + for k, v in pgs_by_osd.items() + }, + 'objects': { + k: float(v) / float(max(objects, 1)) + for k, v in objects_by_osd.items() + }, + 'bytes': { + k: float(v) / float(max(bytes, 1)) + for k, v in bytes_by_osd.items() + }, + } + pe.total_by_pool[pool] = { + 'pgs': pgs, + 'objects': objects, + 'bytes': bytes, + } + for root in pe.total_by_root: + pe.count_by_root[root] = { + 'pgs': { + k: float(v) + for k, v in actual_by_root[root]['pgs'].items() + }, + 'objects': { + k: float(v) + for k, v in actual_by_root[root]['objects'].items() + }, + 'bytes': { + k: float(v) + for k, v in actual_by_root[root]['bytes'].items() + }, + } + pe.actual_by_root[root] = { + 'pgs': { + k: float(v) / float(max(pe.total_by_root[root]['pgs'], 1)) + for k, v in actual_by_root[root]['pgs'].items() + }, + 'objects': { + k: float(v) / float(max(pe.total_by_root[root]['objects'], 1)) + for k, v in actual_by_root[root]['objects'].items() + }, + 'bytes': { + k: float(v) / float(max(pe.total_by_root[root]['bytes'], 1)) + for k, v in actual_by_root[root]['bytes'].items() + }, + } + self.log.debug('actual_by_pool %s' % pe.actual_by_pool) + self.log.debug('actual_by_root %s' % pe.actual_by_root) + + # average and stddev and score + pe.stats_by_root = { + a: pe.calc_stats( + b, + pe.target_by_root[a], + pe.total_by_root[a] + ) for a, b in pe.count_by_root.items() + } + self.log.debug('stats_by_root %s' % pe.stats_by_root) + + # the scores are already normalized + pe.score_by_root = { + r: { + 'pgs': pe.stats_by_root[r]['pgs']['score'], + 'objects': pe.stats_by_root[r]['objects']['score'], + 'bytes': pe.stats_by_root[r]['bytes']['score'], + } for r in pe.total_by_root.keys() + } + self.log.debug('score_by_root %s' % pe.score_by_root) + + # get the list of score metrics, comma separated + metrics = self.get_module_option('crush_compat_metrics').split(',') + + # total score is just average of normalized stddevs + pe.score = 0.0 + for r, vs in pe.score_by_root.items(): + for k, v in vs.items(): + if k in metrics: + pe.score += v + pe.score /= len(metrics) * len(roots) + return pe + + def evaluate(self, ms, pools, verbose=False): + pe = self.calc_eval(ms, pools) + return pe.show(verbose=verbose) + + def optimize(self, plan): + self.log.info('Optimize plan %s' % plan.name) + max_misplaced = self.get_ceph_option('target_max_misplaced_ratio') + self.log.info('Mode %s, max misplaced %f' % + (plan.mode, max_misplaced)) + + info = self.get('pg_status') + unknown = info.get('unknown_pgs_ratio', 0.0) + degraded = info.get('degraded_ratio', 0.0) + inactive = info.get('inactive_pgs_ratio', 0.0) + misplaced = info.get('misplaced_ratio', 0.0) + plan.pg_status = info + self.log.debug('unknown %f degraded %f inactive %f misplaced %g', + unknown, degraded, inactive, misplaced) + if unknown > 0.0: + detail = 'Some PGs (%f) are unknown; try again later' % unknown + self.log.info(detail) + return -errno.EAGAIN, detail + elif degraded > 0.0: + detail = 'Some objects (%f) are degraded; try again later' % degraded + self.log.info(detail) + return -errno.EAGAIN, detail + elif inactive > 0.0: + detail = 'Some PGs (%f) are inactive; try again later' % inactive + self.log.info(detail) + return -errno.EAGAIN, detail + elif misplaced >= max_misplaced: + detail = 'Too many objects (%f > %f) are misplaced; ' \ + 'try again later' % (misplaced, max_misplaced) + self.log.info(detail) + return -errno.EAGAIN, detail + else: + if plan.mode == 'upmap': + return self.do_upmap(plan) + elif plan.mode == 'crush-compat': + return self.do_crush_compat(plan) + elif plan.mode == 'none': + detail = 'Please do "ceph balancer mode" to choose a valid mode first' + self.log.info('Idle') + return -errno.ENOEXEC, detail + else: + detail = 'Unrecognized mode %s' % plan.mode + self.log.info(detail) + return -errno.EINVAL, detail + + def do_upmap(self, plan): + self.log.info('do_upmap') + max_optimizations = self.get_module_option('upmap_max_optimizations') + max_deviation = self.get_module_option('upmap_max_deviation') + osdmap_dump = plan.osdmap_dump + + if len(plan.pools): + pools = plan.pools + else: # all + pools = [str(i['pool_name']) for i in osdmap_dump.get('pools',[])] + if len(pools) == 0: + detail = 'No pools available' + self.log.info(detail) + return -errno.ENOENT, detail + # shuffle pool list so they all get equal (in)attention + random.shuffle(pools) + self.log.info('pools %s' % pools) + + adjusted_pools = [] + inc = plan.inc + total_did = 0 + left = max_optimizations + pools_with_pg_merge = [p['pool_name'] for p in osdmap_dump.get('pools', []) + if p['pg_num'] > p['pg_num_target']] + crush_rule_by_pool_name = dict((p['pool_name'], p['crush_rule']) for p in osdmap_dump.get('pools', [])) + for pool in pools: + if pool not in crush_rule_by_pool_name: + self.log.info('pool %s does not exist' % pool) + continue + if pool in pools_with_pg_merge: + self.log.info('pool %s has pending PG(s) for merging, skipping for now' % pool) + continue + adjusted_pools.append(pool) + # shuffle so all pools get equal (in)attention + random.shuffle(adjusted_pools) + pool_dump = osdmap_dump.get('pools', []) + for pool in adjusted_pools: + for p in pool_dump: + if p['pool_name'] == pool: + pool_id = p['pool'] + break + + # note that here we deliberately exclude any scrubbing pgs too + # since scrubbing activities have significant impacts on performance + num_pg_active_clean = 0 + for p in plan.pg_status.get('pgs_by_pool_state', []): + pgs_pool_id = p['pool_id'] + if pgs_pool_id != pool_id: + continue + for s in p['pg_state_counts']: + if s['state_name'] == 'active+clean': + num_pg_active_clean += s['count'] + break + available = min(left, num_pg_active_clean) + did = plan.osdmap.calc_pg_upmaps(inc, max_deviation, available, [pool]) + total_did += did + left -= did + if left <= 0: + break + self.log.info('prepared %d/%d changes' % (total_did, max_optimizations)) + if total_did == 0: + return -errno.EALREADY, 'Unable to find further optimization, ' \ + 'or pool(s) pg_num is decreasing, ' \ + 'or distribution is already perfect' + return 0, '' + + def do_crush_compat(self, plan): + self.log.info('do_crush_compat') + max_iterations = self.get_module_option('crush_compat_max_iterations') + if max_iterations < 1: + return -errno.EINVAL, '"crush_compat_max_iterations" must be >= 1' + step = self.get_module_option('crush_compat_step') + if step <= 0 or step >= 1.0: + return -errno.EINVAL, '"crush_compat_step" must be in (0, 1)' + max_misplaced = self.get_ceph_option('target_max_misplaced_ratio') + min_pg_per_osd = 2 + + ms = plan.initial + osdmap = ms.osdmap + crush = osdmap.get_crush() + pe = self.calc_eval(ms, plan.pools) + min_score_to_optimize = self.get_module_option('min_score') + if pe.score <= min_score_to_optimize: + if pe.score == 0: + detail = 'Distribution is already perfect' + else: + detail = 'score %f <= min_score %f, will not optimize' \ + % (pe.score, min_score_to_optimize) + self.log.info(detail) + return -errno.EALREADY, detail + + # get current osd reweights + orig_osd_weight = { a['osd']: a['weight'] + for a in ms.osdmap_dump.get('osds',[]) } + reweighted_osds = [ a for a,b in orig_osd_weight.items() + if b < 1.0 and b > 0.0 ] + + # get current compat weight-set weights + orig_ws = self.get_compat_weight_set_weights(ms) + if not orig_ws: + return -errno.EAGAIN, 'compat weight-set not available' + orig_ws = { a: b for a, b in orig_ws.items() if a >= 0 } + + # Make sure roots don't overlap their devices. If so, we + # can't proceed. + roots = list(pe.target_by_root.keys()) + self.log.debug('roots %s', roots) + visited = {} + overlap = {} + root_ids = {} + for root, wm in pe.target_by_root.items(): + for osd in wm: + if osd in visited: + if osd not in overlap: + overlap[osd] = [ visited[osd] ] + overlap[osd].append(root) + visited[osd] = root + if len(overlap) > 0: + detail = 'Some osds belong to multiple subtrees: %s' % \ + overlap + self.log.error(detail) + return -errno.EOPNOTSUPP, detail + + # rebalance by pgs, objects, or bytes + metrics = self.get_module_option('crush_compat_metrics').split(',') + key = metrics[0] # balancing using the first score metric + if key not in ['pgs', 'bytes', 'objects']: + self.log.warning("Invalid crush_compat balancing key %s. Using 'pgs'." % key) + key = 'pgs' + + # go + best_ws = copy.deepcopy(orig_ws) + best_ow = copy.deepcopy(orig_osd_weight) + best_pe = pe + left = max_iterations + bad_steps = 0 + next_ws = copy.deepcopy(best_ws) + next_ow = copy.deepcopy(best_ow) + while left > 0: + # adjust + self.log.debug('best_ws %s' % best_ws) + random.shuffle(roots) + for root in roots: + pools = best_pe.root_pools[root] + osds = len(best_pe.target_by_root[root]) + min_pgs = osds * min_pg_per_osd + if best_pe.total_by_root[root][key] < min_pgs: + self.log.info('Skipping root %s (pools %s), total pgs %d ' + '< minimum %d (%d per osd)', + root, pools, + best_pe.total_by_root[root][key], + min_pgs, min_pg_per_osd) + continue + self.log.info('Balancing root %s (pools %s) by %s' % + (root, pools, key)) + target = best_pe.target_by_root[root] + actual = best_pe.actual_by_root[root][key] + queue = sorted(actual.keys(), + key=lambda osd: -abs(target[osd] - actual[osd])) + for osd in queue: + if orig_osd_weight[osd] == 0: + self.log.debug('skipping out osd.%d', osd) + else: + deviation = target[osd] - actual[osd] + if deviation == 0: + break + self.log.debug('osd.%d deviation %f', osd, deviation) + weight = best_ws[osd] + ow = orig_osd_weight[osd] + if actual[osd] > 0: + calc_weight = target[osd] / actual[osd] * weight * ow + else: + # for newly created osds, reset calc_weight at target value + # this way weight-set will end up absorbing *step* of its + # target (final) value at the very beginning and slowly catch up later. + # note that if this turns out causing too many misplaced + # pgs, then we'll reduce step and retry + calc_weight = target[osd] + new_weight = weight * (1.0 - step) + calc_weight * step + self.log.debug('Reweight osd.%d %f -> %f', osd, weight, + new_weight) + next_ws[osd] = new_weight + if ow < 1.0: + new_ow = min(1.0, max(step + (1.0 - step) * ow, + ow + .005)) + self.log.debug('Reweight osd.%d reweight %f -> %f', + osd, ow, new_ow) + next_ow[osd] = new_ow + + # normalize weights under this root + root_weight = crush.get_item_weight(pe.root_ids[root]) + root_sum = sum(b for a,b in next_ws.items() + if a in target.keys()) + if root_sum > 0 and root_weight > 0: + factor = root_sum / root_weight + self.log.debug('normalizing root %s %d, weight %f, ' + 'ws sum %f, factor %f', + root, pe.root_ids[root], root_weight, + root_sum, factor) + for osd in actual.keys(): + next_ws[osd] = next_ws[osd] / factor + + # recalc + plan.compat_ws = copy.deepcopy(next_ws) + next_ms = plan.final_state() + next_pe = self.calc_eval(next_ms, plan.pools) + next_misplaced = next_ms.calc_misplaced_from(ms) + self.log.debug('Step result score %f -> %f, misplacing %f', + best_pe.score, next_pe.score, next_misplaced) + + if next_misplaced > max_misplaced: + if best_pe.score < pe.score: + self.log.debug('Step misplaced %f > max %f, stopping', + next_misplaced, max_misplaced) + break + step /= 2.0 + next_ws = copy.deepcopy(best_ws) + next_ow = copy.deepcopy(best_ow) + self.log.debug('Step misplaced %f > max %f, reducing step to %f', + next_misplaced, max_misplaced, step) + else: + if next_pe.score > best_pe.score * 1.0001: + bad_steps += 1 + if bad_steps < 5 and random.randint(0, 100) < 70: + self.log.debug('Score got worse, taking another step') + else: + step /= 2.0 + next_ws = copy.deepcopy(best_ws) + next_ow = copy.deepcopy(best_ow) + self.log.debug('Score got worse, trying smaller step %f', + step) + else: + bad_steps = 0 + best_pe = next_pe + best_ws = copy.deepcopy(next_ws) + best_ow = copy.deepcopy(next_ow) + if best_pe.score == 0: + break + left -= 1 + + # allow a small regression if we are phasing out osd weights + fudge = 0 + if best_ow != orig_osd_weight: + fudge = .001 + + if best_pe.score < pe.score + fudge: + self.log.info('Success, score %f -> %f', pe.score, best_pe.score) + plan.compat_ws = best_ws + for osd, w in best_ow.items(): + if w != orig_osd_weight[osd]: + self.log.debug('osd.%d reweight %f', osd, w) + plan.osd_weights[osd] = w + return 0, '' + else: + self.log.info('Failed to find further optimization, score %f', + pe.score) + plan.compat_ws = {} + return -errno.EDOM, 'Unable to find further optimization, ' \ + 'change balancer mode and retry might help' + + def get_compat_weight_set_weights(self, ms): + have_choose_args = CRUSHMap.have_default_choose_args(ms.crush_dump) + if have_choose_args: + # get number of buckets in choose_args + choose_args_len = len(CRUSHMap.get_default_choose_args(ms.crush_dump)) + if not have_choose_args or choose_args_len != len(ms.crush_dump['buckets']): + # enable compat weight-set first + self.log.debug('no choose_args or all buckets do not have weight-sets') + self.log.debug('ceph osd crush weight-set create-compat') + result = CommandResult('') + self.send_command(result, 'mon', '', json.dumps({ + 'prefix': 'osd crush weight-set create-compat', + 'format': 'json', + }), '') + r, outb, outs = result.wait() + if r != 0: + self.log.error('Error creating compat weight-set') + return + + result = CommandResult('') + self.send_command(result, 'mon', '', json.dumps({ + 'prefix': 'osd crush dump', + 'format': 'json', + }), '') + r, outb, outs = result.wait() + if r != 0: + self.log.error('Error dumping crush map') + return + try: + crushmap = json.loads(outb) + except: + raise RuntimeError('unable to parse crush map') + else: + crushmap = ms.crush_dump + + raw = CRUSHMap.get_default_choose_args(crushmap) + weight_set = {} + for b in raw: + bucket = None + for t in crushmap['buckets']: + if t['id'] == b['bucket_id']: + bucket = t + break + if not bucket: + raise RuntimeError('could not find bucket %s' % b['bucket_id']) + self.log.debug('bucket items %s' % bucket['items']) + self.log.debug('weight set %s' % b['weight_set'][0]) + if len(bucket['items']) != len(b['weight_set'][0]): + raise RuntimeError('weight-set size does not match bucket items') + for pos in range(len(bucket['items'])): + weight_set[bucket['items'][pos]['id']] = b['weight_set'][0][pos] + + self.log.debug('weight_set weights %s' % weight_set) + return weight_set + + def do_crush(self): + self.log.info('do_crush (not yet implemented)') + + def do_osd_weight(self): + self.log.info('do_osd_weight (not yet implemented)') + + def execute(self, plan): + self.log.info('Executing plan %s' % plan.name) + + commands = [] + + # compat weight-set + if len(plan.compat_ws) and \ + not CRUSHMap.have_default_choose_args(plan.initial.crush_dump): + self.log.debug('ceph osd crush weight-set create-compat') + result = CommandResult('') + self.send_command(result, 'mon', '', json.dumps({ + 'prefix': 'osd crush weight-set create-compat', + 'format': 'json', + }), '') + r, outb, outs = result.wait() + if r != 0: + self.log.error('Error creating compat weight-set') + return r, outs + + for osd, weight in plan.compat_ws.items(): + self.log.info('ceph osd crush weight-set reweight-compat osd.%d %f', + osd, weight) + result = CommandResult('') + self.send_command(result, 'mon', '', json.dumps({ + 'prefix': 'osd crush weight-set reweight-compat', + 'format': 'json', + 'item': 'osd.%d' % osd, + 'weight': [weight], + }), '') + commands.append(result) + + # new_weight + reweightn = {} + for osd, weight in plan.osd_weights.items(): + reweightn[str(osd)] = str(int(weight * float(0x10000))) + if len(reweightn): + self.log.info('ceph osd reweightn %s', reweightn) + result = CommandResult('') + self.send_command(result, 'mon', '', json.dumps({ + 'prefix': 'osd reweightn', + 'format': 'json', + 'weights': json.dumps(reweightn), + }), '') + commands.append(result) + + # upmap + incdump = plan.inc.dump() + for item in incdump.get('new_pg_upmap', []): + self.log.info('ceph osd pg-upmap %s mappings %s', item['pgid'], + item['osds']) + result = CommandResult('foo') + self.send_command(result, 'mon', '', json.dumps({ + 'prefix': 'osd pg-upmap', + 'format': 'json', + 'pgid': item['pgid'], + 'id': item['osds'], + }), 'foo') + commands.append(result) + + for pgid in incdump.get('old_pg_upmap', []): + self.log.info('ceph osd rm-pg-upmap %s', pgid) + result = CommandResult('foo') + self.send_command(result, 'mon', '', json.dumps({ + 'prefix': 'osd rm-pg-upmap', + 'format': 'json', + 'pgid': pgid, + }), 'foo') + commands.append(result) + + for item in incdump.get('new_pg_upmap_items', []): + self.log.info('ceph osd pg-upmap-items %s mappings %s', item['pgid'], + item['mappings']) + osdlist = [] + for m in item['mappings']: + osdlist += [m['from'], m['to']] + result = CommandResult('foo') + self.send_command(result, 'mon', '', json.dumps({ + 'prefix': 'osd pg-upmap-items', + 'format': 'json', + 'pgid': item['pgid'], + 'id': osdlist, + }), 'foo') + commands.append(result) + + for pgid in incdump.get('old_pg_upmap_items', []): + self.log.info('ceph osd rm-pg-upmap-items %s', pgid) + result = CommandResult('foo') + self.send_command(result, 'mon', '', json.dumps({ + 'prefix': 'osd rm-pg-upmap-items', + 'format': 'json', + 'pgid': pgid, + }), 'foo') + commands.append(result) + + # wait for commands + self.log.debug('commands %s' % commands) + for result in commands: + r, outb, outs = result.wait() + if r != 0: + self.log.error('execute error: r = %d, detail = %s' % (r, outs)) + return r, outs + self.log.debug('done') + return 0, '' + + def gather_telemetry(self): + return { + 'active': self.active, + 'mode': self.mode, + } diff --git a/src/pybind/mgr/ceph_module.pyi b/src/pybind/mgr/ceph_module.pyi new file mode 100644 index 000000000..d853fbf82 --- /dev/null +++ b/src/pybind/mgr/ceph_module.pyi @@ -0,0 +1,114 @@ +# This is an interface definition of classes that are generated within C++. +# Used by mypy to do proper type checking of mgr modules. +# Without this file, all classes have undefined base classes. + +from typing import Any, Dict, List, Mapping, Optional, Sequence, Tuple, Union +try: + from typing import Protocol # Protocol was added in Python 3.8 +except ImportError: + class Protocol: # type: ignore + pass + + +class BasePyOSDMap(object): + def _get_epoch(self): ... + def _get_crush_version(self): ... + def _dump(self):... + def _new_incremental(self):... + def _apply_incremental(self, inc: 'BasePyOSDMapIncremental'):... + def _get_crush(self):... + def _get_pools_by_take(self, take):... + def _calc_pg_upmaps(self, inc, max_deviation, max_iterations, pool):... + def _map_pool_pgs_up(self, poolid):... + def _pg_to_up_acting_osds(self, pool_id, ps):... + def _pool_raw_used_rate(self, pool_id):... + +class BasePyOSDMapIncremental(object): + def _get_epoch(self):... + def _dump(self):... + def _set_osd_reweights(self, weightmap):... + def _set_crush_compat_weight_set_weights(self, weightmap):... + +class BasePyCRUSH(object): + def _dump(self):... + def _get_item_weight(self, item):... + def _get_item_name(self, item):... + def _find_roots(self):... + def _find_takes(self):... + def _get_take_weight_osd_map(self, root):... + +class BaseMgrStandbyModule(object): + def __init__(self, capsule): pass + def _ceph_get(self, data_name: str) -> Dict[str, Any]: ... + def _ceph_get_mgr_id(self):... + def _ceph_get_module_option(self, key, prefix=None):... + def _ceph_get_option(self, key):... + def _ceph_get_store(self, key):... + def _ceph_get_active_uri(self):... + + +OptionValue = Optional[Union[bool, int, float, str]] + + +class CompletionT(Protocol): + def complete(self, r: int, outb: str, outs: str) -> None: ... + + +ServerInfoT = Dict[str, Union[str, List[Dict[str, str]]]] +HealthCheckT = Mapping[str, Union[int, str, Sequence[str]]] +PerfCounterT = Dict[str, Any] + +class BaseMgrModule(object): + def __init__(self, py_modules_ptr: object, this_ptr: object) -> None: pass + def _ceph_get_version(self) -> str: ... + def _ceph_get_release_name(self) -> str: ... + def _ceph_lookup_release_name(self, release: int) -> str: ... + def _ceph_cluster_log(self, channel: str, priority: int, message: str) -> None: ... + def _ceph_get_context(self) -> object: ... + def _ceph_get(self, data_name: str) -> Dict[str, Any]: ... + def _ceph_get_server(self, hostname: Optional[str]) -> Union[ServerInfoT, + List[ServerInfoT]]: ... + def _ceph_get_perf_schema(self, svc_type: str, svc_name: str) -> Dict[str, Any]: ... + def _ceph_get_counter(self, svc_type: str, svc_name: str, path: str) -> Dict[str, List[Tuple[float, int]]]: ... + def _ceph_get_latest_counter(self, svc_type, svc_name, path): ... + def _ceph_get_metadata(self, svc_type, svc_id): ... + def _ceph_get_daemon_status(self, svc_type, svc_id): ... + def _ceph_send_command(self, + result: CompletionT, + svc_type: str, + svc_id: str, + command: str, + tag: str, + inbuf: Optional[str]) -> None: ... + def _ceph_set_health_checks(self, checks: Mapping[str, HealthCheckT]) -> None: ... + def _ceph_get_mgr_id(self) -> str: ... + def _ceph_get_ceph_conf_path(self) -> str: ... + def _ceph_get_option(self, key: str) -> OptionValue: ... + def _ceph_get_foreign_option(self, entity: str, key: str) -> OptionValue: ... + def _ceph_get_module_option(self, + key: str, + default: str, + localized_prefix: str = "") -> OptionValue: ... + def _ceph_get_store_prefix(self, key_prefix) -> Dict[str, str]: ... + def _ceph_set_module_option(self, module: str, key: str, val: Optional[str]) -> None: ... + def _ceph_set_store(self, key: str, val: Optional[str]) -> None: ... + def _ceph_get_store(self, key: str) -> Optional[str]: ... + # mgr actually imports OSDMap from mgr_module and constructs an OSDMap + def _ceph_get_osdmap(self) -> BasePyOSDMap: ... + def _ceph_set_uri(self, uri: str) -> None: ... + def _ceph_set_device_wear_level(self, devid: str, val: float) -> None: ... + def _ceph_have_mon_connection(self) -> bool: ... + def _ceph_update_progress_event(self, evid: str, desc: str, progress: float, add_to_ceph_s: bool) -> None: ... + def _ceph_complete_progress_event(self, evid: str) -> None: ... + def _ceph_clear_all_progress_events(self) -> None: ... + def _ceph_dispatch_remote(self, module_name: str, method_name: str, *args: Any, **kwargs: Any) -> Any: ... + def _ceph_add_osd_perf_query(self, query: Dict[str, Dict[str, Any]]) -> Optional[int]: ... + def _ceph_remove_osd_perf_query(self, query_id: int) -> None: ... + def _ceph_get_osd_perf_counters(self, query_id: int) -> Optional[Dict[str, List[PerfCounterT]]]: ... + def _ceph_add_mds_perf_query(self, query: Dict[str, Dict[str, Any]]) -> Optional[int]: ... + def _ceph_remove_mds_perf_query(self, query_id: int) -> None: ... + def _ceph_reregister_mds_perf_queries(self) -> None: ... + def _ceph_get_mds_perf_counters(self, query_id: int) -> Optional[Dict[str, List[PerfCounterT]]]: ... + def _ceph_unregister_client(self, addrs: str) -> None: ... + def _ceph_register_client(self, addrs: str) -> None: ... + def _ceph_is_authorized(self, arguments: Dict[str, str]) -> bool: ... diff --git a/src/pybind/mgr/cephadm/.gitignore b/src/pybind/mgr/cephadm/.gitignore new file mode 100644 index 000000000..a273f8603 --- /dev/null +++ b/src/pybind/mgr/cephadm/.gitignore @@ -0,0 +1,2 @@ +.vagrant +ssh-config diff --git a/src/pybind/mgr/cephadm/HACKING.rst b/src/pybind/mgr/cephadm/HACKING.rst new file mode 100644 index 000000000..fa6ea9e1b --- /dev/null +++ b/src/pybind/mgr/cephadm/HACKING.rst @@ -0,0 +1,272 @@ +Development +=========== + + +There are multiple ways to set up a development environment for the SSH orchestrator. +In the following I'll use the `vstart` method. + +1) Make sure remoto is installed (0.35 or newer) + +2) Use vstart to spin up a cluster + + +:: + + # ../src/vstart.sh -n --cephadm + +*Note that when you specify `--cephadm` you have to have passwordless ssh access to localhost* + +It will add your ~/.ssh/id_rsa and ~/.ssh/id_rsa.pub to `mgr/ssh/ssh_identity_{key, pub}` +and add your $HOSTNAME to the list of known hosts. + +This will also enable the cephadm mgr module and enable it as the orchestrator backend. + +*Optional:* + +While the above is sufficient for most operations, you may want to add a second host to the mix. +There is `Vagrantfile` for creating a minimal cluster in `src/pybind/mgr/cephadm/`. + +If you wish to extend the one-node-localhost cluster to i.e. test more sophisticated OSD deployments you can follow the next steps: + +From within the `src/pybind/mgr/cephadm` directory. + + +1) Spawn VMs + +:: + + # vagrant up + +This will spawn three machines by default. +mon0, mgr0 and osd0 with 2 additional disks. + +You can change that by passing `MONS` (default: 1), `MGRS` (default: 1), `OSDS` (default: 1) and +`DISKS` (default: 2) environment variables to overwrite the defaults. In order to not always have +to set the environment variables you can now create as JSON see `./vagrant.config.example.json` +for details. + +If will also come with the necessary packages preinstalled as well as your ~/.ssh/id_rsa.pub key +injected. (to users root and vagrant; the cephadm-orchestrator currently connects as root) + + +2) Update the ssh-config + +The cephadm orchestrator needs to understand how to connect to the new node. Most likely the VM +isn't reachable with the default settings used: + +``` +Host * +User root +StrictHostKeyChecking no +``` + +You want to adjust this by retrieving an adapted ssh_config from Vagrant. + +:: + + # vagrant ssh-config > ssh-config + + +Now set the newly created config for Ceph. + +:: + + # ceph cephadm set-ssh-config -i + + +3) Add the new host + +Add the newly created host(s) to the inventory. + +:: + + + # ceph orch host add + + +4) Verify the inventory + +You should see the hostname in the list. + +:: + + # ceph orch host ls + + +5) Verify the devices + +To verify all disks are set and in good shape look if all devices have been spawned +and can be found + +:: + + # ceph orch device ls + + +6) Make a snapshot of all your VMs! + +To not go the long way again the next time snapshot your VMs in order to revert them back +if they are dirty. + +In `this repository `_ you can find two +scripts that will help you with doing a snapshot and reverting it, without having to manual +snapshot and revert each VM individually. + + +Understanding ``AsyncCompletion`` +================================= + +How can I store temporary variables? +------------------------------------ + +Let's imagine you want to write code similar to + +.. code:: python + + hosts = self.get_hosts() + inventory = self.get_inventory(hosts) + return self._create_osd(hosts, drive_group, inventory) + +That won't work, as ``get_hosts`` and ``get_inventory`` return objects +of type ``AsyncCompletion``. + +Now let's imaging a Python 3 world, where we can use ``async`` and +``await``. Then we actually can write this like so: + +.. code:: python + + hosts = await self.get_hosts() + inventory = await self.get_inventory(hosts) + return self._create_osd(hosts, drive_group, inventory) + +Let's use a simple example to make this clear: + +.. code:: python + + val = await func_1() + return func_2(val) + +As we're not yet in Python 3, we need to do write ``await`` manually by +calling ``orchestrator.Completion.then()``: + +.. code:: python + + func_1().then(lambda val: func_2(val)) + + # or + func_1().then(func_2) + +Now let's desugar the original example: + +.. code:: python + + hosts = await self.get_hosts() + inventory = await self.get_inventory(hosts) + return self._create_osd(hosts, drive_group, inventory) + +Now let's replace one ``async`` at a time: + +.. code:: python + + hosts = await self.get_hosts() + return self.get_inventory(hosts).then(lambda inventory: + self._create_osd(hosts, drive_group, inventory)) + +Then finally: + +.. code:: python + + self.get_hosts().then(lambda hosts: + self.get_inventory(hosts).then(lambda inventory: + self._create_osd(hosts, + drive_group, inventory))) + +This also works without lambdas: + +.. code:: python + + def call_inventory(hosts): + def call_create(inventory) + return self._create_osd(hosts, drive_group, inventory) + + return self.get_inventory(hosts).then(call_create) + + self.get_hosts(call_inventory) + +We should add support for ``await`` as soon as we're on Python 3. + +I want to call my function for every host! +------------------------------------------ + +Imagine you have a function that looks like so: + +.. code:: python + + @async_completion + def deploy_stuff(name, node): + ... + +And you want to call ``deploy_stuff`` like so: + +.. code:: python + + return [deploy_stuff(name, node) for node in nodes] + +This won't work as expected. The number of ``AsyncCompletion`` objects +created should be ``O(1)``. But there is a solution: +``@async_map_completion`` + +.. code:: python + + @async_map_completion + def deploy_stuff(name, node): + ... + + return deploy_stuff([(name, node) for node in nodes]) + +This way, we're only creating one ``AsyncCompletion`` object. Note that +you should not create new ``AsyncCompletion`` within ``deploy_stuff``, as +we're then no longer have ``O(1)`` completions: + +.. code:: python + + @async_completion + def other_async_function(): + ... + + @async_map_completion + def deploy_stuff(name, node): + return other_async_function() # wrong! + +Why do we need this? +-------------------- + +I've tried to look into making Completions composable by being able to +call one completion from another completion. I.e. making them re-usable +using Promises E.g.: + +.. code:: python + + >>> return self.get_hosts().then(self._create_osd) + +where ``get_hosts`` returns a Completion of list of hosts and +``_create_osd`` takes a list of hosts. + +The concept behind this is to store the computation steps explicit and +then explicitly evaluate the chain: + +.. code:: python + + p = Completion(on_complete=lambda x: x*2).then(on_complete=lambda x: str(x)) + p.finalize(2) + assert p.result = "4" + +or graphically: + +:: + + +---------------+ +-----------------+ + | | then | | + | lambda x: x*x | +--> | lambda x: str(x)| + | | | | + +---------------+ +-----------------+ diff --git a/src/pybind/mgr/cephadm/Vagrantfile b/src/pybind/mgr/cephadm/Vagrantfile new file mode 100644 index 000000000..3a08380c3 --- /dev/null +++ b/src/pybind/mgr/cephadm/Vagrantfile @@ -0,0 +1,66 @@ +# vi: set ft=ruby : +# +# In order to reduce the need of recreating all vagrant boxes everytime they +# get dirty, snaptshot them and revert the snapshot of them instead. +# Two helpful scripts to do this easily can be found here: +# https://github.com/Devp00l/vagrant-helper-scripts + +require 'json' +configFileName = 'vagrant.config.json' +CONFIG = File.file?(configFileName) && JSON.parse(File.read(File.join(File.dirname(__FILE__), configFileName))) + +def getConfig(name, default) + down = name.downcase + up = name.upcase + CONFIG && CONFIG[down] ? CONFIG[down] : (ENV[up] ? ENV[up].to_i : default) +end + +OSDS = getConfig('OSDS', 1) +MGRS = getConfig('MGRS', 1) +MONS = getConfig('MONS', 1) +DISKS = getConfig('DISKS', 2) + +# Activate only for test purpose as it changes the output of each vagrant command link to get the ssh_config. +# puts "Your setup:","OSDs: #{OSDS}","MGRs: #{MGRS}","MONs: #{MONS}","Disks per OSD: #{DISKS}" + +Vagrant.configure("2") do |config| + config.vm.synced_folder ".", "/vagrant", disabled: true + config.vm.network "private_network", type: "dhcp" + config.vm.box = "centos/8" + + (0..MONS - 1).each do |i| + config.vm.define "mon#{i}" do |mon| + mon.vm.hostname = "mon#{i}" + end + end + (0..MGRS - 1).each do |i| + config.vm.define "mgr#{i}" do |mgr| + mgr.vm.hostname = "mgr#{i}" + end + end + (0..OSDS - 1).each do |i| + config.vm.define "osd#{i}" do |osd| + osd.vm.hostname = "osd#{i}" + osd.vm.provider :libvirt do |libvirt| + (0..DISKS - 1).each do |d| + # In ruby value.chr makes ASCII char from value + libvirt.storage :file, :size => '20G', :device => "vd#{(98+d).chr}#{i}" + end + end + end + end + + config.vm.provision "file", source: "~/.ssh/id_rsa.pub", destination: "~/.ssh/id_rsa.pub" + config.vm.provision "shell", inline: <<-SHELL + cat /home/vagrant/.ssh/id_rsa.pub >> /home/vagrant/.ssh/authorized_keys + sudo cp -r /home/vagrant/.ssh /root/.ssh + SHELL + + config.vm.provision "shell", inline: <<-SHELL + sudo yum install -y yum-utils + sudo yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm + sudo rpm --import 'https://download.ceph.com/keys/release.asc' + curl -L https://shaman.ceph.com/api/repos/ceph/master/latest/centos/8/repo/ | sudo tee /etc/yum.repos.d/shaman.repo + sudo yum install -y python36 podman ceph + SHELL +end diff --git a/src/pybind/mgr/cephadm/__init__.py b/src/pybind/mgr/cephadm/__init__.py new file mode 100644 index 000000000..597d883f7 --- /dev/null +++ b/src/pybind/mgr/cephadm/__init__.py @@ -0,0 +1,10 @@ +from .module import CephadmOrchestrator + +__all__ = [ + "CephadmOrchestrator", +] + +import os +if 'UNITTEST' in os.environ: + import tests + __all__.append(tests.__name__) diff --git a/src/pybind/mgr/cephadm/autotune.py b/src/pybind/mgr/cephadm/autotune.py new file mode 100644 index 000000000..51c931cba --- /dev/null +++ b/src/pybind/mgr/cephadm/autotune.py @@ -0,0 +1,54 @@ +import logging +from typing import List, Optional, Callable, Any, Tuple + +from orchestrator._interface import DaemonDescription + +logger = logging.getLogger(__name__) + + +class MemoryAutotuner(object): + + min_size_by_type = { + 'mds': 4096 * 1048576, + 'mgr': 4096 * 1048576, + 'mon': 1024 * 1048576, + 'crash': 128 * 1048576, + 'keepalived': 128 * 1048576, + 'haproxy': 128 * 1048576, + } + default_size = 1024 * 1048576 + + def __init__( + self, + daemons: List[DaemonDescription], + config_get: Callable[[str, str], Any], + total_mem: int, + ): + self.daemons = daemons + self.config_get = config_get + self.total_mem = total_mem + + def tune(self) -> Tuple[Optional[int], List[str]]: + tuned_osds: List[str] = [] + total = self.total_mem + for d in self.daemons: + if d.daemon_type == 'mds': + total -= self.config_get(d.name(), 'mds_cache_memory_limit') + continue + if d.daemon_type != 'osd': + assert d.daemon_type + total -= max( + self.min_size_by_type.get(d.daemon_type, self.default_size), + d.memory_usage or 0 + ) + continue + if not self.config_get(d.name(), 'osd_memory_target_autotune'): + total -= self.config_get(d.name(), 'osd_memory_target') + continue + tuned_osds.append(d.name()) + if total < 0: + return None, [] + if not tuned_osds: + return None, [] + per = total // len(tuned_osds) + return int(per), tuned_osds diff --git a/src/pybind/mgr/cephadm/ceph.repo b/src/pybind/mgr/cephadm/ceph.repo new file mode 100644 index 000000000..6f710e7ce --- /dev/null +++ b/src/pybind/mgr/cephadm/ceph.repo @@ -0,0 +1,23 @@ +[ceph] +name=Ceph packages for $basearch +baseurl=https://download.ceph.com/rpm-mimic/el7/$basearch +enabled=1 +priority=2 +gpgcheck=1 +gpgkey=https://download.ceph.com/keys/release.asc + +[ceph-noarch] +name=Ceph noarch packages +baseurl=https://download.ceph.com/rpm-mimic/el7/noarch +enabled=1 +priority=2 +gpgcheck=1 +gpgkey=https://download.ceph.com/keys/release.asc + +[ceph-source] +name=Ceph source packages +baseurl=https://download.ceph.com/rpm-mimic/el7/SRPMS +enabled=0 +priority=2 +gpgcheck=1 +gpgkey=https://download.ceph.com/keys/release.asc diff --git a/src/pybind/mgr/cephadm/configchecks.py b/src/pybind/mgr/cephadm/configchecks.py new file mode 100644 index 000000000..dc7a09827 --- /dev/null +++ b/src/pybind/mgr/cephadm/configchecks.py @@ -0,0 +1,705 @@ +import json +import ipaddress +import logging + +from mgr_module import ServiceInfoT + +from typing import TYPE_CHECKING, Any, Dict, List, Optional, cast, Tuple, Callable + +if TYPE_CHECKING: + from cephadm.module import CephadmOrchestrator + +logger = logging.getLogger(__name__) + + +class HostFacts: + + def __init__(self) -> None: + self.arch: Optional[str] = None + self.bios_date: Optional[str] = None + self.bios_version: Optional[str] = None + self.cpu_cores: Optional[int] = None + self.cpu_count: Optional[int] = None + self.cpu_load: Optional[Dict[str, float]] = None + self.cpu_model: Optional[str] = None + self.cpu_threads: Optional[int] = None + self.flash_capacity: Optional[str] = None + self.flash_capacity_bytes: Optional[int] = None + self.flash_count: Optional[int] = None + self.flash_list: Optional[List[Dict[str, Any]]] = None + self.hdd_capacity: Optional[str] = None + self.hdd_capacity_bytes: Optional[int] = None + self.hdd_count: Optional[int] = None + self.hdd_list: Optional[List[Dict[str, Any]]] = None + self.hostname: Optional[str] = None + self.interfaces: Optional[Dict[str, Dict[str, Any]]] = None + self.kernel: Optional[str] = None + self.kernel_parameters: Optional[Dict[str, Any]] = None + self.kernel_security: Optional[Dict[str, str]] = None + self.memory_available_kb: Optional[int] = None + self.memory_free_kb: Optional[int] = None + self.memory_total_kb: Optional[int] = None + self.model: Optional[str] = None + self.nic_count: Optional[int] = None + self.operating_system: Optional[str] = None + self.subscribed: Optional[str] = None + self.system_uptime: Optional[float] = None + self.timestamp: Optional[float] = None + self.vendor: Optional[str] = None + self._valid = False + + def load_facts(self, json_data: Dict[str, Any]) -> None: + + if isinstance(json_data, dict): + keys = json_data.keys() + if all([k in keys for k in self.__dict__ if not k.startswith('_')]): + self._valid = True + for k in json_data.keys(): + if hasattr(self, k): + setattr(self, k, json_data[k]) + else: + self._valid = False + else: + self._valid = False + + def subnet_to_nic(self, subnet: str) -> Optional[str]: + ip_version = ipaddress.ip_network(subnet).version + logger.debug(f"subnet {subnet} is IP version {ip_version}") + interfaces = cast(Dict[str, Dict[str, Any]], self.interfaces) + nic = None + for iface in interfaces.keys(): + addr = '' + if ip_version == 4: + addr = interfaces[iface].get('ipv4_address', '') + else: + addr = interfaces[iface].get('ipv6_address', '') + if addr: + a = addr.split('/')[0] + if ipaddress.ip_address(a) in ipaddress.ip_network(subnet): + nic = iface + break + return nic + + +class SubnetLookup: + def __init__(self, subnet: str, hostname: str, mtu: str, speed: str): + self.subnet = subnet + self.mtu_map = { + mtu: [hostname] + } + self.speed_map = { + speed: [hostname] + } + + @ property + def host_list(self) -> List[str]: + hosts = [] + for mtu in self.mtu_map: + hosts.extend(self.mtu_map.get(mtu, [])) + return hosts + + def update(self, hostname: str, mtu: str, speed: str) -> None: + if mtu in self.mtu_map and hostname not in self.mtu_map[mtu]: + self.mtu_map[mtu].append(hostname) + else: + self.mtu_map[mtu] = [hostname] + + if speed in self.speed_map and hostname not in self.speed_map[speed]: + self.speed_map[speed].append(hostname) + else: + self.speed_map[speed] = [hostname] + + def __repr__(self) -> str: + return json.dumps({ + "subnet": self.subnet, + "mtu_map": self.mtu_map, + "speed_map": self.speed_map + }) + + +class CephadmCheckDefinition: + def __init__(self, mgr: "CephadmOrchestrator", healthcheck_name: str, description: str, name: str, func: Callable) -> None: + self.mgr = mgr + self.log = logger + self.healthcheck_name = healthcheck_name + self.description = description + self.name = name + self.func = func + + @property + def status(self) -> str: + check_states: Dict[str, str] = {} + # Issuing a get each time, since the value could be set at the CLI + raw_states = self.mgr.get_store('config_checks') + if not raw_states: + self.log.error( + "config_checks setting is not defined - unable to determine healthcheck state") + return "Unknown" + + try: + check_states = json.loads(raw_states) + except json.JSONDecodeError: + self.log.error("Unable to serialize the config_checks settings to JSON") + return "Unavailable" + + return check_states.get(self.name, 'Missing') + + def to_json(self) -> Dict[str, Any]: + return { + "healthcheck_name": self.healthcheck_name, + "description": self.description, + "name": self.name, + "status": self.status, + "valid": True if self.func else False + } + + +class CephadmConfigChecks: + def __init__(self, mgr: "CephadmOrchestrator"): + self.mgr: "CephadmOrchestrator" = mgr + self.health_checks: List[CephadmCheckDefinition] = [ + CephadmCheckDefinition(mgr, "CEPHADM_CHECK_KERNEL_LSM", + "checks SELINUX/Apparmor profiles are consistent across cluster hosts", + "kernel_security", + self._check_kernel_lsm), + CephadmCheckDefinition(mgr, "CEPHADM_CHECK_SUBSCRIPTION", + "checks subscription states are consistent for all cluster hosts", + "os_subscription", + self._check_subscription), + CephadmCheckDefinition(mgr, "CEPHADM_CHECK_PUBLIC_MEMBERSHIP", + "check that all hosts have a NIC on the Ceph public_netork", + "public_network", + self._check_public_network), + CephadmCheckDefinition(mgr, "CEPHADM_CHECK_MTU", + "check that OSD hosts share a common MTU setting", + "osd_mtu_size", + self._check_osd_mtu), + CephadmCheckDefinition(mgr, "CEPHADM_CHECK_LINKSPEED", + "check that OSD hosts share a common linkspeed", + "osd_linkspeed", + self._check_osd_linkspeed), + CephadmCheckDefinition(mgr, "CEPHADM_CHECK_NETWORK_MISSING", + "checks that the cluster/public networks defined exist on the Ceph hosts", + "network_missing", + self._check_network_missing), + CephadmCheckDefinition(mgr, "CEPHADM_CHECK_CEPH_RELEASE", + "check for Ceph version consistency - ceph daemons should be on the same release (unless upgrade is active)", + "ceph_release", + self._check_release_parity), + CephadmCheckDefinition(mgr, "CEPHADM_CHECK_KERNEL_VERSION", + "checks that the MAJ.MIN of the kernel on Ceph hosts is consistent", + "kernel_version", + self._check_kernel_version), + ] + self.log = logger + self.host_facts: Dict[str, HostFacts] = {} + self.subnet_lookup: Dict[str, SubnetLookup] = {} # subnet CIDR -> SubnetLookup Object + self.lsm_to_host: Dict[str, List[str]] = {} + self.subscribed: Dict[str, List[str]] = { + "yes": [], + "no": [], + "unknown": [], + } + self.host_to_role: Dict[str, List[str]] = {} + self.kernel_to_hosts: Dict[str, List[str]] = {} + + self.public_network_list: List[str] = [] + self.cluster_network_list: List[str] = [] + self.health_check_raised = False + self.active_checks: List[str] = [] # checks enabled and executed + self.skipped_checks: List[str] = [] # checks enabled, but skipped due to a pre-req failure + + raw_checks = self.mgr.get_store('config_checks') + if not raw_checks: + # doesn't exist, so seed the checks + self.seed_config_checks() + else: + # setting is there, so ensure there is an entry for each of the checks that + # this module supports (account for upgrades/changes) + try: + config_checks = json.loads(raw_checks) + except json.JSONDecodeError: + self.log.error("Unable to serialize config_checks config. Reset to defaults") + self.seed_config_checks() + else: + # Ensure the config_checks setting is consistent with this module + from_config = set(config_checks.keys()) + from_module = set([c.name for c in self.health_checks]) + old_checks = from_config.difference(from_module) + new_checks = from_module.difference(from_config) + + if old_checks: + self.log.debug(f"old checks being removed from config_checks: {old_checks}") + for i in old_checks: + del config_checks[i] + if new_checks: + self.log.debug(f"new checks being added to config_checks: {new_checks}") + for i in new_checks: + config_checks[i] = 'enabled' + + if old_checks or new_checks: + self.log.info( + f"config_checks updated: {len(old_checks)} removed, {len(new_checks)} added") + self.mgr.set_store('config_checks', json.dumps(config_checks)) + else: + self.log.debug("config_checks match module definition") + + def lookup_check(self, key_value: str, key_name: str = 'name') -> Optional[CephadmCheckDefinition]: + + for c in self.health_checks: + if getattr(c, key_name) == key_value: + return c + return None + + @property + def defined_checks(self) -> int: + return len(self.health_checks) + + @property + def active_checks_count(self) -> int: + return len(self.active_checks) + + def seed_config_checks(self) -> None: + defaults = {check.name: 'enabled' for check in self.health_checks} + self.mgr.set_store('config_checks', json.dumps(defaults)) + + @property + def skipped_checks_count(self) -> int: + return len(self.skipped_checks) + + def to_json(self) -> List[Dict[str, str]]: + return [check.to_json() for check in self.health_checks] + + def load_network_config(self) -> None: + ret, out, _err = self.mgr.check_mon_command({ + 'prefix': 'config dump', + 'format': 'json' + }) + assert ret == 0 + js = json.loads(out) + for item in js: + if item['name'] == "cluster_network": + self.cluster_network_list = item['value'].strip().split(',') + if item['name'] == "public_network": + self.public_network_list = item['value'].strip().split(',') + + self.log.debug(f"public networks {self.public_network_list}") + self.log.debug(f"cluster networks {self.cluster_network_list}") + + def _update_subnet(self, subnet: str, hostname: str, nic: Dict[str, Any]) -> None: + mtu = nic.get('mtu', None) + speed = nic.get('speed', None) + if not mtu or not speed: + return + + this_subnet = self.subnet_lookup.get(subnet, None) + if this_subnet: + this_subnet.update(hostname, mtu, speed) + else: + self.subnet_lookup[subnet] = SubnetLookup(subnet, hostname, mtu, speed) + + def _update_subnet_lookups(self, hostname: str, devname: str, nic: Dict[str, Any]) -> None: + if nic['ipv4_address']: + try: + iface4 = ipaddress.IPv4Interface(nic['ipv4_address']) + subnet = str(iface4.network) + except ipaddress.AddressValueError as e: + self.log.exception(f"Invalid network on {hostname}, interface {devname} : {str(e)}") + else: + self._update_subnet(subnet, hostname, nic) + + if nic['ipv6_address']: + try: + iface6 = ipaddress.IPv6Interface(nic['ipv6_address']) + subnet = str(iface6.network) + except ipaddress.AddressValueError as e: + self.log.exception(f"Invalid network on {hostname}, interface {devname} : {str(e)}") + else: + self._update_subnet(subnet, hostname, nic) + + def hosts_with_role(self, role: str) -> List[str]: + host_list = [] + for hostname, roles in self.host_to_role.items(): + if role in roles: + host_list.append(hostname) + return host_list + + def reset(self) -> None: + self.subnet_lookup.clear() + self.lsm_to_host.clear() + self.subscribed['yes'] = [] + self.subscribed['no'] = [] + self.subscribed['unknown'] = [] + self.host_to_role.clear() + self.kernel_to_hosts.clear() + + def _get_majority(self, data: Dict[str, List[str]]) -> Tuple[str, int]: + assert isinstance(data, dict) + + majority_key = '' + majority_count = 0 + for key in data: + if len(data[key]) > majority_count: + majority_count = len(data[key]) + majority_key = key + return majority_key, majority_count + + def get_ceph_metadata(self) -> Dict[str, Optional[Dict[str, str]]]: + """Build a map of service -> service metadata""" + service_map: Dict[str, Optional[Dict[str, str]]] = {} + + for server in self.mgr.list_servers(): + for service in cast(List[ServiceInfoT], server.get('services', [])): + if service: + service_map.update( + { + f"{service['type']}.{service['id']}": + self.mgr.get_metadata(service['type'], service['id']) + } + ) + return service_map + + def _check_kernel_lsm(self) -> None: + if len(self.lsm_to_host.keys()) > 1: + + majority_hosts_ptr, majority_hosts_count = self._get_majority(self.lsm_to_host) + lsm_copy = self.lsm_to_host.copy() + del lsm_copy[majority_hosts_ptr] + details = [] + for lsm_key in lsm_copy.keys(): + for host in lsm_copy[lsm_key]: + details.append( + f"{host} has inconsistent KSM settings compared to the " + f"majority of hosts({majority_hosts_count}) in the cluster") + host_sfx = 's' if len(details) > 1 else '' + self.mgr.health_checks['CEPHADM_CHECK_KERNEL_LSM'] = { + 'severity': 'warning', + 'summary': f"Kernel Security Module (SELinux/AppArmor) is inconsistent for " + f"{len(details)} host{host_sfx}", + 'count': len(details), + 'detail': details, + } + self.health_check_raised = True + else: + self.mgr.health_checks.pop('CEPHADM_CHECK_KERNEL_LSM', None) + + def _check_subscription(self) -> None: + if len(self.subscribed['yes']) > 0 and len(self.subscribed['no']) > 0: + # inconsistent subscription states - CEPHADM_CHECK_SUBSCRIPTION + details = [] + for host in self.subscribed['no']: + details.append(f"{host} does not have an active subscription") + self.mgr.health_checks['CEPHADM_CHECK_SUBSCRIPTION'] = { + 'severity': 'warning', + 'summary': f"Support subscriptions inactive on {len(details)} host(s)" + f"({len(self.subscribed['yes'])} subscriptions active)", + 'count': len(details), + 'detail': details, + } + self.health_check_raised = True + else: + self.mgr.health_checks.pop('CEPHADM_CHECK_SUBSCRIPTION', None) + + def _check_public_network(self) -> None: + hosts_remaining: List[str] = list(self.mgr.cache.facts.keys()) + hosts_removed: List[str] = [] + self.log.debug(f"checking public network membership for: {hosts_remaining}") + + for p_net in self.public_network_list: + self.log.debug(f"checking network {p_net}") + subnet_data = self.subnet_lookup.get(p_net, None) + self.log.debug(f"subnet data - {subnet_data}") + + if subnet_data: + hosts_in_subnet = subnet_data.host_list + for host in hosts_in_subnet: + if host in hosts_remaining: + hosts_remaining.remove(host) + hosts_removed.append(host) + else: + if host not in hosts_removed: + self.log.debug(f"host={host}, subnet={p_net}") + self.log.exception( + "Host listed for a subnet but not present in the host facts?") + + # Ideally all hosts will have been removed since they have an IP on at least + # one of the public networks + if hosts_remaining: + if len(hosts_remaining) != len(self.mgr.cache.facts): + # public network is visible on some hosts + details = [ + f"{host} does not have an interface on any public network" for host in hosts_remaining] + + self.mgr.health_checks['CEPHADM_CHECK_PUBLIC_MEMBERSHIP'] = { + 'severity': 'warning', + 'summary': f"Public network(s) is not directly accessible from {len(hosts_remaining)} " + "cluster hosts", + 'count': len(details), + 'detail': details, + } + self.health_check_raised = True + else: + self.mgr.health_checks.pop('CEPHADM_CHECK_PUBLIC_MEMBERSHIP', None) + + def _check_osd_mtu(self) -> None: + osd_hosts = set(self.hosts_with_role('osd')) + osd_network_list = self.cluster_network_list or self.public_network_list + mtu_errors: List[str] = [] + + for osd_net in osd_network_list: + subnet_data = self.subnet_lookup.get(osd_net, None) + + if subnet_data: + + self.log.debug(f"processing mtu map : {json.dumps(subnet_data.mtu_map)}") + mtu_count = {} + max_hosts = 0 + mtu_ptr = '' + diffs = {} + for mtu, host_list in subnet_data.mtu_map.items(): + mtu_hosts = set(host_list) + mtu_count[mtu] = len(mtu_hosts) + errors = osd_hosts.difference(mtu_hosts) + if errors: + diffs[mtu] = errors + if len(errors) > max_hosts: + mtu_ptr = mtu + + if diffs: + self.log.debug("MTU problems detected") + self.log.debug(f"most hosts using {mtu_ptr}") + mtu_copy = subnet_data.mtu_map.copy() + del mtu_copy[mtu_ptr] + for bad_mtu in mtu_copy: + for h in mtu_copy[bad_mtu]: + host = HostFacts() + host.load_facts(self.mgr.cache.facts[h]) + mtu_errors.append( + f"host {h}({host.subnet_to_nic(osd_net)}) is using MTU " + f"{bad_mtu} on {osd_net}, NICs on other hosts use {mtu_ptr}") + + if mtu_errors: + self.mgr.health_checks['CEPHADM_CHECK_MTU'] = { + 'severity': 'warning', + 'summary': f"MTU setting inconsistent on osd network NICs on {len(mtu_errors)} host(s)", + 'count': len(mtu_errors), + 'detail': mtu_errors, + } + self.health_check_raised = True + else: + self.mgr.health_checks.pop('CEPHADM_CHECK_MTU', None) + + def _check_osd_linkspeed(self) -> None: + osd_hosts = set(self.hosts_with_role('osd')) + osd_network_list = self.cluster_network_list or self.public_network_list + + linkspeed_errors = [] + + for osd_net in osd_network_list: + subnet_data = self.subnet_lookup.get(osd_net, None) + + if subnet_data: + + self.log.debug(f"processing subnet : {subnet_data}") + + speed_count = {} + max_hosts = 0 + speed_ptr = '' + diffs = {} + for speed, host_list in subnet_data.speed_map.items(): + speed_hosts = set(host_list) + speed_count[speed] = len(speed_hosts) + errors = osd_hosts.difference(speed_hosts) + if errors: + diffs[speed] = errors + if len(errors) > max_hosts: + speed_ptr = speed + + if diffs: + self.log.debug("linkspeed issue(s) detected") + self.log.debug(f"most hosts using {speed_ptr}") + speed_copy = subnet_data.speed_map.copy() + del speed_copy[speed_ptr] + for bad_speed in speed_copy: + if bad_speed > speed_ptr: + # skip speed is better than most...it can stay! + continue + for h in speed_copy[bad_speed]: + host = HostFacts() + host.load_facts(self.mgr.cache.facts[h]) + linkspeed_errors.append( + f"host {h}({host.subnet_to_nic(osd_net)}) has linkspeed of " + f"{bad_speed} on {osd_net}, NICs on other hosts use {speed_ptr}") + + if linkspeed_errors: + self.mgr.health_checks['CEPHADM_CHECK_LINKSPEED'] = { + 'severity': 'warning', + 'summary': "Link speed is inconsistent on osd network NICs for " + f"{len(linkspeed_errors)} host(s)", + 'count': len(linkspeed_errors), + 'detail': linkspeed_errors, + } + self.health_check_raised = True + else: + self.mgr.health_checks.pop('CEPHADM_CHECK_LINKSPEED', None) + + def _check_network_missing(self) -> None: + all_networks = self.public_network_list.copy() + all_networks.extend(self.cluster_network_list) + + missing_networks = [] + for subnet in all_networks: + subnet_data = self.subnet_lookup.get(subnet, None) + + if not subnet_data: + missing_networks.append(f"{subnet} not found on any host in the cluster") + self.log.warning( + f"Network {subnet} has been defined, but is not present on any host") + + if missing_networks: + net_sfx = 's' if len(missing_networks) > 1 else '' + self.mgr.health_checks['CEPHADM_CHECK_NETWORK_MISSING'] = { + 'severity': 'warning', + 'summary': f"Public/cluster network{net_sfx} defined, but can not be found on " + "any host", + 'count': len(missing_networks), + 'detail': missing_networks, + } + self.health_check_raised = True + else: + self.mgr.health_checks.pop('CEPHADM_CHECK_NETWORK_MISSING', None) + + def _check_release_parity(self) -> None: + upgrade_status = self.mgr.upgrade.upgrade_status() + if upgrade_status.in_progress: + # skip version consistency checks during an upgrade cycle + self.skipped_checks.append('ceph_release') + return + + services = self.get_ceph_metadata() + self.log.debug(json.dumps(services)) + version_to_svcs: Dict[str, List[str]] = {} + + for svc in services: + if services[svc]: + metadata = cast(Dict[str, str], services[svc]) + v = metadata.get('ceph_release', '') + if v in version_to_svcs: + version_to_svcs[v].append(svc) + else: + version_to_svcs[v] = [svc] + + if len(version_to_svcs) > 1: + majority_ptr, _majority_count = self._get_majority(version_to_svcs) + ver_copy = version_to_svcs.copy() + del ver_copy[majority_ptr] + details = [] + for v in ver_copy: + for svc in ver_copy[v]: + details.append( + f"{svc} is running {v} (majority of cluster is using {majority_ptr})") + + self.mgr.health_checks['CEPHADM_CHECK_CEPH_RELEASE'] = { + 'severity': 'warning', + 'summary': 'Ceph cluster running mixed ceph releases', + 'count': len(details), + 'detail': details, + } + self.health_check_raised = True + self.log.warning( + f"running with {len(version_to_svcs)} different ceph releases within this cluster") + else: + self.mgr.health_checks.pop('CEPHADM_CHECK_CEPH_RELEASE', None) + + def _check_kernel_version(self) -> None: + if len(self.kernel_to_hosts.keys()) > 1: + majority_hosts_ptr, majority_hosts_count = self._get_majority(self.kernel_to_hosts) + kver_copy = self.kernel_to_hosts.copy() + del kver_copy[majority_hosts_ptr] + details = [] + for k in kver_copy: + for h in kver_copy[k]: + details.append( + f"host {h} running kernel {k}, majority of hosts({majority_hosts_count}) " + f"running {majority_hosts_ptr}") + + self.log.warning("mixed kernel versions detected") + self.mgr.health_checks['CEPHADM_CHECK_KERNEL_VERSION'] = { + 'severity': 'warning', + 'summary': f"{len(details)} host(s) running different kernel versions", + 'count': len(details), + 'detail': details, + } + self.health_check_raised = True + else: + self.mgr.health_checks.pop('CEPHADM_CHECK_KERNEL_VERSION', None) + + def _process_hosts(self) -> None: + self.log.debug(f"processing data from {len(self.mgr.cache.facts)} hosts") + for hostname in self.mgr.cache.facts: + host = HostFacts() + host.load_facts(self.mgr.cache.facts[hostname]) + if not host._valid: + self.log.warning(f"skipping {hostname} - incompatible host facts") + continue + + kernel_lsm = cast(Dict[str, str], host.kernel_security) + lsm_desc = kernel_lsm.get('description', '') + if lsm_desc: + if lsm_desc in self.lsm_to_host: + self.lsm_to_host[lsm_desc].append(hostname) + else: + self.lsm_to_host[lsm_desc] = [hostname] + + subscription_state = host.subscribed.lower() if host.subscribed else None + if subscription_state: + self.subscribed[subscription_state].append(hostname) + + interfaces = cast(Dict[str, Dict[str, Any]], host.interfaces) + for name in interfaces.keys(): + if name in ['lo']: + continue + self._update_subnet_lookups(hostname, name, interfaces[name]) + + if host.kernel: + kernel_maj_min = '.'.join(host.kernel.split('.')[0:2]) + if kernel_maj_min in self.kernel_to_hosts: + self.kernel_to_hosts[kernel_maj_min].append(hostname) + else: + self.kernel_to_hosts[kernel_maj_min] = [hostname] + else: + self.log.warning(f"Host gather facts for {hostname} is missing kernel information") + + # NOTE: if daemondescription had systemd enabled state, we could check for systemd 'tampering' + self.host_to_role[hostname] = list(self.mgr.cache.get_daemon_types(hostname)) + + def run_checks(self) -> None: + checks_enabled = self.mgr.get_module_option('config_checks_enabled') + if checks_enabled is not True: + return + + self.reset() + + check_config: Dict[str, str] = {} + checks_raw: Optional[str] = self.mgr.get_store('config_checks') + if checks_raw: + try: + check_config.update(json.loads(checks_raw)) + except json.JSONDecodeError: + self.log.exception( + "mgr/cephadm/config_checks is not JSON serializable - all checks will run") + + # build lookup "maps" by walking the host facts, once + self._process_hosts() + + self.health_check_raised = False + self.active_checks = [] + self.skipped_checks = [] + + # process all healthchecks that are not explcitly disabled + for health_check in self.health_checks: + if check_config.get(health_check.name, '') != 'disabled': + self.active_checks.append(health_check.name) + health_check.func() + + self.mgr.set_health_checks(self.mgr.health_checks) diff --git a/src/pybind/mgr/cephadm/inventory.py b/src/pybind/mgr/cephadm/inventory.py new file mode 100644 index 000000000..92e10ea39 --- /dev/null +++ b/src/pybind/mgr/cephadm/inventory.py @@ -0,0 +1,1019 @@ +import datetime +from copy import copy +import ipaddress +import json +import logging +import socket +from typing import TYPE_CHECKING, Dict, List, Iterator, Optional, Any, Tuple, Set, Mapping, cast, \ + NamedTuple, Type + +import orchestrator +from ceph.deployment import inventory +from ceph.deployment.service_spec import ServiceSpec, PlacementSpec +from ceph.utils import str_to_datetime, datetime_to_str, datetime_now +from orchestrator import OrchestratorError, HostSpec, OrchestratorEvent, service_to_daemon_types + +from .utils import resolve_ip +from .migrations import queue_migrate_nfs_spec + +if TYPE_CHECKING: + from .module import CephadmOrchestrator + + +logger = logging.getLogger(__name__) + +HOST_CACHE_PREFIX = "host." +SPEC_STORE_PREFIX = "spec." + + +class Inventory: + """ + The inventory stores a HostSpec for all hosts persistently. + """ + + def __init__(self, mgr: 'CephadmOrchestrator'): + self.mgr = mgr + adjusted_addrs = False + + def is_valid_ip(ip: str) -> bool: + try: + ipaddress.ip_address(ip) + return True + except ValueError: + return False + + # load inventory + i = self.mgr.get_store('inventory') + if i: + self._inventory: Dict[str, dict] = json.loads(i) + # handle old clusters missing 'hostname' key from hostspec + for k, v in self._inventory.items(): + if 'hostname' not in v: + v['hostname'] = k + + # convert legacy non-IP addr? + if is_valid_ip(str(v.get('addr'))): + continue + if len(self._inventory) > 1: + if k == socket.gethostname(): + # Never try to resolve our own host! This is + # fraught and can lead to either a loopback + # address (due to podman's futzing with + # /etc/hosts) or a private IP based on the CNI + # configuration. Instead, wait until the mgr + # fails over to another host and let them resolve + # this host. + continue + ip = resolve_ip(cast(str, v.get('addr'))) + else: + # we only have 1 node in the cluster, so we can't + # rely on another host doing the lookup. use the + # IP the mgr binds to. + ip = self.mgr.get_mgr_ip() + if is_valid_ip(ip) and not ip.startswith('127.0.'): + self.mgr.log.info( + f"inventory: adjusted host {v['hostname']} addr '{v['addr']}' -> '{ip}'" + ) + v['addr'] = ip + adjusted_addrs = True + if adjusted_addrs: + self.save() + else: + self._inventory = dict() + logger.debug('Loaded inventory %s' % self._inventory) + + def keys(self) -> List[str]: + return list(self._inventory.keys()) + + def __contains__(self, host: str) -> bool: + return host in self._inventory + + def assert_host(self, host: str) -> None: + if host not in self._inventory: + raise OrchestratorError('host %s does not exist' % host) + + def add_host(self, spec: HostSpec) -> None: + if spec.hostname in self._inventory: + # addr + if self.get_addr(spec.hostname) != spec.addr: + self.set_addr(spec.hostname, spec.addr) + # labels + for label in spec.labels: + self.add_label(spec.hostname, label) + else: + self._inventory[spec.hostname] = spec.to_json() + self.save() + + def rm_host(self, host: str) -> None: + self.assert_host(host) + del self._inventory[host] + self.save() + + def set_addr(self, host: str, addr: str) -> None: + self.assert_host(host) + self._inventory[host]['addr'] = addr + self.save() + + def add_label(self, host: str, label: str) -> None: + self.assert_host(host) + + if 'labels' not in self._inventory[host]: + self._inventory[host]['labels'] = list() + if label not in self._inventory[host]['labels']: + self._inventory[host]['labels'].append(label) + self.save() + + def rm_label(self, host: str, label: str) -> None: + self.assert_host(host) + + if 'labels' not in self._inventory[host]: + self._inventory[host]['labels'] = list() + if label in self._inventory[host]['labels']: + self._inventory[host]['labels'].remove(label) + self.save() + + def has_label(self, host: str, label: str) -> bool: + return ( + host in self._inventory + and label in self._inventory[host].get('labels', []) + ) + + def get_addr(self, host: str) -> str: + self.assert_host(host) + return self._inventory[host].get('addr', host) + + def spec_from_dict(self, info: dict) -> HostSpec: + hostname = info['hostname'] + return HostSpec( + hostname, + addr=info.get('addr', hostname), + labels=info.get('labels', []), + status='Offline' if hostname in self.mgr.offline_hosts else info.get('status', ''), + ) + + def all_specs(self) -> List[HostSpec]: + return list(map(self.spec_from_dict, self._inventory.values())) + + def get_host_with_state(self, state: str = "") -> List[str]: + """return a list of host names in a specific state""" + return [h for h in self._inventory if self._inventory[h].get("status", "").lower() == state] + + def save(self) -> None: + self.mgr.set_store('inventory', json.dumps(self._inventory)) + + +class SpecDescription(NamedTuple): + spec: ServiceSpec + rank_map: Optional[Dict[int, Dict[int, Optional[str]]]] + created: datetime.datetime + deleted: Optional[datetime.datetime] + + +class SpecStore(): + def __init__(self, mgr): + # type: (CephadmOrchestrator) -> None + self.mgr = mgr + self._specs = {} # type: Dict[str, ServiceSpec] + # service_name -> rank -> gen -> daemon_id + self._rank_maps = {} # type: Dict[str, Dict[int, Dict[int, Optional[str]]]] + self.spec_created = {} # type: Dict[str, datetime.datetime] + self.spec_deleted = {} # type: Dict[str, datetime.datetime] + self.spec_preview = {} # type: Dict[str, ServiceSpec] + + @property + def all_specs(self) -> Mapping[str, ServiceSpec]: + """ + returns active and deleted specs. Returns read-only dict. + """ + return self._specs + + def __contains__(self, name: str) -> bool: + return name in self._specs + + def __getitem__(self, name: str) -> SpecDescription: + if name not in self._specs: + raise OrchestratorError(f'Service {name} not found.') + return SpecDescription(self._specs[name], + self._rank_maps.get(name), + self.spec_created[name], + self.spec_deleted.get(name, None)) + + @property + def active_specs(self) -> Mapping[str, ServiceSpec]: + return {k: v for k, v in self._specs.items() if k not in self.spec_deleted} + + def load(self): + # type: () -> None + for k, v in self.mgr.get_store_prefix(SPEC_STORE_PREFIX).items(): + service_name = k[len(SPEC_STORE_PREFIX):] + try: + j = cast(Dict[str, dict], json.loads(v)) + if ( + (self.mgr.migration_current or 0) < 3 + and j['spec'].get('service_type') == 'nfs' + ): + self.mgr.log.debug(f'found legacy nfs spec {j}') + queue_migrate_nfs_spec(self.mgr, j) + spec = ServiceSpec.from_json(j['spec']) + created = str_to_datetime(cast(str, j['created'])) + self._specs[service_name] = spec + self.spec_created[service_name] = created + + if 'deleted' in j: + deleted = str_to_datetime(cast(str, j['deleted'])) + self.spec_deleted[service_name] = deleted + + if 'rank_map' in j and isinstance(j['rank_map'], dict): + self._rank_maps[service_name] = {} + for rank_str, m in j['rank_map'].items(): + try: + rank = int(rank_str) + except ValueError: + logger.exception(f"failed to parse rank in {j['rank_map']}") + continue + if isinstance(m, dict): + self._rank_maps[service_name][rank] = {} + for gen_str, name in m.items(): + try: + gen = int(gen_str) + except ValueError: + logger.exception(f"failed to parse gen in {j['rank_map']}") + continue + if isinstance(name, str) or m is None: + self._rank_maps[service_name][rank][gen] = name + + self.mgr.log.debug('SpecStore: loaded spec for %s' % ( + service_name)) + except Exception as e: + self.mgr.log.warning('unable to load spec for %s: %s' % ( + service_name, e)) + pass + + def save( + self, + spec: ServiceSpec, + update_create: bool = True, + ) -> None: + name = spec.service_name() + if spec.preview_only: + self.spec_preview[name] = spec + return None + self._specs[name] = spec + + if update_create: + self.spec_created[name] = datetime_now() + self._save(name) + + def save_rank_map(self, + name: str, + rank_map: Dict[int, Dict[int, Optional[str]]]) -> None: + self._rank_maps[name] = rank_map + self._save(name) + + def _save(self, name: str) -> None: + data: Dict[str, Any] = { + 'spec': self._specs[name].to_json(), + 'created': datetime_to_str(self.spec_created[name]), + } + if name in self._rank_maps: + data['rank_map'] = self._rank_maps[name] + if name in self.spec_deleted: + data['deleted'] = datetime_to_str(self.spec_deleted[name]) + + self.mgr.set_store( + SPEC_STORE_PREFIX + name, + json.dumps(data, sort_keys=True), + ) + self.mgr.events.for_service(self._specs[name], + OrchestratorEvent.INFO, + 'service was created') + + def rm(self, service_name: str) -> bool: + if service_name not in self._specs: + return False + + if self._specs[service_name].preview_only: + self.finally_rm(service_name) + return True + + self.spec_deleted[service_name] = datetime_now() + self.save(self._specs[service_name], update_create=False) + return True + + def finally_rm(self, service_name): + # type: (str) -> bool + found = service_name in self._specs + if found: + del self._specs[service_name] + if service_name in self._rank_maps: + del self._rank_maps[service_name] + del self.spec_created[service_name] + if service_name in self.spec_deleted: + del self.spec_deleted[service_name] + self.mgr.set_store(SPEC_STORE_PREFIX + service_name, None) + return found + + def get_created(self, spec: ServiceSpec) -> Optional[datetime.datetime]: + return self.spec_created.get(spec.service_name()) + + +class ClientKeyringSpec(object): + """ + A client keyring file that we should maintain + """ + + def __init__( + self, + entity: str, + placement: PlacementSpec, + mode: Optional[int] = None, + uid: Optional[int] = None, + gid: Optional[int] = None, + ) -> None: + self.entity = entity + self.placement = placement + self.mode = mode or 0o600 + self.uid = uid or 0 + self.gid = gid or 0 + + def validate(self) -> None: + pass + + def to_json(self) -> Dict[str, Any]: + return { + 'entity': self.entity, + 'placement': self.placement.to_json(), + 'mode': self.mode, + 'uid': self.uid, + 'gid': self.gid, + } + + @property + def path(self) -> str: + return f'/etc/ceph/ceph.{self.entity}.keyring' + + @classmethod + def from_json(cls: Type, data: dict) -> 'ClientKeyringSpec': + c = data.copy() + if 'placement' in c: + c['placement'] = PlacementSpec.from_json(c['placement']) + _cls = cls(**c) + _cls.validate() + return _cls + + +class ClientKeyringStore(): + """ + Track client keyring files that we are supposed to maintain + """ + + def __init__(self, mgr): + # type: (CephadmOrchestrator) -> None + self.mgr: CephadmOrchestrator = mgr + self.mgr = mgr + self.keys: Dict[str, ClientKeyringSpec] = {} + + def load(self) -> None: + c = self.mgr.get_store('client_keyrings') or b'{}' + j = json.loads(c) + for e, d in j.items(): + self.keys[e] = ClientKeyringSpec.from_json(d) + + def save(self) -> None: + data = { + k: v.to_json() for k, v in self.keys.items() + } + self.mgr.set_store('client_keyrings', json.dumps(data)) + + def update(self, ks: ClientKeyringSpec) -> None: + self.keys[ks.entity] = ks + self.save() + + def rm(self, entity: str) -> None: + if entity in self.keys: + del self.keys[entity] + self.save() + + +class HostCache(): + """ + HostCache stores different things: + + 1. `daemons`: Deployed daemons O(daemons) + + They're part of the configuration nowadays and need to be + persistent. The name "daemon cache" is unfortunately a bit misleading. + Like for example we really need to know where daemons are deployed on + hosts that are offline. + + 2. `devices`: ceph-volume inventory cache O(hosts) + + As soon as this is populated, it becomes more or less read-only. + + 3. `networks`: network interfaces for each host. O(hosts) + + This is needed in order to deploy MONs. As this is mostly read-only. + + 4. `last_client_files` O(hosts) + + Stores the last digest and owner/mode for files we've pushed to /etc/ceph + (ceph.conf or client keyrings). + + 5. `scheduled_daemon_actions`: O(daemons) + + Used to run daemon actions after deploying a daemon. We need to + store it persistently, in order to stay consistent across + MGR failovers. + """ + + def __init__(self, mgr): + # type: (CephadmOrchestrator) -> None + self.mgr: CephadmOrchestrator = mgr + self.daemons = {} # type: Dict[str, Dict[str, orchestrator.DaemonDescription]] + self.last_daemon_update = {} # type: Dict[str, datetime.datetime] + self.devices = {} # type: Dict[str, List[inventory.Device]] + self.facts = {} # type: Dict[str, Dict[str, Any]] + self.last_facts_update = {} # type: Dict[str, datetime.datetime] + self.last_autotune = {} # type: Dict[str, datetime.datetime] + self.osdspec_previews = {} # type: Dict[str, List[Dict[str, Any]]] + self.osdspec_last_applied = {} # type: Dict[str, Dict[str, datetime.datetime]] + self.networks = {} # type: Dict[str, Dict[str, Dict[str, List[str]]]] + self.last_device_update = {} # type: Dict[str, datetime.datetime] + self.last_device_change = {} # type: Dict[str, datetime.datetime] + self.daemon_refresh_queue = [] # type: List[str] + self.device_refresh_queue = [] # type: List[str] + self.osdspec_previews_refresh_queue = [] # type: List[str] + + # host -> daemon name -> dict + self.daemon_config_deps = {} # type: Dict[str, Dict[str, Dict[str,Any]]] + self.last_host_check = {} # type: Dict[str, datetime.datetime] + self.loading_osdspec_preview = set() # type: Set[str] + self.last_client_files: Dict[str, Dict[str, Tuple[str, int, int, int]]] = {} + self.registry_login_queue: Set[str] = set() + + self.scheduled_daemon_actions: Dict[str, Dict[str, str]] = {} + + def load(self): + # type: () -> None + for k, v in self.mgr.get_store_prefix(HOST_CACHE_PREFIX).items(): + host = k[len(HOST_CACHE_PREFIX):] + if host not in self.mgr.inventory: + self.mgr.log.warning('removing stray HostCache host record %s' % ( + host)) + self.mgr.set_store(k, None) + try: + j = json.loads(v) + if 'last_device_update' in j: + self.last_device_update[host] = str_to_datetime(j['last_device_update']) + else: + self.device_refresh_queue.append(host) + if 'last_device_change' in j: + self.last_device_change[host] = str_to_datetime(j['last_device_change']) + # for services, we ignore the persisted last_*_update + # and always trigger a new scrape on mgr restart. + self.daemon_refresh_queue.append(host) + self.daemons[host] = {} + self.osdspec_previews[host] = [] + self.osdspec_last_applied[host] = {} + self.devices[host] = [] + self.networks[host] = {} + self.daemon_config_deps[host] = {} + for name, d in j.get('daemons', {}).items(): + self.daemons[host][name] = \ + orchestrator.DaemonDescription.from_json(d) + for d in j.get('devices', []): + self.devices[host].append(inventory.Device.from_json(d)) + self.networks[host] = j.get('networks_and_interfaces', {}) + self.osdspec_previews[host] = j.get('osdspec_previews', {}) + self.last_client_files[host] = j.get('last_client_files', {}) + for name, ts in j.get('osdspec_last_applied', {}).items(): + self.osdspec_last_applied[host][name] = str_to_datetime(ts) + + for name, d in j.get('daemon_config_deps', {}).items(): + self.daemon_config_deps[host][name] = { + 'deps': d.get('deps', []), + 'last_config': str_to_datetime(d['last_config']), + } + if 'last_host_check' in j: + self.last_host_check[host] = str_to_datetime(j['last_host_check']) + self.registry_login_queue.add(host) + self.scheduled_daemon_actions[host] = j.get('scheduled_daemon_actions', {}) + + self.mgr.log.debug( + 'HostCache.load: host %s has %d daemons, ' + '%d devices, %d networks' % ( + host, len(self.daemons[host]), len(self.devices[host]), + len(self.networks[host]))) + except Exception as e: + self.mgr.log.warning('unable to load cached state for %s: %s' % ( + host, e)) + pass + + def update_host_daemons(self, host, dm): + # type: (str, Dict[str, orchestrator.DaemonDescription]) -> None + self.daemons[host] = dm + self.last_daemon_update[host] = datetime_now() + + def update_host_facts(self, host, facts): + # type: (str, Dict[str, Dict[str, Any]]) -> None + self.facts[host] = facts + self.last_facts_update[host] = datetime_now() + + def update_autotune(self, host: str) -> None: + self.last_autotune[host] = datetime_now() + + def invalidate_autotune(self, host: str) -> None: + if host in self.last_autotune: + del self.last_autotune[host] + + def devices_changed(self, host: str, b: List[inventory.Device]) -> bool: + a = self.devices[host] + if len(a) != len(b): + return True + aj = {d.path: d.to_json() for d in a} + bj = {d.path: d.to_json() for d in b} + if aj != bj: + self.mgr.log.info("Detected new or changed devices on %s" % host) + return True + return False + + def update_host_devices_networks( + self, + host: str, + dls: List[inventory.Device], + nets: Dict[str, Dict[str, List[str]]] + ) -> None: + if ( + host not in self.devices + or host not in self.last_device_change + or self.devices_changed(host, dls) + ): + self.last_device_change[host] = datetime_now() + self.last_device_update[host] = datetime_now() + self.devices[host] = dls + self.networks[host] = nets + + def update_daemon_config_deps(self, host: str, name: str, deps: List[str], stamp: datetime.datetime) -> None: + self.daemon_config_deps[host][name] = { + 'deps': deps, + 'last_config': stamp, + } + + def update_last_host_check(self, host): + # type: (str) -> None + self.last_host_check[host] = datetime_now() + + def update_osdspec_last_applied(self, host, service_name, ts): + # type: (str, str, datetime.datetime) -> None + self.osdspec_last_applied[host][service_name] = ts + + def update_client_file(self, + host: str, + path: str, + digest: str, + mode: int, + uid: int, + gid: int) -> None: + if host not in self.last_client_files: + self.last_client_files[host] = {} + self.last_client_files[host][path] = (digest, mode, uid, gid) + + def removed_client_file(self, host: str, path: str) -> None: + if ( + host in self.last_client_files + and path in self.last_client_files[host] + ): + del self.last_client_files[host][path] + + def prime_empty_host(self, host): + # type: (str) -> None + """ + Install an empty entry for a host + """ + self.daemons[host] = {} + self.devices[host] = [] + self.networks[host] = {} + self.osdspec_previews[host] = [] + self.osdspec_last_applied[host] = {} + self.daemon_config_deps[host] = {} + self.daemon_refresh_queue.append(host) + self.device_refresh_queue.append(host) + self.osdspec_previews_refresh_queue.append(host) + self.registry_login_queue.add(host) + self.last_client_files[host] = {} + + def refresh_all_host_info(self, host): + # type: (str) -> None + + self.last_host_check.pop(host, None) + self.daemon_refresh_queue.append(host) + self.registry_login_queue.add(host) + self.device_refresh_queue.append(host) + self.last_facts_update.pop(host, None) + self.osdspec_previews_refresh_queue.append(host) + self.last_autotune.pop(host, None) + + def invalidate_host_daemons(self, host): + # type: (str) -> None + self.daemon_refresh_queue.append(host) + if host in self.last_daemon_update: + del self.last_daemon_update[host] + self.mgr.event.set() + + def invalidate_host_devices(self, host): + # type: (str) -> None + self.device_refresh_queue.append(host) + if host in self.last_device_update: + del self.last_device_update[host] + self.mgr.event.set() + + def distribute_new_registry_login_info(self) -> None: + self.registry_login_queue = set(self.mgr.inventory.keys()) + + def save_host(self, host: str) -> None: + j: Dict[str, Any] = { + 'daemons': {}, + 'devices': [], + 'osdspec_previews': [], + 'osdspec_last_applied': {}, + 'daemon_config_deps': {}, + } + if host in self.last_daemon_update: + j['last_daemon_update'] = datetime_to_str(self.last_daemon_update[host]) + if host in self.last_device_update: + j['last_device_update'] = datetime_to_str(self.last_device_update[host]) + if host in self.last_device_change: + j['last_device_change'] = datetime_to_str(self.last_device_change[host]) + if host in self.daemons: + for name, dd in self.daemons[host].items(): + j['daemons'][name] = dd.to_json() + if host in self.devices: + for d in self.devices[host]: + j['devices'].append(d.to_json()) + if host in self.networks: + j['networks_and_interfaces'] = self.networks[host] + if host in self.daemon_config_deps: + for name, depi in self.daemon_config_deps[host].items(): + j['daemon_config_deps'][name] = { + 'deps': depi.get('deps', []), + 'last_config': datetime_to_str(depi['last_config']), + } + if host in self.osdspec_previews and self.osdspec_previews[host]: + j['osdspec_previews'] = self.osdspec_previews[host] + if host in self.osdspec_last_applied: + for name, ts in self.osdspec_last_applied[host].items(): + j['osdspec_last_applied'][name] = datetime_to_str(ts) + + if host in self.last_host_check: + j['last_host_check'] = datetime_to_str(self.last_host_check[host]) + + if host in self.last_client_files: + j['last_client_files'] = self.last_client_files[host] + if host in self.scheduled_daemon_actions: + j['scheduled_daemon_actions'] = self.scheduled_daemon_actions[host] + + self.mgr.set_store(HOST_CACHE_PREFIX + host, json.dumps(j)) + + def rm_host(self, host): + # type: (str) -> None + if host in self.daemons: + del self.daemons[host] + if host in self.devices: + del self.devices[host] + if host in self.facts: + del self.facts[host] + if host in self.last_facts_update: + del self.last_facts_update[host] + if host in self.last_autotune: + del self.last_autotune[host] + if host in self.osdspec_previews: + del self.osdspec_previews[host] + if host in self.osdspec_last_applied: + del self.osdspec_last_applied[host] + if host in self.loading_osdspec_preview: + self.loading_osdspec_preview.remove(host) + if host in self.networks: + del self.networks[host] + if host in self.last_daemon_update: + del self.last_daemon_update[host] + if host in self.last_device_update: + del self.last_device_update[host] + if host in self.last_device_change: + del self.last_device_change[host] + if host in self.daemon_config_deps: + del self.daemon_config_deps[host] + if host in self.scheduled_daemon_actions: + del self.scheduled_daemon_actions[host] + if host in self.last_client_files: + del self.last_client_files[host] + self.mgr.set_store(HOST_CACHE_PREFIX + host, None) + + def get_hosts(self): + # type: () -> List[str] + return list(self.daemons) + + def get_facts(self, host: str) -> Dict[str, Any]: + return self.facts.get(host, {}) + + def _get_daemons(self) -> Iterator[orchestrator.DaemonDescription]: + for dm in self.daemons.copy().values(): + yield from dm.values() + + def get_daemons(self): + # type: () -> List[orchestrator.DaemonDescription] + return list(self._get_daemons()) + + def get_daemons_by_host(self, host: str) -> List[orchestrator.DaemonDescription]: + return list(self.daemons.get(host, {}).values()) + + def get_daemon(self, daemon_name: str, host: Optional[str] = None) -> orchestrator.DaemonDescription: + assert not daemon_name.startswith('ha-rgw.') + dds = self.get_daemons_by_host(host) if host else self._get_daemons() + for dd in dds: + if dd.name() == daemon_name: + return dd + + raise orchestrator.OrchestratorError(f'Unable to find {daemon_name} daemon(s)') + + def has_daemon(self, daemon_name: str, host: Optional[str] = None) -> bool: + try: + self.get_daemon(daemon_name, host) + except orchestrator.OrchestratorError: + return False + return True + + def get_daemons_with_volatile_status(self) -> Iterator[Tuple[str, Dict[str, orchestrator.DaemonDescription]]]: + def alter(host: str, dd_orig: orchestrator.DaemonDescription) -> orchestrator.DaemonDescription: + dd = copy(dd_orig) + if host in self.mgr.offline_hosts: + dd.status = orchestrator.DaemonDescriptionStatus.error + dd.status_desc = 'host is offline' + elif self.mgr.inventory._inventory[host].get("status", "").lower() == "maintenance": + # We do not refresh daemons on hosts in maintenance mode, so stored daemon statuses + # could be wrong. We must assume maintenance is working and daemons are stopped + dd.status = orchestrator.DaemonDescriptionStatus.stopped + dd.events = self.mgr.events.get_for_daemon(dd.name()) + return dd + + for host, dm in self.daemons.copy().items(): + yield host, {name: alter(host, d) for name, d in dm.items()} + + def get_daemons_by_service(self, service_name): + # type: (str) -> List[orchestrator.DaemonDescription] + assert not service_name.startswith('keepalived.') + assert not service_name.startswith('haproxy.') + + return list(dd for dd in self._get_daemons() if dd.service_name() == service_name) + + def get_daemons_by_type(self, service_type: str, host: str = '') -> List[orchestrator.DaemonDescription]: + assert service_type not in ['keepalived', 'haproxy'] + + daemons = self.daemons[host].values() if host else self._get_daemons() + + return [d for d in daemons if d.daemon_type in service_to_daemon_types(service_type)] + + def get_daemon_types(self, hostname: str) -> Set[str]: + """Provide a list of the types of daemons on the host""" + return cast(Set[str], {d.daemon_type for d in self.daemons[hostname].values()}) + + def get_daemon_names(self): + # type: () -> List[str] + return [d.name() for d in self._get_daemons()] + + def get_daemon_last_config_deps(self, host: str, name: str) -> Tuple[Optional[List[str]], Optional[datetime.datetime]]: + if host in self.daemon_config_deps: + if name in self.daemon_config_deps[host]: + return self.daemon_config_deps[host][name].get('deps', []), \ + self.daemon_config_deps[host][name].get('last_config', None) + return None, None + + def get_host_client_files(self, host: str) -> Dict[str, Tuple[str, int, int, int]]: + return self.last_client_files.get(host, {}) + + def host_needs_daemon_refresh(self, host): + # type: (str) -> bool + if host in self.mgr.offline_hosts: + logger.debug(f'Host "{host}" marked as offline. Skipping daemon refresh') + return False + if host in self.daemon_refresh_queue: + self.daemon_refresh_queue.remove(host) + return True + cutoff = datetime_now() - datetime.timedelta( + seconds=self.mgr.daemon_cache_timeout) + if host not in self.last_daemon_update or self.last_daemon_update[host] < cutoff: + return True + return False + + def host_needs_facts_refresh(self, host): + # type: (str) -> bool + if host in self.mgr.offline_hosts: + logger.debug(f'Host "{host}" marked as offline. Skipping gather facts refresh') + return False + cutoff = datetime_now() - datetime.timedelta( + seconds=self.mgr.facts_cache_timeout) + if host not in self.last_facts_update or self.last_facts_update[host] < cutoff: + return True + return False + + def host_needs_autotune_memory(self, host): + # type: (str) -> bool + if host in self.mgr.offline_hosts: + logger.debug(f'Host "{host}" marked as offline. Skipping autotune') + return False + cutoff = datetime_now() - datetime.timedelta( + seconds=self.mgr.autotune_interval) + if host not in self.last_autotune or self.last_autotune[host] < cutoff: + return True + return False + + def host_had_daemon_refresh(self, host: str) -> bool: + """ + ... at least once. + """ + if host in self.last_daemon_update: + return True + if host not in self.daemons: + return False + return bool(self.daemons[host]) + + def host_needs_device_refresh(self, host): + # type: (str) -> bool + if host in self.mgr.offline_hosts: + logger.debug(f'Host "{host}" marked as offline. Skipping device refresh') + return False + if host in self.device_refresh_queue: + self.device_refresh_queue.remove(host) + return True + cutoff = datetime_now() - datetime.timedelta( + seconds=self.mgr.device_cache_timeout) + if host not in self.last_device_update or self.last_device_update[host] < cutoff: + return True + return False + + def host_needs_osdspec_preview_refresh(self, host: str) -> bool: + if host in self.mgr.offline_hosts: + logger.debug(f'Host "{host}" marked as offline. Skipping osdspec preview refresh') + return False + if host in self.osdspec_previews_refresh_queue: + self.osdspec_previews_refresh_queue.remove(host) + return True + # Since this is dependent on other factors (device and spec) this does not need + # to be updated periodically. + return False + + def host_needs_check(self, host): + # type: (str) -> bool + cutoff = datetime_now() - datetime.timedelta( + seconds=self.mgr.host_check_interval) + return host not in self.last_host_check or self.last_host_check[host] < cutoff + + def osdspec_needs_apply(self, host: str, spec: ServiceSpec) -> bool: + if ( + host not in self.devices + or host not in self.last_device_change + or host not in self.last_device_update + or host not in self.osdspec_last_applied + or spec.service_name() not in self.osdspec_last_applied[host] + ): + return True + created = self.mgr.spec_store.get_created(spec) + if not created or created > self.last_device_change[host]: + return True + return self.osdspec_last_applied[host][spec.service_name()] < self.last_device_change[host] + + def host_needs_registry_login(self, host: str) -> bool: + if host in self.mgr.offline_hosts: + return False + if host in self.registry_login_queue: + self.registry_login_queue.remove(host) + return True + return False + + def add_daemon(self, host, dd): + # type: (str, orchestrator.DaemonDescription) -> None + assert host in self.daemons + self.daemons[host][dd.name()] = dd + + def rm_daemon(self, host: str, name: str) -> None: + assert not name.startswith('ha-rgw.') + + if host in self.daemons: + if name in self.daemons[host]: + del self.daemons[host][name] + + def daemon_cache_filled(self) -> bool: + """ + i.e. we have checked the daemons for each hosts at least once. + excluding offline hosts. + + We're not checking for `host_needs_daemon_refresh`, as this might never be + False for all hosts. + """ + return all((self.host_had_daemon_refresh(h) or h in self.mgr.offline_hosts) + for h in self.get_hosts()) + + def schedule_daemon_action(self, host: str, daemon_name: str, action: str) -> None: + assert not daemon_name.startswith('ha-rgw.') + + priorities = { + 'start': 1, + 'restart': 2, + 'reconfig': 3, + 'redeploy': 4, + 'stop': 5, + } + existing_action = self.scheduled_daemon_actions.get(host, {}).get(daemon_name, None) + if existing_action and priorities[existing_action] > priorities[action]: + logger.debug( + f'skipping {action}ing {daemon_name}, cause {existing_action} already scheduled.') + return + + if host not in self.scheduled_daemon_actions: + self.scheduled_daemon_actions[host] = {} + self.scheduled_daemon_actions[host][daemon_name] = action + + def rm_scheduled_daemon_action(self, host: str, daemon_name: str) -> bool: + found = False + if host in self.scheduled_daemon_actions: + if daemon_name in self.scheduled_daemon_actions[host]: + del self.scheduled_daemon_actions[host][daemon_name] + found = True + if not self.scheduled_daemon_actions[host]: + del self.scheduled_daemon_actions[host] + return found + + def get_scheduled_daemon_action(self, host: str, daemon: str) -> Optional[str]: + assert not daemon.startswith('ha-rgw.') + + return self.scheduled_daemon_actions.get(host, {}).get(daemon) + + +class EventStore(): + def __init__(self, mgr): + # type: (CephadmOrchestrator) -> None + self.mgr: CephadmOrchestrator = mgr + self.events = {} # type: Dict[str, List[OrchestratorEvent]] + + def add(self, event: OrchestratorEvent) -> None: + + if event.kind_subject() not in self.events: + self.events[event.kind_subject()] = [event] + + for e in self.events[event.kind_subject()]: + if e.message == event.message: + return + + self.events[event.kind_subject()].append(event) + + # limit to five events for now. + self.events[event.kind_subject()] = self.events[event.kind_subject()][-5:] + + def for_service(self, spec: ServiceSpec, level: str, message: str) -> None: + e = OrchestratorEvent(datetime_now(), 'service', + spec.service_name(), level, message) + self.add(e) + + def from_orch_error(self, e: OrchestratorError) -> None: + if e.event_subject is not None: + self.add(OrchestratorEvent( + datetime_now(), + e.event_subject[0], + e.event_subject[1], + "ERROR", + str(e) + )) + + def for_daemon(self, daemon_name: str, level: str, message: str) -> None: + e = OrchestratorEvent(datetime_now(), 'daemon', daemon_name, level, message) + self.add(e) + + def for_daemon_from_exception(self, daemon_name: str, e: Exception) -> None: + self.for_daemon( + daemon_name, + "ERROR", + str(e) + ) + + def cleanup(self) -> None: + # Needs to be properly done, in case events are persistently stored. + + unknowns: List[str] = [] + daemons = self.mgr.cache.get_daemon_names() + specs = self.mgr.spec_store.all_specs.keys() + for k_s, v in self.events.items(): + kind, subject = k_s.split(':') + if kind == 'service': + if subject not in specs: + unknowns.append(k_s) + elif kind == 'daemon': + if subject not in daemons: + unknowns.append(k_s) + + for k_s in unknowns: + del self.events[k_s] + + def get_for_service(self, name: str) -> List[OrchestratorEvent]: + return self.events.get('service:' + name, []) + + def get_for_daemon(self, name: str) -> List[OrchestratorEvent]: + return self.events.get('daemon:' + name, []) diff --git a/src/pybind/mgr/cephadm/migrations.py b/src/pybind/mgr/cephadm/migrations.py new file mode 100644 index 000000000..f4a3056b2 --- /dev/null +++ b/src/pybind/mgr/cephadm/migrations.py @@ -0,0 +1,333 @@ +import json +import logging +from typing import TYPE_CHECKING, Iterator, Optional, Dict, Any + +from ceph.deployment.service_spec import PlacementSpec, ServiceSpec, HostPlacementSpec +from cephadm.schedule import HostAssignment +import rados + +from mgr_module import NFS_POOL_NAME +from orchestrator import OrchestratorError, DaemonDescription + +if TYPE_CHECKING: + from .module import CephadmOrchestrator + +LAST_MIGRATION = 5 + +logger = logging.getLogger(__name__) + + +class Migrations: + def __init__(self, mgr: "CephadmOrchestrator"): + self.mgr = mgr + + # Why having a global counter, instead of spec versions? + # + # for the first migration: + # The specs don't change in (this) migration. but the scheduler here. + # Adding the version to the specs at this time just felt wrong to me. + # + # And the specs are only another part of cephadm which needs potential upgrades. + # We have the cache, the inventory, the config store, the upgrade (imagine changing the + # upgrade code, while an old upgrade is still in progress), naming of daemons, + # fs-layout of the daemons, etc. + if self.mgr.migration_current is None: + self.set(LAST_MIGRATION) + + v = mgr.get_store('nfs_migration_queue') + self.nfs_migration_queue = json.loads(v) if v else [] + + # for some migrations, we don't need to do anything except for + # incrementing migration_current. + # let's try to shortcut things here. + self.migrate(True) + + def set(self, val: int) -> None: + self.mgr.set_module_option('migration_current', val) + self.mgr.migration_current = val + + def is_migration_ongoing(self) -> bool: + return self.mgr.migration_current != LAST_MIGRATION + + def verify_no_migration(self) -> None: + if self.is_migration_ongoing(): + # this is raised in module.serve() + raise OrchestratorError( + "cephadm migration still ongoing. Please wait, until the migration is complete.") + + def migrate(self, startup: bool = False) -> None: + if self.mgr.migration_current == 0: + if self.migrate_0_1(): + self.set(1) + + if self.mgr.migration_current == 1: + if self.migrate_1_2(): + self.set(2) + + if self.mgr.migration_current == 2 and not startup: + if self.migrate_2_3(): + self.set(3) + + if self.mgr.migration_current == 3: + if self.migrate_3_4(): + self.set(4) + + if self.mgr.migration_current == 4: + if self.migrate_4_5(): + self.set(5) + + def migrate_0_1(self) -> bool: + """ + Migration 0 -> 1 + New scheduler that takes PlacementSpec as the bound and not as recommendation. + I.e. the new scheduler won't suggest any new placements outside of the hosts + specified by label etc. + + Which means, we have to make sure, we're not removing any daemons directly after + upgrading to the new scheduler. + + There is a potential race here: + 1. user updates his spec to remove daemons + 2. mgr gets upgraded to new scheduler, before the old scheduler removed the daemon + 3. now, we're converting the spec to explicit placement, thus reverting (1.) + I think this is ok. + """ + + def interesting_specs() -> Iterator[ServiceSpec]: + for s in self.mgr.spec_store.all_specs.values(): + if s.unmanaged: + continue + p = s.placement + if p is None: + continue + if p.count is None: + continue + if not p.hosts and not p.host_pattern and not p.label: + continue + yield s + + def convert_to_explicit(spec: ServiceSpec) -> None: + existing_daemons = self.mgr.cache.get_daemons_by_service(spec.service_name()) + placements, to_add, to_remove = HostAssignment( + spec=spec, + hosts=self.mgr.inventory.all_specs(), + unreachable_hosts=self.mgr._unreachable_hosts(), + daemons=existing_daemons, + ).place() + + # We have to migrate, only if the new scheduler would remove daemons + if len(placements) >= len(existing_daemons): + return + + def to_hostname(d: DaemonDescription) -> HostPlacementSpec: + if d.hostname in old_hosts: + return old_hosts[d.hostname] + else: + assert d.hostname + return HostPlacementSpec(d.hostname, '', '') + + old_hosts = {h.hostname: h for h in spec.placement.hosts} + new_hosts = [to_hostname(d) for d in existing_daemons] + + new_placement = PlacementSpec( + hosts=new_hosts, + count=spec.placement.count + ) + + new_spec = ServiceSpec.from_json(spec.to_json()) + new_spec.placement = new_placement + + logger.info(f"Migrating {spec.one_line_str()} to explicit placement") + + self.mgr.spec_store.save(new_spec) + + specs = list(interesting_specs()) + if not specs: + return True # nothing to do. shortcut + + if not self.mgr.cache.daemon_cache_filled(): + logger.info("Unable to migrate yet. Daemon Cache still incomplete.") + return False + + for spec in specs: + convert_to_explicit(spec) + + return True + + def migrate_1_2(self) -> bool: + """ + After 15.2.4, we unified some service IDs: MONs, MGRs etc no longer have a service id. + Which means, the service names changed: + + mon.foo -> mon + mgr.foo -> mgr + + This fixes the data structure consistency + """ + bad_specs = {} + for name, spec in self.mgr.spec_store.all_specs.items(): + if name != spec.service_name(): + bad_specs[name] = (spec.service_name(), spec) + + for old, (new, old_spec) in bad_specs.items(): + if new not in self.mgr.spec_store.all_specs: + spec = old_spec + else: + spec = self.mgr.spec_store.all_specs[new] + spec.unmanaged = True + self.mgr.spec_store.save(spec) + self.mgr.spec_store.finally_rm(old) + + return True + + def migrate_2_3(self) -> bool: + if self.nfs_migration_queue: + from nfs.cluster import create_ganesha_pool + + create_ganesha_pool(self.mgr) + for service_id, pool, ns in self.nfs_migration_queue: + if pool != '.nfs': + self.migrate_nfs_spec(service_id, pool, ns) + self.nfs_migration_queue = [] + self.mgr.log.info('Done migrating all NFS services') + return True + + def migrate_nfs_spec(self, service_id: str, pool: str, ns: Optional[str]) -> None: + renamed = False + if service_id.startswith('ganesha-'): + service_id = service_id[8:] + renamed = True + + self.mgr.log.info( + f'Migrating nfs.{service_id} from legacy pool {pool} namespace {ns}' + ) + + # read exports + ioctx = self.mgr.rados.open_ioctx(pool) + if ns is not None: + ioctx.set_namespace(ns) + object_iterator = ioctx.list_objects() + exports = [] + while True: + try: + obj = object_iterator.__next__() + if obj.key.startswith('export-'): + self.mgr.log.debug(f'reading {obj.key}') + exports.append(obj.read().decode()) + except StopIteration: + break + self.mgr.log.info(f'Found {len(exports)} exports for legacy nfs.{service_id}') + + # copy grace file + if service_id != ns: + try: + grace = ioctx.read("grace") + new_ioctx = self.mgr.rados.open_ioctx(NFS_POOL_NAME) + new_ioctx.set_namespace(service_id) + new_ioctx.write_full("grace", grace) + self.mgr.log.info('Migrated nfs-ganesha grace file') + except rados.ObjectNotFound: + self.mgr.log.debug('failed to read old grace file; skipping') + + if renamed and f'nfs.ganesha-{service_id}' in self.mgr.spec_store: + # rename from nfs.ganesha-* to nfs.*. This will destroy old daemons and + # deploy new ones. + self.mgr.log.info(f'Replacing nfs.ganesha-{service_id} with nfs.{service_id}') + spec = self.mgr.spec_store[f'nfs.ganesha-{service_id}'].spec + self.mgr.spec_store.rm(f'nfs.ganesha-{service_id}') + spec.service_id = service_id + self.mgr.spec_store.save(spec, True) + + # We have to remove the old daemons here as well, otherwise we'll end up with a port conflict. + daemons = [d.name() + for d in self.mgr.cache.get_daemons_by_service(f'nfs.ganesha-{service_id}')] + self.mgr.log.info(f'Removing old nfs.ganesha-{service_id} daemons {daemons}') + self.mgr.remove_daemons(daemons) + else: + # redeploy all ganesha daemons to ensures that the daemon + # cephx are correct AND container configs are set up properly + daemons = [d.name() for d in self.mgr.cache.get_daemons_by_service(f'nfs.{service_id}')] + self.mgr.log.info(f'Removing old nfs.{service_id} daemons {daemons}') + self.mgr.remove_daemons(daemons) + + # re-save service spec (without pool and namespace properties!) + spec = self.mgr.spec_store[f'nfs.{service_id}'].spec + self.mgr.spec_store.save(spec) + + # import exports + for export in exports: + ex = '' + for line in export.splitlines(): + if ( + line.startswith(' secret_access_key =') + or line.startswith(' user_id =') + ): + continue + ex += line + '\n' + self.mgr.log.debug(f'importing export: {ex}') + ret, out, err = self.mgr.mon_command({ + 'prefix': 'nfs export apply', + 'cluster_id': service_id + }, inbuf=ex) + if ret: + self.mgr.log.warning(f'Failed to migrate export ({ret}): {err}\nExport was:\n{ex}') + self.mgr.log.info(f'Done migrating nfs.{service_id}') + + def migrate_3_4(self) -> bool: + # We can't set any host with the _admin label, but we're + # going to warn when calling `ceph orch host rm...` + if 'client.admin' not in self.mgr.keys.keys: + self.mgr._client_keyring_set( + entity='client.admin', + placement='label:_admin', + ) + return True + + def migrate_4_5(self) -> bool: + registry_url = self.mgr.get_module_option('registry_url') + registry_username = self.mgr.get_module_option('registry_username') + registry_password = self.mgr.get_module_option('registry_password') + if registry_url and registry_username and registry_password: + + registry_credentials = {'url': registry_url, + 'username': registry_username, 'password': registry_password} + self.mgr.set_store('registry_credentials', json.dumps(registry_credentials)) + + self.mgr.set_module_option('registry_url', None) + self.mgr.check_mon_command({ + 'prefix': 'config rm', + 'who': 'mgr', + 'key': 'mgr/cephadm/registry_url', + }) + self.mgr.set_module_option('registry_username', None) + self.mgr.check_mon_command({ + 'prefix': 'config rm', + 'who': 'mgr', + 'key': 'mgr/cephadm/registry_username', + }) + self.mgr.set_module_option('registry_password', None) + self.mgr.check_mon_command({ + 'prefix': 'config rm', + 'who': 'mgr', + 'key': 'mgr/cephadm/registry_password', + }) + + self.mgr.log.info('Done migrating registry login info') + return True + + +def queue_migrate_nfs_spec(mgr: "CephadmOrchestrator", spec_dict: Dict[Any, Any]) -> None: + """ + After 16.2.5 we dropped the NFSServiceSpec pool and namespace properties. + Queue up a migration to process later, once we are sure that RADOS is available + and so on. + """ + service_id = spec_dict['spec']['service_id'] + args = spec_dict['spec'].get('spec', {}) + pool = args.pop('pool', 'nfs-ganesha') + ns = args.pop('namespace', service_id) + queued = mgr.get_store('nfs_migration_queue') or '[]' + ls = json.loads(queued) + ls.append([service_id, pool, ns]) + mgr.set_store('nfs_migration_queue', json.dumps(ls)) + mgr.log.info(f'Queued nfs.{service_id} for migration') diff --git a/src/pybind/mgr/cephadm/module.py b/src/pybind/mgr/cephadm/module.py new file mode 100644 index 000000000..9fc4298a8 --- /dev/null +++ b/src/pybind/mgr/cephadm/module.py @@ -0,0 +1,2974 @@ +import json +import errno +import ipaddress +import logging +import re +import shlex +from collections import defaultdict +from configparser import ConfigParser +from functools import wraps +from tempfile import TemporaryDirectory +from threading import Event + +import string +from typing import List, Dict, Optional, Callable, Tuple, TypeVar, \ + Any, Set, TYPE_CHECKING, cast, NamedTuple, Sequence, Type + +import datetime +import os +import random +import tempfile +import multiprocessing.pool +import subprocess +from prettytable import PrettyTable + +from ceph.deployment import inventory +from ceph.deployment.drive_group import DriveGroupSpec +from ceph.deployment.service_spec import \ + ServiceSpec, PlacementSpec, \ + HostPlacementSpec, IngressSpec, IscsiServiceSpec +from ceph.utils import str_to_datetime, datetime_to_str, datetime_now +from cephadm.serve import CephadmServe +from cephadm.services.cephadmservice import CephadmDaemonDeploySpec + +from mgr_module import MgrModule, HandleCommandResult, Option, NotifyType +from mgr_util import create_self_signed_cert +import secrets +import orchestrator +from orchestrator.module import to_format, Format + +from orchestrator import OrchestratorError, OrchestratorValidationError, HostSpec, \ + CLICommandMeta, DaemonDescription, DaemonDescriptionStatus, handle_orch_error, \ + service_to_daemon_types +from orchestrator._interface import GenericSpec +from orchestrator._interface import daemon_type_to_service + +from . import remotes +from . import utils +from .migrations import Migrations +from .services.cephadmservice import MonService, MgrService, MdsService, RgwService, \ + RbdMirrorService, CrashService, CephadmService, CephfsMirrorService +from .services.ingress import IngressService +from .services.container import CustomContainerService +from .services.iscsi import IscsiService +from .services.nfs import NFSService +from .services.osd import OSDRemovalQueue, OSDService, OSD, NotFoundError +from .services.monitoring import GrafanaService, AlertmanagerService, PrometheusService, \ + NodeExporterService, SNMPGatewayService +from .services.exporter import CephadmExporter, CephadmExporterConfig +from .schedule import HostAssignment +from .inventory import Inventory, SpecStore, HostCache, EventStore, ClientKeyringStore, ClientKeyringSpec +from .upgrade import CephadmUpgrade +from .template import TemplateMgr +from .utils import CEPH_IMAGE_TYPES, RESCHEDULE_FROM_OFFLINE_HOSTS_TYPES, forall_hosts, \ + cephadmNoImage, CEPH_UPGRADE_ORDER +from .configchecks import CephadmConfigChecks +from .offline_watcher import OfflineHostWatcher + +try: + import remoto + # NOTE(mattoliverau) Patch remoto until remoto PR + # (https://github.com/alfredodeza/remoto/pull/56) lands + from distutils.version import StrictVersion + if StrictVersion(remoto.__version__) <= StrictVersion('1.2'): + def remoto_has_connection(self: Any) -> bool: + return self.gateway.hasreceiver() + + from remoto.backends import BaseConnection + BaseConnection.has_connection = remoto_has_connection + import remoto.process +except ImportError as e: + remoto = None + remoto_import_error = str(e) + +logger = logging.getLogger(__name__) + +T = TypeVar('T') + +DEFAULT_SSH_CONFIG = """ +Host * + User root + StrictHostKeyChecking no + UserKnownHostsFile /dev/null + ConnectTimeout=30 +""" + +# Default container images ----------------------------------------------------- +DEFAULT_IMAGE = 'quay.io/ceph/ceph' +DEFAULT_PROMETHEUS_IMAGE = 'quay.io/prometheus/prometheus:v2.33.4' +DEFAULT_NODE_EXPORTER_IMAGE = 'quay.io/prometheus/node-exporter:v1.3.1' +DEFAULT_ALERT_MANAGER_IMAGE = 'quay.io/prometheus/alertmanager:v0.23.0' +DEFAULT_GRAFANA_IMAGE = 'quay.io/ceph/ceph-grafana:8.3.5' +DEFAULT_HAPROXY_IMAGE = 'docker.io/library/haproxy:2.3' +DEFAULT_KEEPALIVED_IMAGE = 'docker.io/arcts/keepalived' +DEFAULT_SNMP_GATEWAY_IMAGE = 'docker.io/maxwo/snmp-notifier:v1.2.1' +# ------------------------------------------------------------------------------ + + +def service_inactive(spec_name: str) -> Callable: + def inner(func: Callable) -> Callable: + @wraps(func) + def wrapper(*args: Any, **kwargs: Any) -> Any: + obj = args[0] + if obj.get_store(f"spec.{spec_name}") is not None: + return 1, "", f"Unable to change configuration of an active service {spec_name}" + return func(*args, **kwargs) + return wrapper + return inner + + +def host_exists(hostname_position: int = 1) -> Callable: + """Check that a hostname exists in the inventory""" + def inner(func: Callable) -> Callable: + @wraps(func) + def wrapper(*args: Any, **kwargs: Any) -> Any: + this = args[0] # self object + hostname = args[hostname_position] + if hostname not in this.cache.get_hosts(): + candidates = ','.join([h for h in this.cache.get_hosts() if h.startswith(hostname)]) + help_msg = f"Did you mean {candidates}?" if candidates else "" + raise OrchestratorError( + f"Cannot find host '{hostname}' in the inventory. {help_msg}") + + return func(*args, **kwargs) + return wrapper + return inner + + +class CephadmOrchestrator(orchestrator.Orchestrator, MgrModule, + metaclass=CLICommandMeta): + + _STORE_HOST_PREFIX = "host" + + instance = None + NOTIFY_TYPES = [NotifyType.mon_map, NotifyType.pg_summary] + NATIVE_OPTIONS = [] # type: List[Any] + MODULE_OPTIONS = [ + Option( + 'ssh_config_file', + type='str', + default=None, + desc='customized SSH config file to connect to managed hosts', + ), + Option( + 'device_cache_timeout', + type='secs', + default=30 * 60, + desc='seconds to cache device inventory', + ), + Option( + 'device_enhanced_scan', + type='bool', + default=False, + desc='Use libstoragemgmt during device scans', + ), + Option( + 'daemon_cache_timeout', + type='secs', + default=10 * 60, + desc='seconds to cache service (daemon) inventory', + ), + Option( + 'facts_cache_timeout', + type='secs', + default=1 * 60, + desc='seconds to cache host facts data', + ), + Option( + 'host_check_interval', + type='secs', + default=10 * 60, + desc='how frequently to perform a host check', + ), + Option( + 'mode', + type='str', + enum_allowed=['root', 'cephadm-package'], + default='root', + desc='mode for remote execution of cephadm', + ), + Option( + 'container_image_base', + default=DEFAULT_IMAGE, + desc='Container image name, without the tag', + runtime=True, + ), + Option( + 'container_image_prometheus', + default=DEFAULT_PROMETHEUS_IMAGE, + desc='Prometheus container image', + ), + Option( + 'container_image_grafana', + default=DEFAULT_GRAFANA_IMAGE, + desc='Prometheus container image', + ), + Option( + 'container_image_alertmanager', + default=DEFAULT_ALERT_MANAGER_IMAGE, + desc='Prometheus container image', + ), + Option( + 'container_image_node_exporter', + default=DEFAULT_NODE_EXPORTER_IMAGE, + desc='Prometheus container image', + ), + Option( + 'container_image_haproxy', + default=DEFAULT_HAPROXY_IMAGE, + desc='HAproxy container image', + ), + Option( + 'container_image_keepalived', + default=DEFAULT_KEEPALIVED_IMAGE, + desc='Keepalived container image', + ), + Option( + 'container_image_snmp_gateway', + default=DEFAULT_SNMP_GATEWAY_IMAGE, + desc='SNMP Gateway container image', + ), + Option( + 'warn_on_stray_hosts', + type='bool', + default=True, + desc='raise a health warning if daemons are detected on a host ' + 'that is not managed by cephadm', + ), + Option( + 'warn_on_stray_daemons', + type='bool', + default=True, + desc='raise a health warning if daemons are detected ' + 'that are not managed by cephadm', + ), + Option( + 'warn_on_failed_host_check', + type='bool', + default=True, + desc='raise a health warning if the host check fails', + ), + Option( + 'log_to_cluster', + type='bool', + default=True, + desc='log to the "cephadm" cluster log channel"', + ), + Option( + 'allow_ptrace', + type='bool', + default=False, + desc='allow SYS_PTRACE capability on ceph containers', + long_desc='The SYS_PTRACE capability is needed to attach to a ' + 'process with gdb or strace. Enabling this options ' + 'can allow debugging daemons that encounter problems ' + 'at runtime.', + ), + Option( + 'container_init', + type='bool', + default=True, + desc='Run podman/docker with `--init`' + ), + Option( + 'prometheus_alerts_path', + type='str', + default='/etc/prometheus/ceph/ceph_default_alerts.yml', + desc='location of alerts to include in prometheus deployments', + ), + Option( + 'migration_current', + type='int', + default=None, + desc='internal - do not modify', + # used to track track spec and other data migrations. + ), + Option( + 'config_dashboard', + type='bool', + default=True, + desc='manage configs like API endpoints in Dashboard.' + ), + Option( + 'manage_etc_ceph_ceph_conf', + type='bool', + default=False, + desc='Manage and own /etc/ceph/ceph.conf on the hosts.', + ), + Option( + 'manage_etc_ceph_ceph_conf_hosts', + type='str', + default='*', + desc='PlacementSpec describing on which hosts to manage /etc/ceph/ceph.conf', + ), + # not used anymore + Option( + 'registry_url', + type='str', + default=None, + desc='Registry url for login purposes. This is not the default registry' + ), + Option( + 'registry_username', + type='str', + default=None, + desc='Custom repository username. Only used for logging into a registry.' + ), + Option( + 'registry_password', + type='str', + default=None, + desc='Custom repository password. Only used for logging into a registry.' + ), + #### + Option( + 'registry_insecure', + type='bool', + default=False, + desc='Registry is to be considered insecure (no TLS available). Only for development purposes.' + ), + Option( + 'use_repo_digest', + type='bool', + default=True, + desc='Automatically convert image tags to image digest. Make sure all daemons use the same image', + ), + Option( + 'config_checks_enabled', + type='bool', + default=False, + desc='Enable or disable the cephadm configuration analysis', + ), + Option( + 'default_registry', + type='str', + default='docker.io', + desc='Search-registry to which we should normalize unqualified image names. ' + 'This is not the default registry', + ), + Option( + 'max_count_per_host', + type='int', + default=10, + desc='max number of daemons per service per host', + ), + Option( + 'autotune_memory_target_ratio', + type='float', + default=.7, + desc='ratio of total system memory to divide amongst autotuned daemons' + ), + Option( + 'autotune_interval', + type='secs', + default=10 * 60, + desc='how frequently to autotune daemon memory' + ), + Option( + 'max_osd_draining_count', + type='int', + default=10, + desc='max number of osds that will be drained simultaneously when osds are removed' + ), + ] + + def __init__(self, *args: Any, **kwargs: Any): + super(CephadmOrchestrator, self).__init__(*args, **kwargs) + self._cluster_fsid: str = self.get('mon_map')['fsid'] + self.last_monmap: Optional[datetime.datetime] = None + + # for serve() + self.run = True + self.event = Event() + + if self.get_store('pause'): + self.paused = True + else: + self.paused = False + + # for mypy which does not run the code + if TYPE_CHECKING: + self.ssh_config_file = None # type: Optional[str] + self.device_cache_timeout = 0 + self.daemon_cache_timeout = 0 + self.facts_cache_timeout = 0 + self.host_check_interval = 0 + self.max_count_per_host = 0 + self.mode = '' + self.container_image_base = '' + self.container_image_prometheus = '' + self.container_image_grafana = '' + self.container_image_alertmanager = '' + self.container_image_node_exporter = '' + self.container_image_haproxy = '' + self.container_image_keepalived = '' + self.container_image_snmp_gateway = '' + self.warn_on_stray_hosts = True + self.warn_on_stray_daemons = True + self.warn_on_failed_host_check = True + self.allow_ptrace = False + self.container_init = True + self.prometheus_alerts_path = '' + self.migration_current: Optional[int] = None + self.config_dashboard = True + self.manage_etc_ceph_ceph_conf = True + self.manage_etc_ceph_ceph_conf_hosts = '*' + self.registry_url: Optional[str] = None + self.registry_username: Optional[str] = None + self.registry_password: Optional[str] = None + self.registry_insecure: bool = False + self.use_repo_digest = True + self.default_registry = '' + self.autotune_memory_target_ratio = 0.0 + self.autotune_interval = 0 + self.apply_spec_fails: List[Tuple[str, str]] = [] + self.max_osd_draining_count = 10 + self.device_enhanced_scan = False + + self._cons: Dict[str, Tuple[remoto.backends.BaseConnection, + remoto.backends.LegacyModuleExecute]] = {} + + self.notify(NotifyType.mon_map, None) + self.config_notify() + + path = self.get_ceph_option('cephadm_path') + try: + assert isinstance(path, str) + with open(path, 'r') as f: + self._cephadm = f.read() + except (IOError, TypeError) as e: + raise RuntimeError("unable to read cephadm at '%s': %s" % ( + path, str(e))) + + self.cephadm_binary_path = self._get_cephadm_binary_path() + + self._worker_pool = multiprocessing.pool.ThreadPool(10) + + self._reconfig_ssh() + + CephadmOrchestrator.instance = self + + self.upgrade = CephadmUpgrade(self) + + self.health_checks: Dict[str, dict] = {} + + self.inventory = Inventory(self) + + self.cache = HostCache(self) + self.cache.load() + + self.to_remove_osds = OSDRemovalQueue(self) + self.to_remove_osds.load_from_store() + + self.spec_store = SpecStore(self) + self.spec_store.load() + + self.keys = ClientKeyringStore(self) + self.keys.load() + + # ensure the host lists are in sync + for h in self.inventory.keys(): + if h not in self.cache.daemons: + self.cache.prime_empty_host(h) + for h in self.cache.get_hosts(): + if h not in self.inventory: + self.cache.rm_host(h) + + # in-memory only. + self.events = EventStore(self) + self.offline_hosts: Set[str] = set() + + self.migration = Migrations(self) + + _service_clses: Sequence[Type[CephadmService]] = [ + OSDService, NFSService, MonService, MgrService, MdsService, + RgwService, RbdMirrorService, GrafanaService, AlertmanagerService, + PrometheusService, NodeExporterService, CrashService, IscsiService, + IngressService, CustomContainerService, CephadmExporter, CephfsMirrorService, + SNMPGatewayService, + ] + + # https://github.com/python/mypy/issues/8993 + self.cephadm_services: Dict[str, CephadmService] = { + cls.TYPE: cls(self) for cls in _service_clses} # type: ignore + + self.mgr_service: MgrService = cast(MgrService, self.cephadm_services['mgr']) + self.osd_service: OSDService = cast(OSDService, self.cephadm_services['osd']) + self.iscsi_service: IscsiService = cast(IscsiService, self.cephadm_services['iscsi']) + + self.template = TemplateMgr(self) + + self.requires_post_actions: Set[str] = set() + self.need_connect_dashboard_rgw = False + + self.config_checker = CephadmConfigChecks(self) + + self.offline_watcher = OfflineHostWatcher(self) + self.offline_watcher.start() + + def shutdown(self) -> None: + self.log.debug('shutdown') + self._worker_pool.close() + self._worker_pool.join() + self.offline_watcher.shutdown() + self.run = False + self.event.set() + + def _get_cephadm_service(self, service_type: str) -> CephadmService: + assert service_type in ServiceSpec.KNOWN_SERVICE_TYPES + return self.cephadm_services[service_type] + + def _get_cephadm_binary_path(self) -> str: + import hashlib + m = hashlib.sha256() + m.update(self._cephadm.encode()) + return f'/var/lib/ceph/{self._cluster_fsid}/cephadm.{m.hexdigest()}' + + def _kick_serve_loop(self) -> None: + self.log.debug('_kick_serve_loop') + self.event.set() + + def serve(self) -> None: + """ + The main loop of cephadm. + + A command handler will typically change the declarative state + of cephadm. This loop will then attempt to apply this new state. + """ + serve = CephadmServe(self) + serve.serve() + + def set_container_image(self, entity: str, image: str) -> None: + self.check_mon_command({ + 'prefix': 'config set', + 'name': 'container_image', + 'value': image, + 'who': entity, + }) + + def config_notify(self) -> None: + """ + This method is called whenever one of our config options is changed. + + TODO: this method should be moved into mgr_module.py + """ + for opt in self.MODULE_OPTIONS: + setattr(self, + opt['name'], # type: ignore + self.get_module_option(opt['name'])) # type: ignore + self.log.debug(' mgr option %s = %s', + opt['name'], getattr(self, opt['name'])) # type: ignore + for opt in self.NATIVE_OPTIONS: + setattr(self, + opt, # type: ignore + self.get_ceph_option(opt)) + self.log.debug(' native option %s = %s', opt, getattr(self, opt)) # type: ignore + + self.event.set() + + def notify(self, notify_type: NotifyType, notify_id: Optional[str]) -> None: + if notify_type == NotifyType.mon_map: + # get monmap mtime so we can refresh configs when mons change + monmap = self.get('mon_map') + self.last_monmap = str_to_datetime(monmap['modified']) + if self.last_monmap and self.last_monmap > datetime_now(): + self.last_monmap = None # just in case clocks are skewed + if getattr(self, 'manage_etc_ceph_ceph_conf', False): + # getattr, due to notify() being called before config_notify() + self._kick_serve_loop() + if notify_type == NotifyType.pg_summary: + self._trigger_osd_removal() + + def _trigger_osd_removal(self) -> None: + remove_queue = self.to_remove_osds.as_osd_ids() + if not remove_queue: + return + data = self.get("osd_stats") + for osd in data.get('osd_stats', []): + if osd.get('num_pgs') == 0: + # if _ANY_ osd that is currently in the queue appears to be empty, + # start the removal process + if int(osd.get('osd')) in remove_queue: + self.log.debug('Found empty osd. Starting removal process') + # if the osd that is now empty is also part of the removal queue + # start the process + self._kick_serve_loop() + + def pause(self) -> None: + if not self.paused: + self.log.info('Paused') + self.set_store('pause', 'true') + self.paused = True + # wake loop so we update the health status + self._kick_serve_loop() + + def resume(self) -> None: + if self.paused: + self.log.info('Resumed') + self.paused = False + self.set_store('pause', None) + # unconditionally wake loop so that 'orch resume' can be used to kick + # cephadm + self._kick_serve_loop() + + def get_unique_name( + self, + daemon_type: str, + host: str, + existing: List[orchestrator.DaemonDescription], + prefix: Optional[str] = None, + forcename: Optional[str] = None, + rank: Optional[int] = None, + rank_generation: Optional[int] = None, + ) -> str: + """ + Generate a unique random service name + """ + suffix = daemon_type not in [ + 'mon', 'crash', + 'prometheus', 'node-exporter', 'grafana', 'alertmanager', + 'container', 'cephadm-exporter', 'snmp-gateway' + ] + if forcename: + if len([d for d in existing if d.daemon_id == forcename]): + raise orchestrator.OrchestratorValidationError( + f'name {daemon_type}.{forcename} already in use') + return forcename + + if '.' in host: + host = host.split('.')[0] + while True: + if prefix: + name = prefix + '.' + else: + name = '' + if rank is not None and rank_generation is not None: + name += f'{rank}.{rank_generation}.' + name += host + if suffix: + name += '.' + ''.join(random.choice(string.ascii_lowercase) + for _ in range(6)) + if len([d for d in existing if d.daemon_id == name]): + if not suffix: + raise orchestrator.OrchestratorValidationError( + f'name {daemon_type}.{name} already in use') + self.log.debug('name %s exists, trying again', name) + continue + return name + + def _reconfig_ssh(self) -> None: + temp_files = [] # type: list + ssh_options = [] # type: List[str] + + # ssh_config + ssh_config_fname = self.ssh_config_file + ssh_config = self.get_store("ssh_config") + if ssh_config is not None or ssh_config_fname is None: + if not ssh_config: + ssh_config = DEFAULT_SSH_CONFIG + f = tempfile.NamedTemporaryFile(prefix='cephadm-conf-') + os.fchmod(f.fileno(), 0o600) + f.write(ssh_config.encode('utf-8')) + f.flush() # make visible to other processes + temp_files += [f] + ssh_config_fname = f.name + if ssh_config_fname: + self.validate_ssh_config_fname(ssh_config_fname) + ssh_options += ['-F', ssh_config_fname] + self.ssh_config = ssh_config + + # identity + ssh_key = self.get_store("ssh_identity_key") + ssh_pub = self.get_store("ssh_identity_pub") + self.ssh_pub = ssh_pub + self.ssh_key = ssh_key + if ssh_key and ssh_pub: + tkey = tempfile.NamedTemporaryFile(prefix='cephadm-identity-') + tkey.write(ssh_key.encode('utf-8')) + os.fchmod(tkey.fileno(), 0o600) + tkey.flush() # make visible to other processes + tpub = open(tkey.name + '.pub', 'w') + os.fchmod(tpub.fileno(), 0o600) + tpub.write(ssh_pub) + tpub.flush() # make visible to other processes + temp_files += [tkey, tpub] + ssh_options += ['-i', tkey.name] + + self._temp_files = temp_files + ssh_options += ['-o', 'ServerAliveInterval=7', '-o', 'ServerAliveCountMax=3'] + self._ssh_options = ' '.join(ssh_options) # type: Optional[str] + + if self.mode == 'root': + self.ssh_user = self.get_store('ssh_user', default='root') + elif self.mode == 'cephadm-package': + self.ssh_user = 'cephadm' + + self._reset_cons() + + def validate_ssh_config_content(self, ssh_config: Optional[str]) -> None: + if ssh_config is None or len(ssh_config.strip()) == 0: + raise OrchestratorValidationError('ssh_config cannot be empty') + # StrictHostKeyChecking is [yes|no] ? + res = re.findall(r'StrictHostKeyChecking\s+.*', ssh_config) + if not res: + raise OrchestratorValidationError('ssh_config requires StrictHostKeyChecking') + for s in res: + if 'ask' in s.lower(): + raise OrchestratorValidationError(f'ssh_config cannot contain: \'{s}\'') + + def validate_ssh_config_fname(self, ssh_config_fname: str) -> None: + if not os.path.isfile(ssh_config_fname): + raise OrchestratorValidationError("ssh_config \"{}\" does not exist".format( + ssh_config_fname)) + + def _reset_con(self, host: str) -> None: + conn, r = self._cons.get(host, (None, None)) + if conn: + self.log.debug('_reset_con close %s' % host) + conn.exit() + del self._cons[host] + + def _reset_cons(self) -> None: + for host, conn_and_r in self._cons.items(): + self.log.debug('_reset_cons close %s' % host) + conn, r = conn_and_r + conn.exit() + self._cons = {} + + def update_watched_hosts(self) -> None: + # currently, we are watching hosts with nfs daemons + hosts_to_watch = [d.hostname for d in self.cache.get_daemons( + ) if d.daemon_type in RESCHEDULE_FROM_OFFLINE_HOSTS_TYPES] + self.offline_watcher.set_hosts(list(set([h for h in hosts_to_watch if h is not None]))) + + def offline_hosts_remove(self, host: str) -> None: + if host in self.offline_hosts: + self.offline_hosts.remove(host) + + @staticmethod + def can_run() -> Tuple[bool, str]: + if remoto is not None: + return True, "" + else: + return False, "loading remoto library:{}".format( + remoto_import_error) + + def available(self) -> Tuple[bool, str, Dict[str, Any]]: + """ + The cephadm orchestrator is always available. + """ + ok, err = self.can_run() + if not ok: + return ok, err, {} + if not self.ssh_key or not self.ssh_pub: + return False, 'SSH keys not set. Use `ceph cephadm set-priv-key` and `ceph cephadm set-pub-key` or `ceph cephadm generate-key`', {} + + # mypy is unable to determine type for _processes since it's private + worker_count: int = self._worker_pool._processes # type: ignore + ret = { + "workers": worker_count, + "paused": self.paused, + } + + return True, err, ret + + def _validate_and_set_ssh_val(self, what: str, new: Optional[str], old: Optional[str]) -> None: + self.set_store(what, new) + self._reconfig_ssh() + if self.cache.get_hosts(): + # Can't check anything without hosts + host = self.cache.get_hosts()[0] + r = CephadmServe(self)._check_host(host) + if r is not None: + # connection failed reset user + self.set_store(what, old) + self._reconfig_ssh() + raise OrchestratorError('ssh connection %s@%s failed' % (self.ssh_user, host)) + self.log.info(f'Set ssh {what}') + + @orchestrator._cli_write_command( + prefix='cephadm set-ssh-config') + def _set_ssh_config(self, inbuf: Optional[str] = None) -> Tuple[int, str, str]: + """ + Set the ssh_config file (use -i ) + """ + # Set an ssh_config file provided from stdin + + old = self.ssh_config + if inbuf == old: + return 0, "value unchanged", "" + self.validate_ssh_config_content(inbuf) + self._validate_and_set_ssh_val('ssh_config', inbuf, old) + return 0, "", "" + + @orchestrator._cli_write_command('cephadm clear-ssh-config') + def _clear_ssh_config(self) -> Tuple[int, str, str]: + """ + Clear the ssh_config file + """ + # Clear the ssh_config file provided from stdin + self.set_store("ssh_config", None) + self.ssh_config_tmp = None + self.log.info('Cleared ssh_config') + self._reconfig_ssh() + return 0, "", "" + + @orchestrator._cli_read_command('cephadm get-ssh-config') + def _get_ssh_config(self) -> HandleCommandResult: + """ + Returns the ssh config as used by cephadm + """ + if self.ssh_config_file: + self.validate_ssh_config_fname(self.ssh_config_file) + with open(self.ssh_config_file) as f: + return HandleCommandResult(stdout=f.read()) + ssh_config = self.get_store("ssh_config") + if ssh_config: + return HandleCommandResult(stdout=ssh_config) + return HandleCommandResult(stdout=DEFAULT_SSH_CONFIG) + + @orchestrator._cli_write_command('cephadm generate-key') + def _generate_key(self) -> Tuple[int, str, str]: + """ + Generate a cluster SSH key (if not present) + """ + if not self.ssh_pub or not self.ssh_key: + self.log.info('Generating ssh key...') + tmp_dir = TemporaryDirectory() + path = tmp_dir.name + '/key' + try: + subprocess.check_call([ + '/usr/bin/ssh-keygen', + '-C', 'ceph-%s' % self._cluster_fsid, + '-N', '', + '-f', path + ]) + with open(path, 'r') as f: + secret = f.read() + with open(path + '.pub', 'r') as f: + pub = f.read() + finally: + os.unlink(path) + os.unlink(path + '.pub') + tmp_dir.cleanup() + self.set_store('ssh_identity_key', secret) + self.set_store('ssh_identity_pub', pub) + self._reconfig_ssh() + return 0, '', '' + + @orchestrator._cli_write_command( + 'cephadm set-priv-key') + def _set_priv_key(self, inbuf: Optional[str] = None) -> Tuple[int, str, str]: + """Set cluster SSH private key (use -i )""" + if inbuf is None or len(inbuf) == 0: + return -errno.EINVAL, "", "empty private ssh key provided" + old = self.ssh_key + if inbuf == old: + return 0, "value unchanged", "" + self._validate_and_set_ssh_val('ssh_identity_key', inbuf, old) + self.log.info('Set ssh private key') + return 0, "", "" + + @orchestrator._cli_write_command( + 'cephadm set-pub-key') + def _set_pub_key(self, inbuf: Optional[str] = None) -> Tuple[int, str, str]: + """Set cluster SSH public key (use -i )""" + if inbuf is None or len(inbuf) == 0: + return -errno.EINVAL, "", "empty public ssh key provided" + old = self.ssh_pub + if inbuf == old: + return 0, "value unchanged", "" + self._validate_and_set_ssh_val('ssh_identity_pub', inbuf, old) + return 0, "", "" + + @orchestrator._cli_write_command( + 'cephadm clear-key') + def _clear_key(self) -> Tuple[int, str, str]: + """Clear cluster SSH key""" + self.set_store('ssh_identity_key', None) + self.set_store('ssh_identity_pub', None) + self._reconfig_ssh() + self.log.info('Cleared cluster SSH key') + return 0, '', '' + + @orchestrator._cli_read_command( + 'cephadm get-pub-key') + def _get_pub_key(self) -> Tuple[int, str, str]: + """Show SSH public key for connecting to cluster hosts""" + if self.ssh_pub: + return 0, self.ssh_pub, '' + else: + return -errno.ENOENT, '', 'No cluster SSH key defined' + + @orchestrator._cli_read_command( + 'cephadm get-user') + def _get_user(self) -> Tuple[int, str, str]: + """ + Show user for SSHing to cluster hosts + """ + if self.ssh_user is None: + return -errno.ENOENT, '', 'No cluster SSH user configured' + else: + return 0, self.ssh_user, '' + + @orchestrator._cli_read_command( + 'cephadm set-user') + def set_ssh_user(self, user: str) -> Tuple[int, str, str]: + """ + Set user for SSHing to cluster hosts, passwordless sudo will be needed for non-root users + """ + current_user = self.ssh_user + if user == current_user: + return 0, "value unchanged", "" + + self._validate_and_set_ssh_val('ssh_user', user, current_user) + current_ssh_config = self._get_ssh_config() + new_ssh_config = re.sub(r"(\s{2}User\s)(.*)", r"\1" + user, current_ssh_config.stdout) + self._set_ssh_config(new_ssh_config) + + msg = 'ssh user set to %s' % user + if user != 'root': + msg += '. sudo will be used' + self.log.info(msg) + return 0, msg, '' + + @orchestrator._cli_read_command( + 'cephadm registry-login') + def registry_login(self, url: Optional[str] = None, username: Optional[str] = None, password: Optional[str] = None, inbuf: Optional[str] = None) -> Tuple[int, str, str]: + """ + Set custom registry login info by providing url, username and password or json file with login info (-i ) + """ + # if password not given in command line, get it through file input + if not (url and username and password) and (inbuf is None or len(inbuf) == 0): + return -errno.EINVAL, "", ("Invalid arguments. Please provide arguments " + "or -i ") + elif (url and username and password): + registry_json = {'url': url, 'username': username, 'password': password} + else: + assert isinstance(inbuf, str) + registry_json = json.loads(inbuf) + if "url" not in registry_json or "username" not in registry_json or "password" not in registry_json: + return -errno.EINVAL, "", ("json provided for custom registry login did not include all necessary fields. " + "Please setup json file as\n" + "{\n" + " \"url\": \"REGISTRY_URL\",\n" + " \"username\": \"REGISTRY_USERNAME\",\n" + " \"password\": \"REGISTRY_PASSWORD\"\n" + "}\n") + + # verify login info works by attempting login on random host + host = None + for host_name in self.inventory.keys(): + host = host_name + break + if not host: + raise OrchestratorError('no hosts defined') + r = CephadmServe(self)._registry_login(host, registry_json) + if r is not None: + return 1, '', r + # if logins succeeded, store info + self.log.debug("Host logins successful. Storing login info.") + self.set_store('registry_credentials', json.dumps(registry_json)) + # distribute new login info to all hosts + self.cache.distribute_new_registry_login_info() + return 0, "registry login scheduled", '' + + @orchestrator._cli_read_command('cephadm check-host') + def check_host(self, host: str, addr: Optional[str] = None) -> Tuple[int, str, str]: + """Check whether we can access and manage a remote host""" + try: + out, err, code = CephadmServe(self)._run_cephadm(host, cephadmNoImage, 'check-host', + ['--expect-hostname', host], + addr=addr, + error_ok=True, no_fsid=True) + if code: + return 1, '', ('check-host failed:\n' + '\n'.join(err)) + except OrchestratorError: + self.log.exception(f"check-host failed for '{host}'") + return 1, '', ('check-host failed:\n' + + f"Host '{host}' not found. Use 'ceph orch host ls' to see all managed hosts.") + # if we have an outstanding health alert for this host, give the + # serve thread a kick + if 'CEPHADM_HOST_CHECK_FAILED' in self.health_checks: + for item in self.health_checks['CEPHADM_HOST_CHECK_FAILED']['detail']: + if item.startswith('host %s ' % host): + self.event.set() + return 0, '%s (%s) ok' % (host, addr), '\n'.join(err) + + @orchestrator._cli_read_command( + 'cephadm prepare-host') + def _prepare_host(self, host: str, addr: Optional[str] = None) -> Tuple[int, str, str]: + """Prepare a remote host for use with cephadm""" + out, err, code = CephadmServe(self)._run_cephadm(host, cephadmNoImage, 'prepare-host', + ['--expect-hostname', host], + addr=addr, + error_ok=True, no_fsid=True) + if code: + return 1, '', ('prepare-host failed:\n' + '\n'.join(err)) + # if we have an outstanding health alert for this host, give the + # serve thread a kick + if 'CEPHADM_HOST_CHECK_FAILED' in self.health_checks: + for item in self.health_checks['CEPHADM_HOST_CHECK_FAILED']['detail']: + if item.startswith('host %s ' % host): + self.event.set() + return 0, '%s (%s) ok' % (host, addr), '\n'.join(err) + + @orchestrator._cli_write_command( + prefix='cephadm set-extra-ceph-conf') + def _set_extra_ceph_conf(self, inbuf: Optional[str] = None) -> HandleCommandResult: + """ + Text that is appended to all daemon's ceph.conf. + Mainly a workaround, till `config generate-minimal-conf` generates + a complete ceph.conf. + + Warning: this is a dangerous operation. + """ + if inbuf: + # sanity check. + cp = ConfigParser() + cp.read_string(inbuf, source='') + + self.set_store("extra_ceph_conf", json.dumps({ + 'conf': inbuf, + 'last_modified': datetime_to_str(datetime_now()) + })) + self.log.info('Set extra_ceph_conf') + self._kick_serve_loop() + return HandleCommandResult() + + @orchestrator._cli_read_command( + 'cephadm get-extra-ceph-conf') + def _get_extra_ceph_conf(self) -> HandleCommandResult: + """ + Get extra ceph conf that is appended + """ + return HandleCommandResult(stdout=self.extra_ceph_conf().conf) + + def _set_exporter_config(self, config: Dict[str, str]) -> None: + self.set_store('exporter_config', json.dumps(config)) + + def _get_exporter_config(self) -> Dict[str, str]: + cfg_str = self.get_store('exporter_config') + return json.loads(cfg_str) if cfg_str else {} + + def _set_exporter_option(self, option: str, value: Optional[str] = None) -> None: + kv_option = f'exporter_{option}' + self.set_store(kv_option, value) + + def _get_exporter_option(self, option: str) -> Optional[str]: + kv_option = f'exporter_{option}' + return self.get_store(kv_option) + + @orchestrator._cli_write_command( + prefix='cephadm generate-exporter-config') + @service_inactive('cephadm-exporter') + def _generate_exporter_config(self) -> Tuple[int, str, str]: + """ + Generate default SSL crt/key and token for cephadm exporter daemons + """ + self._set_exporter_defaults() + self.log.info('Default settings created for cephadm exporter(s)') + return 0, "", "" + + def _set_exporter_defaults(self) -> None: + crt, key = self._generate_exporter_ssl() + token = self._generate_exporter_token() + self._set_exporter_config({ + "crt": crt, + "key": key, + "token": token, + "port": CephadmExporterConfig.DEFAULT_PORT + }) + self._set_exporter_option('enabled', 'true') + + def _generate_exporter_ssl(self) -> Tuple[str, str]: + return create_self_signed_cert(dname={"O": "Ceph", "OU": "cephadm-exporter"}) + + def _generate_exporter_token(self) -> str: + return secrets.token_hex(32) + + @orchestrator._cli_write_command( + prefix='cephadm clear-exporter-config') + @service_inactive('cephadm-exporter') + def _clear_exporter_config(self) -> Tuple[int, str, str]: + """ + Clear the SSL configuration used by cephadm exporter daemons + """ + self._clear_exporter_config_settings() + self.log.info('Cleared cephadm exporter configuration') + return 0, "", "" + + def _clear_exporter_config_settings(self) -> None: + self.set_store('exporter_config', None) + self._set_exporter_option('enabled', None) + + @orchestrator._cli_write_command( + prefix='cephadm set-exporter-config') + @service_inactive('cephadm-exporter') + def _store_exporter_config(self, inbuf: Optional[str] = None) -> Tuple[int, str, str]: + """ + Set custom cephadm-exporter configuration from a json file (-i ). JSON must contain crt, key, token and port + """ + if not inbuf: + return 1, "", "JSON configuration has not been provided (-i )" + + cfg = CephadmExporterConfig(self) + rc, reason = cfg.load_from_json(inbuf) + if rc: + return 1, "", reason + + rc, reason = cfg.validate_config() + if rc: + return 1, "", reason + + self._set_exporter_config({ + "crt": cfg.crt, + "key": cfg.key, + "token": cfg.token, + "port": cfg.port + }) + self.log.info("Loaded and verified the TLS configuration") + return 0, "", "" + + @orchestrator._cli_read_command( + 'cephadm get-exporter-config') + def _show_exporter_config(self) -> Tuple[int, str, str]: + """ + Show the current cephadm-exporter configuraion (JSON)' + """ + cfg = self._get_exporter_config() + return 0, json.dumps(cfg, indent=2), "" + + @orchestrator._cli_read_command('cephadm config-check ls') + def _config_checks_list(self, format: Format = Format.plain) -> HandleCommandResult: + """List the available configuration checks and their current state""" + + if format not in [Format.plain, Format.json, Format.json_pretty]: + return HandleCommandResult( + retval=1, + stderr="Requested format is not supported when listing configuration checks" + ) + + if format in [Format.json, Format.json_pretty]: + return HandleCommandResult( + stdout=to_format(self.config_checker.health_checks, + format, + many=True, + cls=None)) + + # plain formatting + table = PrettyTable( + ['NAME', + 'HEALTHCHECK', + 'STATUS', + 'DESCRIPTION' + ], border=False) + table.align['NAME'] = 'l' + table.align['HEALTHCHECK'] = 'l' + table.align['STATUS'] = 'l' + table.align['DESCRIPTION'] = 'l' + table.left_padding_width = 0 + table.right_padding_width = 2 + for c in self.config_checker.health_checks: + table.add_row(( + c.name, + c.healthcheck_name, + c.status, + c.description, + )) + + return HandleCommandResult(stdout=table.get_string()) + + @orchestrator._cli_read_command('cephadm config-check status') + def _config_check_status(self) -> HandleCommandResult: + """Show whether the configuration checker feature is enabled/disabled""" + status = self.get_module_option('config_checks_enabled') + return HandleCommandResult(stdout="Enabled" if status else "Disabled") + + @orchestrator._cli_write_command('cephadm config-check enable') + def _config_check_enable(self, check_name: str) -> HandleCommandResult: + """Enable a specific configuration check""" + if not self._config_check_valid(check_name): + return HandleCommandResult(retval=1, stderr="Invalid check name") + + err, msg = self._update_config_check(check_name, 'enabled') + if err: + return HandleCommandResult( + retval=err, + stderr=f"Failed to enable check '{check_name}' : {msg}") + + return HandleCommandResult(stdout="ok") + + @orchestrator._cli_write_command('cephadm config-check disable') + def _config_check_disable(self, check_name: str) -> HandleCommandResult: + """Disable a specific configuration check""" + if not self._config_check_valid(check_name): + return HandleCommandResult(retval=1, stderr="Invalid check name") + + err, msg = self._update_config_check(check_name, 'disabled') + if err: + return HandleCommandResult(retval=err, stderr=f"Failed to disable check '{check_name}': {msg}") + else: + # drop any outstanding raised healthcheck for this check + config_check = self.config_checker.lookup_check(check_name) + if config_check: + if config_check.healthcheck_name in self.health_checks: + self.health_checks.pop(config_check.healthcheck_name, None) + self.set_health_checks(self.health_checks) + else: + self.log.error( + f"Unable to resolve a check name ({check_name}) to a healthcheck definition?") + + return HandleCommandResult(stdout="ok") + + def _config_check_valid(self, check_name: str) -> bool: + return check_name in [chk.name for chk in self.config_checker.health_checks] + + def _update_config_check(self, check_name: str, status: str) -> Tuple[int, str]: + checks_raw = self.get_store('config_checks') + if not checks_raw: + return 1, "config_checks setting is not available" + + checks = json.loads(checks_raw) + checks.update({ + check_name: status + }) + self.log.info(f"updated config check '{check_name}' : {status}") + self.set_store('config_checks', json.dumps(checks)) + return 0, "" + + class ExtraCephConf(NamedTuple): + conf: str + last_modified: Optional[datetime.datetime] + + def extra_ceph_conf(self) -> 'CephadmOrchestrator.ExtraCephConf': + data = self.get_store('extra_ceph_conf') + if not data: + return CephadmOrchestrator.ExtraCephConf('', None) + try: + j = json.loads(data) + except ValueError: + msg = 'Unable to load extra_ceph_conf: Cannot decode JSON' + self.log.exception('%s: \'%s\'', msg, data) + return CephadmOrchestrator.ExtraCephConf('', None) + return CephadmOrchestrator.ExtraCephConf(j['conf'], str_to_datetime(j['last_modified'])) + + def extra_ceph_conf_is_newer(self, dt: datetime.datetime) -> bool: + conf = self.extra_ceph_conf() + if not conf.last_modified: + return False + return conf.last_modified > dt + + @orchestrator._cli_write_command( + 'cephadm osd activate' + ) + def _osd_activate(self, host: List[str]) -> HandleCommandResult: + """ + Start OSD containers for existing OSDs + """ + + @forall_hosts + def run(h: str) -> str: + return self.osd_service.deploy_osd_daemons_for_existing_osds(h, 'osd') + + return HandleCommandResult(stdout='\n'.join(run(host))) + + @orchestrator._cli_read_command('orch client-keyring ls') + def _client_keyring_ls(self, format: Format = Format.plain) -> HandleCommandResult: + """ + List client keyrings under cephadm management + """ + if format != Format.plain: + output = to_format(self.keys.keys.values(), format, many=True, cls=ClientKeyringSpec) + else: + table = PrettyTable( + ['ENTITY', 'PLACEMENT', 'MODE', 'OWNER', 'PATH'], + border=False) + table.align = 'l' + table.left_padding_width = 0 + table.right_padding_width = 2 + for ks in sorted(self.keys.keys.values(), key=lambda ks: ks.entity): + table.add_row(( + ks.entity, ks.placement.pretty_str(), + utils.file_mode_to_str(ks.mode), + f'{ks.uid}:{ks.gid}', + ks.path, + )) + output = table.get_string() + return HandleCommandResult(stdout=output) + + @orchestrator._cli_write_command('orch client-keyring set') + def _client_keyring_set( + self, + entity: str, + placement: str, + owner: Optional[str] = None, + mode: Optional[str] = None, + ) -> HandleCommandResult: + """ + Add or update client keyring under cephadm management + """ + if not entity.startswith('client.'): + raise OrchestratorError('entity must start with client.') + if owner: + try: + uid, gid = map(int, owner.split(':')) + except Exception: + raise OrchestratorError('owner must look like ":", e.g., "0:0"') + else: + uid = 0 + gid = 0 + if mode: + try: + imode = int(mode, 8) + except Exception: + raise OrchestratorError('mode must be an octal mode, e.g. "600"') + else: + imode = 0o600 + pspec = PlacementSpec.from_string(placement) + ks = ClientKeyringSpec(entity, pspec, mode=imode, uid=uid, gid=gid) + self.keys.update(ks) + self._kick_serve_loop() + return HandleCommandResult() + + @orchestrator._cli_write_command('orch client-keyring rm') + def _client_keyring_rm( + self, + entity: str, + ) -> HandleCommandResult: + """ + Remove client keyring from cephadm management + """ + self.keys.rm(entity) + self._kick_serve_loop() + return HandleCommandResult() + + def _get_connection(self, host: str) -> Tuple['remoto.backends.BaseConnection', + 'remoto.backends.LegacyModuleExecute']: + """ + Setup a connection for running commands on remote host. + """ + conn, r = self._cons.get(host, (None, None)) + if conn: + if conn.has_connection(): + self.log.debug('Have connection to %s' % host) + return conn, r + else: + self._reset_con(host) + assert self.ssh_user + n = self.ssh_user + '@' + host + self.log.debug("Opening connection to {} with ssh options '{}'".format( + n, self._ssh_options)) + child_logger = self.log.getChild(n) + child_logger.setLevel('WARNING') + conn = remoto.Connection( + n, + logger=child_logger, + ssh_options=self._ssh_options, + sudo=True if self.ssh_user != 'root' else False) + + r = conn.import_module(remotes) + self._cons[host] = conn, r + + return conn, r + + def _executable_path(self, conn: 'remoto.backends.BaseConnection', executable: str) -> str: + """ + Remote validator that accepts a connection object to ensure that a certain + executable is available returning its full path if so. + + Otherwise an exception with thorough details will be raised, informing the + user that the executable was not found. + """ + executable_path = conn.remote_module.which(executable) + if not executable_path: + raise RuntimeError("Executable '{}' not found on host '{}'".format( + executable, conn.hostname)) + self.log.debug("Found executable '{}' at path '{}'".format(executable, + executable_path)) + return executable_path + + def _get_container_image(self, daemon_name: str) -> Optional[str]: + daemon_type = daemon_name.split('.', 1)[0] # type: ignore + image: Optional[str] = None + if daemon_type in CEPH_IMAGE_TYPES: + # get container image + image = str(self.get_foreign_ceph_option( + utils.name_to_config_section(daemon_name), + 'container_image' + )).strip() + elif daemon_type == 'prometheus': + image = self.container_image_prometheus + elif daemon_type == 'grafana': + image = self.container_image_grafana + elif daemon_type == 'alertmanager': + image = self.container_image_alertmanager + elif daemon_type == 'node-exporter': + image = self.container_image_node_exporter + elif daemon_type == 'haproxy': + image = self.container_image_haproxy + elif daemon_type == 'keepalived': + image = self.container_image_keepalived + elif daemon_type == CustomContainerService.TYPE: + # The image can't be resolved, the necessary information + # is only available when a container is deployed (given + # via spec). + image = None + elif daemon_type == 'snmp-gateway': + image = self.container_image_snmp_gateway + else: + assert False, daemon_type + + self.log.debug('%s container image %s' % (daemon_name, image)) + + return image + + def _schedulable_hosts(self) -> List[HostSpec]: + """ + Returns all usable hosts that went through _refresh_host_daemons(). + + This mitigates a potential race, where new host was added *after* + ``_refresh_host_daemons()`` was called, but *before* + ``_apply_all_specs()`` was called. thus we end up with a hosts + where daemons might be running, but we have not yet detected them. + """ + return [ + h for h in self.inventory.all_specs() + if ( + self.cache.host_had_daemon_refresh(h.hostname) + and '_no_schedule' not in h.labels + ) + ] + + def _unreachable_hosts(self) -> List[HostSpec]: + """ + Return all hosts that are offline or in maintenance mode. + + The idea is we should not touch the daemons on these hosts (since + in theory the hosts are inaccessible so we CAN'T touch them) but + we still want to count daemons that exist on these hosts toward the + placement so daemons on these hosts aren't just moved elsewhere + """ + return [ + h for h in self.inventory.all_specs() + if ( + h.status.lower() in ['maintenance', 'offline'] + or h.hostname in self.offline_hosts + ) + ] + + def _check_valid_addr(self, host: str, addr: str) -> str: + # make sure hostname is resolvable before trying to make a connection + try: + ip_addr = utils.resolve_ip(addr) + except OrchestratorError as e: + msg = str(e) + f''' +You may need to supply an address for {addr} + +Please make sure that the host is reachable and accepts connections using the cephadm SSH key +To add the cephadm SSH key to the host: +> ceph cephadm get-pub-key > ~/ceph.pub +> ssh-copy-id -f -i ~/ceph.pub {self.ssh_user}@{addr} + +To check that the host is reachable open a new shell with the --no-hosts flag: +> cephadm shell --no-hosts + +Then run the following: +> ceph cephadm get-ssh-config > ssh_config +> ceph config-key get mgr/cephadm/ssh_identity_key > ~/cephadm_private_key +> chmod 0600 ~/cephadm_private_key +> ssh -F ssh_config -i ~/cephadm_private_key {self.ssh_user}@{addr}''' + raise OrchestratorError(msg) + + if ipaddress.ip_address(ip_addr).is_loopback and host == addr: + # if this is a re-add, use old address. otherwise error + if host not in self.inventory or self.inventory.get_addr(host) == host: + raise OrchestratorError( + (f'Cannot automatically resolve ip address of host {host}. Ip resolved to loopback address: {ip_addr}\n' + + f'Please explicitly provide the address (ceph orch host add {host} --addr )')) + self.log.debug( + f'Received loopback address resolving ip for {host}: {ip_addr}. Falling back to previous address.') + ip_addr = self.inventory.get_addr(host) + out, err, code = CephadmServe(self)._run_cephadm( + host, cephadmNoImage, 'check-host', + ['--expect-hostname', host], + addr=addr, + error_ok=True, no_fsid=True) + if code: + msg = 'check-host failed:\n' + '\n'.join(err) + # err will contain stdout and stderr, so we filter on the message text to + # only show the errors + errors = [_i.replace("ERROR: ", "") for _i in err if _i.startswith('ERROR')] + if errors: + msg = f'Host {host} ({addr}) failed check(s): {errors}' + raise OrchestratorError(msg) + return ip_addr + + def _add_host(self, spec): + # type: (HostSpec) -> str + """ + Add a host to be managed by the orchestrator. + + :param host: host name + """ + HostSpec.validate(spec) + ip_addr = self._check_valid_addr(spec.hostname, spec.addr) + if spec.addr == spec.hostname and ip_addr: + spec.addr = ip_addr + + if spec.hostname in self.inventory and self.inventory.get_addr(spec.hostname) != spec.addr: + self.cache.refresh_all_host_info(spec.hostname) + + # prime crush map? + if spec.location: + self.check_mon_command({ + 'prefix': 'osd crush add-bucket', + 'name': spec.hostname, + 'type': 'host', + 'args': [f'{k}={v}' for k, v in spec.location.items()], + }) + + if spec.hostname not in self.inventory: + self.cache.prime_empty_host(spec.hostname) + self.inventory.add_host(spec) + self.offline_hosts_remove(spec.hostname) + if spec.status == 'maintenance': + self._set_maintenance_healthcheck() + self.event.set() # refresh stray health check + self.log.info('Added host %s' % spec.hostname) + return "Added host '{}' with addr '{}'".format(spec.hostname, spec.addr) + + @handle_orch_error + def add_host(self, spec: HostSpec) -> str: + return self._add_host(spec) + + @handle_orch_error + def remove_host(self, host: str, force: bool = False, offline: bool = False) -> str: + """ + Remove a host from orchestrator management. + + :param host: host name + :param force: bypass running daemons check + :param offline: remove offline host + """ + + # check if host is offline + host_offline = host in self.offline_hosts + + if host_offline and not offline: + raise OrchestratorValidationError( + "{} is offline, please use --offline and --force to remove this host. This can potentially cause data loss".format(host)) + + if not host_offline and offline: + raise OrchestratorValidationError( + "{} is online, please remove host without --offline.".format(host)) + + if offline and not force: + raise OrchestratorValidationError("Removing an offline host requires --force") + + # check if there are daemons on the host + if not force: + daemons = self.cache.get_daemons_by_host(host) + if daemons: + self.log.warning(f"Blocked {host} removal. Daemons running: {daemons}") + + daemons_table = "" + daemons_table += "{:<20} {:<15}\n".format("type", "id") + daemons_table += "{:<20} {:<15}\n".format("-" * 20, "-" * 15) + for d in daemons: + daemons_table += "{:<20} {:<15}\n".format(d.daemon_type, d.daemon_id) + + raise OrchestratorValidationError("Not allowed to remove %s from cluster. " + "The following daemons are running in the host:" + "\n%s\nPlease run 'ceph orch host drain %s' to remove daemons from host" % ( + host, daemons_table, host)) + + # check, if there we're removing the last _admin host + if not force: + p = PlacementSpec(label='_admin') + admin_hosts = p.filter_matching_hostspecs(self.inventory.all_specs()) + if len(admin_hosts) == 1 and admin_hosts[0] == host: + raise OrchestratorValidationError(f"Host {host} is the last host with the '_admin'" + " label. Please add the '_admin' label to a host" + " or add --force to this command") + + def run_cmd(cmd_args: dict) -> None: + ret, out, err = self.mon_command(cmd_args) + if ret != 0: + self.log.debug(f"ran {cmd_args} with mon_command") + self.log.error( + f"cmd: {cmd_args.get('prefix')} failed with: {err}. (errno:{ret})") + self.log.debug(f"cmd: {cmd_args.get('prefix')} returns: {out}") + + if offline: + daemons = self.cache.get_daemons_by_host(host) + for d in daemons: + self.log.info(f"removing: {d.name()}") + + if d.daemon_type != 'osd': + self.cephadm_services[str(d.daemon_type)].pre_remove(d) + self.cephadm_services[str(d.daemon_type)].post_remove(d, is_failed_deploy=False) + else: + cmd_args = { + 'prefix': 'osd purge-actual', + 'id': int(str(d.daemon_id)), + 'yes_i_really_mean_it': True + } + run_cmd(cmd_args) + + cmd_args = { + 'prefix': 'osd crush rm', + 'name': host + } + run_cmd(cmd_args) + + self.inventory.rm_host(host) + self.cache.rm_host(host) + self._reset_con(host) + self.event.set() # refresh stray health check + self.log.info('Removed host %s' % host) + return "Removed {} host '{}'".format('offline' if offline else '', host) + + @handle_orch_error + def update_host_addr(self, host: str, addr: str) -> str: + self._check_valid_addr(host, addr) + self.inventory.set_addr(host, addr) + self._reset_con(host) + self.event.set() # refresh stray health check + self.log.info('Set host %s addr to %s' % (host, addr)) + return "Updated host '{}' addr to '{}'".format(host, addr) + + @handle_orch_error + def get_hosts(self): + # type: () -> List[orchestrator.HostSpec] + """ + Return a list of hosts managed by the orchestrator. + + Notes: + - skip async: manager reads from cache. + """ + return list(self.inventory.all_specs()) + + @handle_orch_error + def get_facts(self, hostname: Optional[str] = None) -> List[Dict[str, Any]]: + """ + Return a list of hosts metadata(gather_facts) managed by the orchestrator. + + Notes: + - skip async: manager reads from cache. + """ + if hostname: + return [self.cache.get_facts(hostname)] + + return [self.cache.get_facts(hostname) for hostname in self.cache.get_hosts()] + + @handle_orch_error + def add_host_label(self, host: str, label: str) -> str: + self.inventory.add_label(host, label) + self.log.info('Added label %s to host %s' % (label, host)) + self._kick_serve_loop() + return 'Added label %s to host %s' % (label, host) + + @handle_orch_error + def remove_host_label(self, host: str, label: str, force: bool = False) -> str: + # if we remove the _admin label from the only host that has it we could end up + # removing the only instance of the config and keyring and cause issues + if not force and label == '_admin': + p = PlacementSpec(label='_admin') + admin_hosts = p.filter_matching_hostspecs(self.inventory.all_specs()) + if len(admin_hosts) == 1 and admin_hosts[0] == host: + raise OrchestratorValidationError(f"Host {host} is the last host with the '_admin'" + " label.\nRemoving the _admin label from this host could cause the removal" + " of the last cluster config/keyring managed by cephadm.\n" + "It is recommended to add the _admin label to another host" + " before completing this operation.\nIf you're certain this is" + " what you want rerun this command with --force.") + self.inventory.rm_label(host, label) + self.log.info('Removed label %s to host %s' % (label, host)) + self._kick_serve_loop() + return 'Removed label %s from host %s' % (label, host) + + def _host_ok_to_stop(self, hostname: str, force: bool = False) -> Tuple[int, str]: + self.log.debug("running host-ok-to-stop checks") + daemons = self.cache.get_daemons() + daemon_map: Dict[str, List[str]] = defaultdict(lambda: []) + for dd in daemons: + assert dd.hostname is not None + assert dd.daemon_type is not None + assert dd.daemon_id is not None + if dd.hostname == hostname: + daemon_map[dd.daemon_type].append(dd.daemon_id) + + notifications: List[str] = [] + error_notifications: List[str] = [] + okay: bool = True + for daemon_type, daemon_ids in daemon_map.items(): + r = self.cephadm_services[daemon_type_to_service( + daemon_type)].ok_to_stop(daemon_ids, force=force) + if r.retval: + okay = False + # collect error notifications so user can see every daemon causing host + # to not be okay to stop + error_notifications.append(r.stderr) + if r.stdout: + # if extra notifications to print for user, add them to notifications list + notifications.append(r.stdout) + + if not okay: + # at least one daemon is not okay to stop + return 1, '\n'.join(error_notifications) + + if notifications: + return 0, (f'It is presumed safe to stop host {hostname}. ' + + 'Note the following:\n\n' + '\n'.join(notifications)) + return 0, f'It is presumed safe to stop host {hostname}' + + @handle_orch_error + def host_ok_to_stop(self, hostname: str) -> str: + if hostname not in self.cache.get_hosts(): + raise OrchestratorError(f'Cannot find host "{hostname}"') + + rc, msg = self._host_ok_to_stop(hostname) + if rc: + raise OrchestratorError(msg, errno=rc) + + self.log.info(msg) + return msg + + def _set_maintenance_healthcheck(self) -> None: + """Raise/update or clear the maintenance health check as needed""" + + in_maintenance = self.inventory.get_host_with_state("maintenance") + if not in_maintenance: + self.remove_health_warning('HOST_IN_MAINTENANCE') + else: + s = "host is" if len(in_maintenance) == 1 else "hosts are" + self.set_health_warning("HOST_IN_MAINTENANCE", f"{len(in_maintenance)} {s} in maintenance mode", 1, [ + f"{h} is in maintenance" for h in in_maintenance]) + + @handle_orch_error + @host_exists() + def enter_host_maintenance(self, hostname: str, force: bool = False) -> str: + """ Attempt to place a cluster host in maintenance + + Placing a host into maintenance disables the cluster's ceph target in systemd + and stops all ceph daemons. If the host is an osd host we apply the noout flag + for the host subtree in crush to prevent data movement during a host maintenance + window. + + :param hostname: (str) name of the host (must match an inventory hostname) + + :raises OrchestratorError: Hostname is invalid, host is already in maintenance + """ + if len(self.cache.get_hosts()) == 1: + raise OrchestratorError("Maintenance feature is not supported on single node clusters") + + # if upgrade is active, deny + if self.upgrade.upgrade_state: + raise OrchestratorError( + f"Unable to place {hostname} in maintenance with upgrade active/paused") + + tgt_host = self.inventory._inventory[hostname] + if tgt_host.get("status", "").lower() == "maintenance": + raise OrchestratorError(f"Host {hostname} is already in maintenance") + + host_daemons = self.cache.get_daemon_types(hostname) + self.log.debug("daemons on host {}".format(','.join(host_daemons))) + if host_daemons: + # daemons on this host, so check the daemons can be stopped + # and if so, place the host into maintenance by disabling the target + rc, msg = self._host_ok_to_stop(hostname, force) + if rc: + raise OrchestratorError( + msg + '\nNote: Warnings can be bypassed with the --force flag', errno=rc) + + # call the host-maintenance function + _out, _err, _code = CephadmServe(self)._run_cephadm(hostname, cephadmNoImage, "host-maintenance", + ["enter"], + error_ok=True) + returned_msg = _err[0].split('\n')[-1] + if returned_msg.startswith('failed') or returned_msg.startswith('ERROR'): + raise OrchestratorError( + f"Failed to place {hostname} into maintenance for cluster {self._cluster_fsid}") + + if "osd" in host_daemons: + crush_node = hostname if '.' not in hostname else hostname.split('.')[0] + rc, out, err = self.mon_command({ + 'prefix': 'osd set-group', + 'flags': 'noout', + 'who': [crush_node], + 'format': 'json' + }) + if rc: + self.log.warning( + f"maintenance mode request for {hostname} failed to SET the noout group (rc={rc})") + raise OrchestratorError( + f"Unable to set the osds on {hostname} to noout (rc={rc})") + else: + self.log.info( + f"maintenance mode request for {hostname} has SET the noout group") + + # update the host status in the inventory + tgt_host["status"] = "maintenance" + self.inventory._inventory[hostname] = tgt_host + self.inventory.save() + + self._set_maintenance_healthcheck() + return f'Daemons for Ceph cluster {self._cluster_fsid} stopped on host {hostname}. Host {hostname} moved to maintenance mode' + + @handle_orch_error + @host_exists() + def exit_host_maintenance(self, hostname: str) -> str: + """Exit maintenance mode and return a host to an operational state + + Returning from maintnenance will enable the clusters systemd target and + start it, and remove any noout that has been added for the host if the + host has osd daemons + + :param hostname: (str) host name + + :raises OrchestratorError: Unable to return from maintenance, or unset the + noout flag + """ + tgt_host = self.inventory._inventory[hostname] + if tgt_host['status'] != "maintenance": + raise OrchestratorError(f"Host {hostname} is not in maintenance mode") + + outs, errs, _code = CephadmServe(self)._run_cephadm(hostname, cephadmNoImage, 'host-maintenance', + ['exit'], + error_ok=True) + returned_msg = errs[0].split('\n')[-1] + if returned_msg.startswith('failed') or returned_msg.startswith('ERROR'): + raise OrchestratorError( + f"Failed to exit maintenance state for host {hostname}, cluster {self._cluster_fsid}") + + if "osd" in self.cache.get_daemon_types(hostname): + crush_node = hostname if '.' not in hostname else hostname.split('.')[0] + rc, _out, _err = self.mon_command({ + 'prefix': 'osd unset-group', + 'flags': 'noout', + 'who': [crush_node], + 'format': 'json' + }) + if rc: + self.log.warning( + f"exit maintenance request failed to UNSET the noout group for {hostname}, (rc={rc})") + raise OrchestratorError(f"Unable to set the osds on {hostname} to noout (rc={rc})") + else: + self.log.info( + f"exit maintenance request has UNSET for the noout group on host {hostname}") + + # update the host record status + tgt_host['status'] = "" + self.inventory._inventory[hostname] = tgt_host + self.inventory.save() + + self._set_maintenance_healthcheck() + + return f"Ceph cluster {self._cluster_fsid} on {hostname} has exited maintenance mode" + + @handle_orch_error + @host_exists() + def rescan_host(self, hostname: str) -> str: + """Use cephadm to issue a disk rescan on each HBA + + Some HBAs and external enclosures don't automatically register + device insertion with the kernel, so for these scenarios we need + to manually rescan + + :param hostname: (str) host name + """ + self.log.info(f'disk rescan request sent to host "{hostname}"') + _out, _err, _code = CephadmServe(self)._run_cephadm(hostname, cephadmNoImage, "disk-rescan", + [], no_fsid=True, error_ok=True) + if not _err: + raise OrchestratorError('Unexpected response from cephadm disk-rescan call') + + msg = _err[0].split('\n')[-1] + log_msg = f'disk rescan: {msg}' + if msg.upper().startswith('OK'): + self.log.info(log_msg) + else: + self.log.warning(log_msg) + + return f'{msg}' + + def get_minimal_ceph_conf(self) -> str: + _, config, _ = self.check_mon_command({ + "prefix": "config generate-minimal-conf", + }) + extra = self.extra_ceph_conf().conf + if extra: + config += '\n\n' + extra.strip() + '\n' + return config + + def _invalidate_daemons_and_kick_serve(self, filter_host: Optional[str] = None) -> None: + if filter_host: + self.cache.invalidate_host_daemons(filter_host) + else: + for h in self.cache.get_hosts(): + # Also discover daemons deployed manually + self.cache.invalidate_host_daemons(h) + + self._kick_serve_loop() + + @handle_orch_error + def describe_service(self, service_type: Optional[str] = None, service_name: Optional[str] = None, + refresh: bool = False) -> List[orchestrator.ServiceDescription]: + if refresh: + self._invalidate_daemons_and_kick_serve() + self.log.debug('Kicked serve() loop to refresh all services') + + sm: Dict[str, orchestrator.ServiceDescription] = {} + + # known services + for nm, spec in self.spec_store.all_specs.items(): + if service_type is not None and service_type != spec.service_type: + continue + if service_name is not None and service_name != nm: + continue + + if spec.service_type != 'osd': + size = spec.placement.get_target_count(self._schedulable_hosts()) + else: + # osd counting is special + size = 0 + + sm[nm] = orchestrator.ServiceDescription( + spec=spec, + size=size, + running=0, + events=self.events.get_for_service(spec.service_name()), + created=self.spec_store.spec_created[nm], + deleted=self.spec_store.spec_deleted.get(nm, None), + virtual_ip=spec.get_virtual_ip(), + ports=spec.get_port_start(), + ) + if spec.service_type == 'ingress': + # ingress has 2 daemons running per host + sm[nm].size *= 2 + + # factor daemons into status + for h, dm in self.cache.get_daemons_with_volatile_status(): + for name, dd in dm.items(): + assert dd.hostname is not None, f'no hostname for {dd!r}' + assert dd.daemon_type is not None, f'no daemon_type for {dd!r}' + + n: str = dd.service_name() + + if ( + service_type + and service_type != daemon_type_to_service(dd.daemon_type) + ): + continue + if service_name and service_name != n: + continue + + if n not in sm: + # new unmanaged service + spec = ServiceSpec( + unmanaged=True, + service_type=daemon_type_to_service(dd.daemon_type), + service_id=dd.service_id(), + ) + sm[n] = orchestrator.ServiceDescription( + last_refresh=dd.last_refresh, + container_image_id=dd.container_image_id, + container_image_name=dd.container_image_name, + spec=spec, + size=0, + ) + + if dd.status == DaemonDescriptionStatus.running: + sm[n].running += 1 + if dd.daemon_type == 'osd': + # The osd count can't be determined by the Placement spec. + # Showing an actual/expected representation cannot be determined + # here. So we're setting running = size for now. + sm[n].size += 1 + if ( + not sm[n].last_refresh + or not dd.last_refresh + or dd.last_refresh < sm[n].last_refresh # type: ignore + ): + sm[n].last_refresh = dd.last_refresh + + return list(sm.values()) + + @handle_orch_error + def list_daemons(self, + service_name: Optional[str] = None, + daemon_type: Optional[str] = None, + daemon_id: Optional[str] = None, + host: Optional[str] = None, + refresh: bool = False) -> List[orchestrator.DaemonDescription]: + if refresh: + self._invalidate_daemons_and_kick_serve(host) + self.log.debug('Kicked serve() loop to refresh all daemons') + + result = [] + for h, dm in self.cache.get_daemons_with_volatile_status(): + if host and h != host: + continue + for name, dd in dm.items(): + if daemon_type is not None and daemon_type != dd.daemon_type: + continue + if daemon_id is not None and daemon_id != dd.daemon_id: + continue + if service_name is not None and service_name != dd.service_name(): + continue + if not dd.memory_request and dd.daemon_type in ['osd', 'mon']: + dd.memory_request = cast(Optional[int], self.get_foreign_ceph_option( + dd.name(), + f"{dd.daemon_type}_memory_target" + )) + result.append(dd) + return result + + @handle_orch_error + def service_action(self, action: str, service_name: str) -> List[str]: + if service_name not in self.spec_store.all_specs.keys(): + raise OrchestratorError(f'Invalid service name "{service_name}".' + + ' View currently running services using "ceph orch ls"') + dds: List[DaemonDescription] = self.cache.get_daemons_by_service(service_name) + if not dds: + raise OrchestratorError(f'No daemons exist under service name "{service_name}".' + + ' View currently running services using "ceph orch ls"') + if action == 'stop' and service_name.split('.')[0].lower() in ['mgr', 'mon', 'osd']: + return [f'Stopping entire {service_name} service is prohibited.'] + self.log.info('%s service %s' % (action.capitalize(), service_name)) + return [ + self._schedule_daemon_action(dd.name(), action) + for dd in dds + ] + + def _daemon_action(self, + daemon_spec: CephadmDaemonDeploySpec, + action: str, + image: Optional[str] = None) -> str: + self._daemon_action_set_image(action, image, daemon_spec.daemon_type, + daemon_spec.daemon_id) + + if (action == 'redeploy' or action == 'restart') and self.daemon_is_self(daemon_spec.daemon_type, + daemon_spec.daemon_id): + self.mgr_service.fail_over() + return '' # unreachable + + if action == 'redeploy' or action == 'reconfig': + if daemon_spec.daemon_type != 'osd': + daemon_spec = self.cephadm_services[daemon_type_to_service( + daemon_spec.daemon_type)].prepare_create(daemon_spec) + else: + # for OSDs, we still need to update config, just not carry out the full + # prepare_create function + daemon_spec.final_config, daemon_spec.deps = self.osd_service.generate_config(daemon_spec) + return CephadmServe(self)._create_daemon(daemon_spec, reconfig=(action == 'reconfig')) + + actions = { + 'start': ['reset-failed', 'start'], + 'stop': ['stop'], + 'restart': ['reset-failed', 'restart'], + } + name = daemon_spec.name() + for a in actions[action]: + try: + out, err, code = CephadmServe(self)._run_cephadm( + daemon_spec.host, name, 'unit', + ['--name', name, a]) + except Exception: + self.log.exception(f'`{daemon_spec.host}: cephadm unit {name} {a}` failed') + self.cache.invalidate_host_daemons(daemon_spec.host) + msg = "{} {} from host '{}'".format(action, name, daemon_spec.host) + self.events.for_daemon(name, 'INFO', msg) + return msg + + def _daemon_action_set_image(self, action: str, image: Optional[str], daemon_type: str, daemon_id: str) -> None: + if image is not None: + if action != 'redeploy': + raise OrchestratorError( + f'Cannot execute {action} with new image. `action` needs to be `redeploy`') + if daemon_type not in CEPH_IMAGE_TYPES: + raise OrchestratorError( + f'Cannot redeploy {daemon_type}.{daemon_id} with a new image: Supported ' + f'types are: {", ".join(CEPH_IMAGE_TYPES)}') + + self.check_mon_command({ + 'prefix': 'config set', + 'name': 'container_image', + 'value': image, + 'who': utils.name_to_config_section(daemon_type + '.' + daemon_id), + }) + + @handle_orch_error + def daemon_action(self, action: str, daemon_name: str, image: Optional[str] = None) -> str: + d = self.cache.get_daemon(daemon_name) + assert d.daemon_type is not None + assert d.daemon_id is not None + + if (action == 'redeploy' or action == 'restart') and self.daemon_is_self(d.daemon_type, d.daemon_id) \ + and not self.mgr_service.mgr_map_has_standby(): + raise OrchestratorError( + f'Unable to schedule redeploy for {daemon_name}: No standby MGRs') + + self._daemon_action_set_image(action, image, d.daemon_type, d.daemon_id) + + self.log.info(f'Schedule {action} daemon {daemon_name}') + return self._schedule_daemon_action(daemon_name, action) + + def daemon_is_self(self, daemon_type: str, daemon_id: str) -> bool: + return daemon_type == 'mgr' and daemon_id == self.get_mgr_id() + + def get_active_mgr_digests(self) -> List[str]: + digests = self.mgr_service.get_active_daemon( + self.cache.get_daemons_by_type('mgr')).container_image_digests + return digests if digests else [] + + def _schedule_daemon_action(self, daemon_name: str, action: str) -> str: + dd = self.cache.get_daemon(daemon_name) + assert dd.daemon_type is not None + assert dd.daemon_id is not None + assert dd.hostname is not None + if (action == 'redeploy' or action == 'restart') and self.daemon_is_self(dd.daemon_type, dd.daemon_id) \ + and not self.mgr_service.mgr_map_has_standby(): + raise OrchestratorError( + f'Unable to schedule redeploy for {daemon_name}: No standby MGRs') + self.cache.schedule_daemon_action(dd.hostname, dd.name(), action) + msg = "Scheduled to {} {} on host '{}'".format(action, daemon_name, dd.hostname) + self._kick_serve_loop() + return msg + + @handle_orch_error + def remove_daemons(self, names): + # type: (List[str]) -> List[str] + args = [] + for host, dm in self.cache.daemons.items(): + for name in names: + if name in dm: + args.append((name, host)) + if not args: + raise OrchestratorError('Unable to find daemon(s) %s' % (names)) + self.log.info('Remove daemons %s' % ' '.join([a[0] for a in args])) + return self._remove_daemons(args) + + @handle_orch_error + def remove_service(self, service_name: str, force: bool = False) -> str: + self.log.info('Remove service %s' % service_name) + self._trigger_preview_refresh(service_name=service_name) + if service_name in self.spec_store: + if self.spec_store[service_name].spec.service_type in ('mon', 'mgr'): + return f'Unable to remove {service_name} service.\n' \ + f'Note, you might want to mark the {service_name} service as "unmanaged"' + else: + return f"Invalid service '{service_name}'. Use 'ceph orch ls' to list available services.\n" + + # Report list of affected OSDs? + if not force and service_name.startswith('osd.'): + osds_msg = {} + for h, dm in self.cache.get_daemons_with_volatile_status(): + osds_to_remove = [] + for name, dd in dm.items(): + if dd.daemon_type == 'osd' and dd.service_name() == service_name: + osds_to_remove.append(str(dd.daemon_id)) + if osds_to_remove: + osds_msg[h] = osds_to_remove + if osds_msg: + msg = '' + for h, ls in osds_msg.items(): + msg += f'\thost {h}: {" ".join([f"osd.{id}" for id in ls])}' + raise OrchestratorError(f'If {service_name} is removed then the following OSDs will remain, --force to proceed anyway\n{msg}') + + found = self.spec_store.rm(service_name) + if found and service_name.startswith('osd.'): + self.spec_store.finally_rm(service_name) + self._kick_serve_loop() + return f'Removed service {service_name}' + + @handle_orch_error + def get_inventory(self, host_filter: Optional[orchestrator.InventoryFilter] = None, refresh: bool = False) -> List[orchestrator.InventoryHost]: + """ + Return the storage inventory of hosts matching the given filter. + + :param host_filter: host filter + + TODO: + - add filtering by label + """ + if refresh: + if host_filter and host_filter.hosts: + for h in host_filter.hosts: + self.log.debug(f'will refresh {h} devs') + self.cache.invalidate_host_devices(h) + else: + for h in self.cache.get_hosts(): + self.log.debug(f'will refresh {h} devs') + self.cache.invalidate_host_devices(h) + + self.event.set() + self.log.debug('Kicked serve() loop to refresh devices') + + result = [] + for host, dls in self.cache.devices.items(): + if host_filter and host_filter.hosts and host not in host_filter.hosts: + continue + result.append(orchestrator.InventoryHost(host, + inventory.Devices(dls))) + return result + + @handle_orch_error + def zap_device(self, host: str, path: str) -> str: + """Zap a device on a managed host. + + Use ceph-volume zap to return a device to an unused/free state + + Args: + host (str): hostname of the cluster host + path (str): device path + + Raises: + OrchestratorError: host is not a cluster host + OrchestratorError: host is in maintenance and therefore unavailable + OrchestratorError: device path not found on the host + OrchestratorError: device is known to a different ceph cluster + OrchestratorError: device holds active osd + OrchestratorError: device cache hasn't been populated yet.. + + Returns: + str: output from the zap command + """ + + self.log.info('Zap device %s:%s' % (host, path)) + + if host not in self.inventory.keys(): + raise OrchestratorError( + f"Host '{host}' is not a member of the cluster") + + host_info = self.inventory._inventory.get(host, {}) + if host_info.get('status', '').lower() == 'maintenance': + raise OrchestratorError( + f"Host '{host}' is in maintenance mode, which prevents any actions against it.") + + if host not in self.cache.devices: + raise OrchestratorError( + f"Host '{host} hasn't been scanned yet to determine it's inventory. Please try again later.") + + host_devices = self.cache.devices[host] + path_found = False + osd_id_list: List[str] = [] + + for dev in host_devices: + if dev.path == path: + # match, so look a little deeper + if dev.lvs: + for lv in cast(List[Dict[str, str]], dev.lvs): + if lv.get('osd_id', ''): + lv_fsid = lv.get('cluster_fsid') + if lv_fsid != self._cluster_fsid: + raise OrchestratorError( + f"device {path} has lv's from a different Ceph cluster ({lv_fsid})") + osd_id_list.append(lv.get('osd_id', '')) + path_found = True + break + if not path_found: + raise OrchestratorError( + f"Device path '{path}' not found on host '{host}'") + + if osd_id_list: + dev_name = os.path.basename(path) + active_osds: List[str] = [] + for osd_id in osd_id_list: + metadata = self.get_metadata('osd', str(osd_id)) + if metadata: + if metadata.get('hostname', '') == host and dev_name in metadata.get('devices', '').split(','): + active_osds.append("osd." + osd_id) + if active_osds: + raise OrchestratorError( + f"Unable to zap: device '{path}' on {host} has {len(active_osds)} active " + f"OSD{'s' if len(active_osds) > 1 else ''}" + f" ({', '.join(active_osds)}). Use 'ceph orch osd rm' first.") + + out, err, code = CephadmServe(self)._run_cephadm( + host, 'osd', 'ceph-volume', + ['--', 'lvm', 'zap', '--destroy', path], + error_ok=True) + + self.cache.invalidate_host_devices(host) + if code: + raise OrchestratorError('Zap failed: %s' % '\n'.join(out + err)) + msg = f'zap successful for {path} on {host}' + self.log.info(msg) + + return msg + '\n' + + @handle_orch_error + def blink_device_light(self, ident_fault: str, on: bool, locs: List[orchestrator.DeviceLightLoc]) -> List[str]: + """ + Blink a device light. Calling something like:: + + lsmcli local-disk-ident-led-on --path $path + + If you must, you can customize this via:: + + ceph config-key set mgr/cephadm/blink_device_light_cmd '' + ceph config-key set mgr/cephadm//blink_device_light_cmd '' + + See templates/blink_device_light_cmd.j2 + """ + @forall_hosts + def blink(host: str, dev: str, path: str) -> str: + cmd_line = self.template.render('blink_device_light_cmd.j2', + { + 'on': on, + 'ident_fault': ident_fault, + 'dev': dev, + 'path': path + }, + host=host) + cmd_args = shlex.split(cmd_line) + + out, err, code = CephadmServe(self)._run_cephadm( + host, 'osd', 'shell', ['--'] + cmd_args, + error_ok=True) + if code: + raise OrchestratorError( + 'Unable to affect %s light for %s:%s. Command: %s' % ( + ident_fault, host, dev, ' '.join(cmd_args))) + self.log.info('Set %s light for %s:%s %s' % ( + ident_fault, host, dev, 'on' if on else 'off')) + return "Set %s light for %s:%s %s" % ( + ident_fault, host, dev, 'on' if on else 'off') + + return blink(locs) + + def get_osd_uuid_map(self, only_up=False): + # type: (bool) -> Dict[str, str] + osd_map = self.get('osd_map') + r = {} + for o in osd_map['osds']: + # only include OSDs that have ever started in this map. this way + # an interrupted osd create can be repeated and succeed the second + # time around. + osd_id = o.get('osd') + if osd_id is None: + raise OrchestratorError("Could not retrieve osd_id from osd_map") + if not only_up: + r[str(osd_id)] = o.get('uuid', '') + return r + + def get_osd_by_id(self, osd_id: int) -> Optional[Dict[str, Any]]: + osd = [x for x in self.get('osd_map')['osds'] + if x['osd'] == osd_id] + + if len(osd) != 1: + return None + + return osd[0] + + def _trigger_preview_refresh(self, + specs: Optional[List[DriveGroupSpec]] = None, + service_name: Optional[str] = None, + ) -> None: + # Only trigger a refresh when a spec has changed + trigger_specs = [] + if specs: + for spec in specs: + preview_spec = self.spec_store.spec_preview.get(spec.service_name()) + # the to-be-preview spec != the actual spec, this means we need to + # trigger a refresh, if the spec has been removed (==None) we need to + # refresh as well. + if not preview_spec or spec != preview_spec: + trigger_specs.append(spec) + if service_name: + trigger_specs = [cast(DriveGroupSpec, self.spec_store.spec_preview.get(service_name))] + if not any(trigger_specs): + return None + + refresh_hosts = self.osd_service.resolve_hosts_for_osdspecs(specs=trigger_specs) + for host in refresh_hosts: + self.log.info(f"Marking host: {host} for OSDSpec preview refresh.") + self.cache.osdspec_previews_refresh_queue.append(host) + + @handle_orch_error + def apply_drivegroups(self, specs: List[DriveGroupSpec]) -> List[str]: + """ + Deprecated. Please use `apply()` instead. + + Keeping this around to be compapatible to mgr/dashboard + """ + return [self._apply(spec) for spec in specs] + + @handle_orch_error + def create_osds(self, drive_group: DriveGroupSpec) -> str: + hosts: List[HostSpec] = self.inventory.all_specs() + filtered_hosts: List[str] = drive_group.placement.filter_matching_hostspecs(hosts) + if not filtered_hosts: + return "Invalid 'host:device' spec: host not found in cluster. Please check 'ceph orch host ls' for available hosts" + return self.osd_service.create_from_spec(drive_group) + + def _preview_osdspecs(self, + osdspecs: Optional[List[DriveGroupSpec]] = None + ) -> dict: + if not osdspecs: + return {'n/a': [{'error': True, + 'message': 'No OSDSpec or matching hosts found.'}]} + matching_hosts = self.osd_service.resolve_hosts_for_osdspecs(specs=osdspecs) + if not matching_hosts: + return {'n/a': [{'error': True, + 'message': 'No OSDSpec or matching hosts found.'}]} + # Is any host still loading previews or still in the queue to be previewed + pending_hosts = {h for h in self.cache.loading_osdspec_preview if h in matching_hosts} + if pending_hosts or any(item in self.cache.osdspec_previews_refresh_queue for item in matching_hosts): + # Report 'pending' when any of the matching hosts is still loading previews (flag is True) + return {'n/a': [{'error': True, + 'message': 'Preview data is being generated.. ' + 'Please re-run this command in a bit.'}]} + # drop all keys that are not in search_hosts and only select reports that match the requested osdspecs + previews_for_specs = {} + for host, raw_reports in self.cache.osdspec_previews.items(): + if host not in matching_hosts: + continue + osd_reports = [] + for osd_report in raw_reports: + if osd_report.get('osdspec') in [x.service_id for x in osdspecs]: + osd_reports.append(osd_report) + previews_for_specs.update({host: osd_reports}) + return previews_for_specs + + def _calc_daemon_deps(self, + spec: Optional[ServiceSpec], + daemon_type: str, + daemon_id: str) -> List[str]: + deps = [] + if daemon_type == 'haproxy': + # because cephadm creates new daemon instances whenever + # port or ip changes, identifying daemons by name is + # sufficient to detect changes. + if not spec: + return [] + ingress_spec = cast(IngressSpec, spec) + assert ingress_spec.backend_service + daemons = self.cache.get_daemons_by_service(ingress_spec.backend_service) + deps = [d.name() for d in daemons] + elif daemon_type == 'keepalived': + # because cephadm creates new daemon instances whenever + # port or ip changes, identifying daemons by name is + # sufficient to detect changes. + if not spec: + return [] + daemons = self.cache.get_daemons_by_service(spec.service_name()) + deps = [d.name() for d in daemons if d.daemon_type == 'haproxy'] + elif daemon_type == 'iscsi': + if spec: + iscsi_spec = cast(IscsiServiceSpec, spec) + deps = [self.iscsi_service.get_trusted_ips(iscsi_spec)] + else: + deps = [self.get_mgr_ip()] + else: + need = { + 'prometheus': ['mgr', 'alertmanager', 'node-exporter', 'ingress'], + 'grafana': ['prometheus'], + 'alertmanager': ['mgr', 'alertmanager', 'snmp-gateway'], + } + for dep_type in need.get(daemon_type, []): + for dd in self.cache.get_daemons_by_type(dep_type): + deps.append(dd.name()) + if daemon_type == 'prometheus': + deps.append(str(self.get_module_option_ex('prometheus', 'server_port', 9283))) + return sorted(deps) + + @forall_hosts + def _remove_daemons(self, name: str, host: str) -> str: + return CephadmServe(self)._remove_daemon(name, host) + + def _check_pool_exists(self, pool: str, service_name: str) -> None: + logger.info(f'Checking pool "{pool}" exists for service {service_name}') + if not self.rados.pool_exists(pool): + raise OrchestratorError(f'Cannot find pool "{pool}" for ' + f'service {service_name}') + + def _add_daemon(self, + daemon_type: str, + spec: ServiceSpec) -> List[str]: + """ + Add (and place) a daemon. Require explicit host placement. Do not + schedule, and do not apply the related scheduling limitations. + """ + if spec.service_name() not in self.spec_store: + raise OrchestratorError('Unable to add a Daemon without Service.\n' + 'Please use `ceph orch apply ...` to create a Service.\n' + 'Note, you might want to create the service with "unmanaged=true"') + + self.log.debug('_add_daemon %s spec %s' % (daemon_type, spec.placement)) + if not spec.placement.hosts: + raise OrchestratorError('must specify host(s) to deploy on') + count = spec.placement.count or len(spec.placement.hosts) + daemons = self.cache.get_daemons_by_service(spec.service_name()) + return self._create_daemons(daemon_type, spec, daemons, + spec.placement.hosts, count) + + def _create_daemons(self, + daemon_type: str, + spec: ServiceSpec, + daemons: List[DaemonDescription], + hosts: List[HostPlacementSpec], + count: int) -> List[str]: + if count > len(hosts): + raise OrchestratorError('too few hosts: want %d, have %s' % ( + count, hosts)) + + did_config = False + service_type = daemon_type_to_service(daemon_type) + + args = [] # type: List[CephadmDaemonDeploySpec] + for host, network, name in hosts: + daemon_id = self.get_unique_name(daemon_type, host, daemons, + prefix=spec.service_id, + forcename=name) + + if not did_config: + self.cephadm_services[service_type].config(spec) + did_config = True + + daemon_spec = self.cephadm_services[service_type].make_daemon_spec( + host, daemon_id, network, spec, + # NOTE: this does not consider port conflicts! + ports=spec.get_port_start()) + self.log.debug('Placing %s.%s on host %s' % ( + daemon_type, daemon_id, host)) + args.append(daemon_spec) + + # add to daemon list so next name(s) will also be unique + sd = orchestrator.DaemonDescription( + hostname=host, + daemon_type=daemon_type, + daemon_id=daemon_id, + ) + daemons.append(sd) + + @ forall_hosts + def create_func_map(*args: Any) -> str: + daemon_spec = self.cephadm_services[daemon_type].prepare_create(*args) + return CephadmServe(self)._create_daemon(daemon_spec) + + return create_func_map(args) + + @handle_orch_error + def add_daemon(self, spec: ServiceSpec) -> List[str]: + ret: List[str] = [] + try: + with orchestrator.set_exception_subject('service', spec.service_name(), overwrite=True): + for d_type in service_to_daemon_types(spec.service_type): + ret.extend(self._add_daemon(d_type, spec)) + return ret + except OrchestratorError as e: + self.events.from_orch_error(e) + raise + + @handle_orch_error + def apply_mon(self, spec: ServiceSpec) -> str: + return self._apply(spec) + + def _apply(self, spec: GenericSpec) -> str: + if spec.service_type == 'host': + return self._add_host(cast(HostSpec, spec)) + + if spec.service_type == 'osd': + # _trigger preview refresh needs to be smart and + # should only refresh if a change has been detected + self._trigger_preview_refresh(specs=[cast(DriveGroupSpec, spec)]) + + return self._apply_service_spec(cast(ServiceSpec, spec)) + + def set_health_warning(self, name: str, summary: str, count: int, detail: List[str]) -> None: + self.health_checks[name] = { + 'severity': 'warning', + 'summary': summary, + 'count': count, + 'detail': detail, + } + self.set_health_checks(self.health_checks) + + def remove_health_warning(self, name: str) -> None: + if name in self.health_checks: + del self.health_checks[name] + self.set_health_checks(self.health_checks) + + def _plan(self, spec: ServiceSpec) -> dict: + if spec.service_type == 'osd': + return {'service_name': spec.service_name(), + 'service_type': spec.service_type, + 'data': self._preview_osdspecs(osdspecs=[cast(DriveGroupSpec, spec)])} + + svc = self.cephadm_services[spec.service_type] + ha = HostAssignment( + spec=spec, + hosts=self._schedulable_hosts(), + unreachable_hosts=self._unreachable_hosts(), + networks=self.cache.networks, + daemons=self.cache.get_daemons_by_service(spec.service_name()), + allow_colo=svc.allow_colo(), + rank_map=self.spec_store[spec.service_name()].rank_map if svc.ranked() else None + ) + ha.validate() + hosts, to_add, to_remove = ha.place() + + return { + 'service_name': spec.service_name(), + 'service_type': spec.service_type, + 'add': [hs.hostname for hs in to_add], + 'remove': [d.name() for d in to_remove] + } + + @handle_orch_error + def plan(self, specs: Sequence[GenericSpec]) -> List: + results = [{'warning': 'WARNING! Dry-Runs are snapshots of a certain point in time and are bound \n' + 'to the current inventory setup. If any of these conditions change, the \n' + 'preview will be invalid. Please make sure to have a minimal \n' + 'timeframe between planning and applying the specs.'}] + if any([spec.service_type == 'host' for spec in specs]): + return [{'error': 'Found . Previews that include Host Specifications are not supported, yet.'}] + for spec in specs: + results.append(self._plan(cast(ServiceSpec, spec))) + return results + + def _apply_service_spec(self, spec: ServiceSpec) -> str: + if spec.placement.is_empty(): + # fill in default placement + defaults = { + 'mon': PlacementSpec(count=5), + 'mgr': PlacementSpec(count=2), + 'mds': PlacementSpec(count=2), + 'rgw': PlacementSpec(count=2), + 'ingress': PlacementSpec(count=2), + 'iscsi': PlacementSpec(count=1), + 'rbd-mirror': PlacementSpec(count=2), + 'cephfs-mirror': PlacementSpec(count=1), + 'nfs': PlacementSpec(count=1), + 'grafana': PlacementSpec(count=1), + 'alertmanager': PlacementSpec(count=1), + 'prometheus': PlacementSpec(count=1), + 'node-exporter': PlacementSpec(host_pattern='*'), + 'crash': PlacementSpec(host_pattern='*'), + 'container': PlacementSpec(count=1), + 'cephadm-exporter': PlacementSpec(host_pattern='*'), + 'snmp-gateway': PlacementSpec(count=1), + } + spec.placement = defaults[spec.service_type] + elif spec.service_type in ['mon', 'mgr'] and \ + spec.placement.count is not None and \ + spec.placement.count < 1: + raise OrchestratorError('cannot scale %s service below 1' % ( + spec.service_type)) + + host_count = len(self.inventory.keys()) + max_count = self.max_count_per_host + + if spec.placement.count is not None: + if spec.service_type in ['mon', 'mgr']: + if spec.placement.count > max(5, host_count): + raise OrchestratorError( + (f'The maximum number of {spec.service_type} daemons allowed with {host_count} hosts is {max(5, host_count)}.')) + elif spec.service_type != 'osd': + if spec.placement.count > (max_count * host_count): + raise OrchestratorError((f'The maximum number of {spec.service_type} daemons allowed with {host_count} hosts is {host_count*max_count} ({host_count}x{max_count}).' + + ' This limit can be adjusted by changing the mgr/cephadm/max_count_per_host config option')) + + if spec.placement.count_per_host is not None and spec.placement.count_per_host > max_count and spec.service_type != 'osd': + raise OrchestratorError((f'The maximum count_per_host allowed is {max_count}.' + + ' This limit can be adjusted by changing the mgr/cephadm/max_count_per_host config option')) + + HostAssignment( + spec=spec, + hosts=self.inventory.all_specs(), # All hosts, even those without daemon refresh + unreachable_hosts=self._unreachable_hosts(), + networks=self.cache.networks, + daemons=self.cache.get_daemons_by_service(spec.service_name()), + allow_colo=self.cephadm_services[spec.service_type].allow_colo(), + ).validate() + + self.log.info('Saving service %s spec with placement %s' % ( + spec.service_name(), spec.placement.pretty_str())) + self.spec_store.save(spec) + self._kick_serve_loop() + return "Scheduled %s update..." % spec.service_name() + + @handle_orch_error + def apply(self, specs: Sequence[GenericSpec], no_overwrite: bool = False) -> List[str]: + results = [] + for spec in specs: + if no_overwrite: + if spec.service_type == 'host' and cast(HostSpec, spec).hostname in self.inventory: + results.append('Skipped %s host spec. To change %s spec omit --no-overwrite flag' + % (cast(HostSpec, spec).hostname, spec.service_type)) + continue + elif cast(ServiceSpec, spec).service_name() in self.spec_store: + results.append('Skipped %s service spec. To change %s spec omit --no-overwrite flag' + % (cast(ServiceSpec, spec).service_name(), cast(ServiceSpec, spec).service_name())) + continue + results.append(self._apply(spec)) + return results + + @handle_orch_error + def apply_mgr(self, spec: ServiceSpec) -> str: + return self._apply(spec) + + @handle_orch_error + def apply_mds(self, spec: ServiceSpec) -> str: + return self._apply(spec) + + @handle_orch_error + def apply_rgw(self, spec: ServiceSpec) -> str: + return self._apply(spec) + + @handle_orch_error + def apply_ingress(self, spec: ServiceSpec) -> str: + return self._apply(spec) + + @handle_orch_error + def apply_iscsi(self, spec: ServiceSpec) -> str: + return self._apply(spec) + + @handle_orch_error + def apply_rbd_mirror(self, spec: ServiceSpec) -> str: + return self._apply(spec) + + @handle_orch_error + def apply_nfs(self, spec: ServiceSpec) -> str: + return self._apply(spec) + + def _get_dashboard_url(self): + # type: () -> str + return self.get('mgr_map').get('services', {}).get('dashboard', '') + + @handle_orch_error + def apply_prometheus(self, spec: ServiceSpec) -> str: + return self._apply(spec) + + @handle_orch_error + def apply_node_exporter(self, spec: ServiceSpec) -> str: + return self._apply(spec) + + @handle_orch_error + def apply_crash(self, spec: ServiceSpec) -> str: + return self._apply(spec) + + @handle_orch_error + def apply_grafana(self, spec: ServiceSpec) -> str: + return self._apply(spec) + + @handle_orch_error + def apply_alertmanager(self, spec: ServiceSpec) -> str: + return self._apply(spec) + + @handle_orch_error + def apply_container(self, spec: ServiceSpec) -> str: + return self._apply(spec) + + @handle_orch_error + def apply_snmp_gateway(self, spec: ServiceSpec) -> str: + return self._apply(spec) + + @handle_orch_error + def apply_cephadm_exporter(self, spec: ServiceSpec) -> str: + return self._apply(spec) + + @handle_orch_error + def upgrade_check(self, image: str, version: str) -> str: + if self.inventory.get_host_with_state("maintenance"): + raise OrchestratorError("check aborted - you have hosts in maintenance state") + + if version: + target_name = self.container_image_base + ':v' + version + elif image: + target_name = image + else: + raise OrchestratorError('must specify either image or version') + + image_info = CephadmServe(self)._get_container_image_info(target_name) + + ceph_image_version = image_info.ceph_version + if not ceph_image_version: + return f'Unable to extract ceph version from {target_name}.' + if ceph_image_version.startswith('ceph version '): + ceph_image_version = ceph_image_version.split(' ')[2] + version_error = self.upgrade._check_target_version(ceph_image_version) + if version_error: + return f'Incompatible upgrade: {version_error}' + + self.log.debug(f'image info {image} -> {image_info}') + r: dict = { + 'target_name': target_name, + 'target_id': image_info.image_id, + 'target_version': image_info.ceph_version, + 'needs_update': dict(), + 'up_to_date': list(), + 'non_ceph_image_daemons': list() + } + for host, dm in self.cache.daemons.items(): + for name, dd in dm.items(): + if image_info.image_id == dd.container_image_id: + r['up_to_date'].append(dd.name()) + elif dd.daemon_type in CEPH_IMAGE_TYPES: + r['needs_update'][dd.name()] = { + 'current_name': dd.container_image_name, + 'current_id': dd.container_image_id, + 'current_version': dd.version, + } + else: + r['non_ceph_image_daemons'].append(dd.name()) + if self.use_repo_digest and image_info.repo_digests: + # FIXME: we assume the first digest is the best one to use + r['target_digest'] = image_info.repo_digests[0] + + return json.dumps(r, indent=4, sort_keys=True) + + @handle_orch_error + def upgrade_status(self) -> orchestrator.UpgradeStatusSpec: + return self.upgrade.upgrade_status() + + @handle_orch_error + def upgrade_ls(self, image: Optional[str], tags: bool) -> Dict[Any, Any]: + return self.upgrade.upgrade_ls(image, tags) + + @handle_orch_error + def upgrade_start(self, image: str, version: str, daemon_types: Optional[List[str]] = None, host_placement: Optional[str] = None, + services: Optional[List[str]] = None, limit: Optional[int] = None) -> str: + if self.inventory.get_host_with_state("maintenance"): + raise OrchestratorError("upgrade aborted - you have host(s) in maintenance state") + if daemon_types is not None and services is not None: + raise OrchestratorError('--daemon-types and --services are mutually exclusive') + if daemon_types is not None: + for dtype in daemon_types: + if dtype not in CEPH_UPGRADE_ORDER: + raise OrchestratorError(f'Upgrade aborted - Got unexpected daemon type "{dtype}".\n' + f'Viable daemon types for this command are: {utils.CEPH_TYPES + utils.GATEWAY_TYPES}') + if services is not None: + for service in services: + if service not in self.spec_store: + raise OrchestratorError(f'Upgrade aborted - Got unknown service name "{service}".\n' + f'Known services are: {self.spec_store.all_specs.keys()}') + hosts: Optional[List[str]] = None + if host_placement is not None: + all_hosts = list(self.inventory.all_specs()) + placement = PlacementSpec.from_string(host_placement) + hosts = placement.filter_matching_hostspecs(all_hosts) + if not hosts: + raise OrchestratorError( + f'Upgrade aborted - hosts parameter "{host_placement}" provided did not match any hosts') + + if limit is not None: + if limit < 1: + raise OrchestratorError(f'Upgrade aborted - --limit arg must be a positive integer, not {limit}') + + return self.upgrade.upgrade_start(image, version, daemon_types, hosts, services, limit) + + @handle_orch_error + def upgrade_pause(self) -> str: + return self.upgrade.upgrade_pause() + + @handle_orch_error + def upgrade_resume(self) -> str: + return self.upgrade.upgrade_resume() + + @handle_orch_error + def upgrade_stop(self) -> str: + return self.upgrade.upgrade_stop() + + @handle_orch_error + def remove_osds(self, osd_ids: List[str], + replace: bool = False, + force: bool = False, + zap: bool = False) -> str: + """ + Takes a list of OSDs and schedules them for removal. + The function that takes care of the actual removal is + process_removal_queue(). + """ + + daemons: List[orchestrator.DaemonDescription] = self.cache.get_daemons_by_type('osd') + to_remove_daemons = list() + for daemon in daemons: + if daemon.daemon_id in osd_ids: + to_remove_daemons.append(daemon) + if not to_remove_daemons: + return f"Unable to find OSDs: {osd_ids}" + + for daemon in to_remove_daemons: + assert daemon.daemon_id is not None + try: + self.to_remove_osds.enqueue(OSD(osd_id=int(daemon.daemon_id), + replace=replace, + force=force, + zap=zap, + hostname=daemon.hostname, + process_started_at=datetime_now(), + remove_util=self.to_remove_osds.rm_util)) + except NotFoundError: + return f"Unable to find OSDs: {osd_ids}" + + # trigger the serve loop to initiate the removal + self._kick_serve_loop() + return "Scheduled OSD(s) for removal" + + @handle_orch_error + def stop_remove_osds(self, osd_ids: List[str]) -> str: + """ + Stops a `removal` process for a List of OSDs. + This will revert their weight and remove it from the osds_to_remove queue + """ + for osd_id in osd_ids: + try: + self.to_remove_osds.rm(OSD(osd_id=int(osd_id), + remove_util=self.to_remove_osds.rm_util)) + except (NotFoundError, KeyError, ValueError): + return f'Unable to find OSD in the queue: {osd_id}' + + # trigger the serve loop to halt the removal + self._kick_serve_loop() + return "Stopped OSD(s) removal" + + @handle_orch_error + def remove_osds_status(self) -> List[OSD]: + """ + The CLI call to retrieve an osd removal report + """ + return self.to_remove_osds.all_osds() + + @handle_orch_error + def drain_host(self, hostname, force=False): + # type: (str, bool) -> str + """ + Drain all daemons from a host. + :param host: host name + """ + + # if we drain the last admin host we could end up removing the only instance + # of the config and keyring and cause issues + if not force: + p = PlacementSpec(label='_admin') + admin_hosts = p.filter_matching_hostspecs(self.inventory.all_specs()) + if len(admin_hosts) == 1 and admin_hosts[0] == hostname: + raise OrchestratorValidationError(f"Host {hostname} is the last host with the '_admin'" + " label.\nDraining this host could cause the removal" + " of the last cluster config/keyring managed by cephadm.\n" + "It is recommended to add the _admin label to another host" + " before completing this operation.\nIf you're certain this is" + " what you want rerun this command with --force.") + + self.add_host_label(hostname, '_no_schedule') + + daemons: List[orchestrator.DaemonDescription] = self.cache.get_daemons_by_host(hostname) + + osds_to_remove = [d.daemon_id for d in daemons if d.daemon_type == 'osd'] + self.remove_osds(osds_to_remove) + + daemons_table = "" + daemons_table += "{:<20} {:<15}\n".format("type", "id") + daemons_table += "{:<20} {:<15}\n".format("-" * 20, "-" * 15) + for d in daemons: + daemons_table += "{:<20} {:<15}\n".format(d.daemon_type, d.daemon_id) + + return "Scheduled to remove the following daemons from host '{}'\n{}".format(hostname, daemons_table) + + def trigger_connect_dashboard_rgw(self) -> None: + self.need_connect_dashboard_rgw = True + self.event.set() diff --git a/src/pybind/mgr/cephadm/offline_watcher.py b/src/pybind/mgr/cephadm/offline_watcher.py new file mode 100644 index 000000000..006156fc7 --- /dev/null +++ b/src/pybind/mgr/cephadm/offline_watcher.py @@ -0,0 +1,70 @@ +import logging +from typing import List, Optional, TYPE_CHECKING + +import multiprocessing as mp +import threading + +from cephadm.serve import CephadmServe + +try: + import remoto +except ImportError: + remoto = None + + +if TYPE_CHECKING: + from cephadm.module import CephadmOrchestrator + +logger = logging.getLogger(__name__) + + +class OfflineHostWatcher(threading.Thread): + def __init__(self, mgr: "CephadmOrchestrator") -> None: + self.mgr = mgr + self.hosts: Optional[List[str]] = None + self.new_hosts: Optional[List[str]] = None + self.stop = False + self.event = threading.Event() + super(OfflineHostWatcher, self).__init__(target=self.run) + + def run(self) -> None: + self.thread_pool = mp.pool.ThreadPool(10) + while not self.stop: + # only need to take action if we have hosts to check + if self.hosts or self.new_hosts: + if self.new_hosts: + self.hosts = self.new_hosts + self.new_hosts = None + logger.debug(f'OfflineHostDetector: Checking if hosts: {self.hosts} are offline.') + assert self.hosts is not None + self.thread_pool.map(self.check_host, self.hosts) + self.event.wait(20) + self.event.clear() + self.thread_pool.close() + self.thread_pool.join() + + def check_host(self, host: str) -> None: + if host not in self.mgr.offline_hosts: + try: + with CephadmServe(self.mgr)._remote_connection(host) as tpl: + conn, connr = tpl + out, err, code = remoto.process.check(conn, ['true']) + except Exception: + logger.debug(f'OfflineHostDetector: detected {host} to be offline') + # kick serve loop in case corrective action must be taken for offline host + self.mgr._kick_serve_loop() + + def set_hosts(self, hosts: List[str]) -> None: + hosts.sort() + if (not self.hosts or self.hosts != hosts) and hosts: + self.new_hosts = hosts + logger.debug( + f'OfflineHostDetector: Hosts to check if offline swapped to: {self.new_hosts}.') + self.wakeup() + + def wakeup(self) -> None: + self.event.set() + + def shutdown(self) -> None: + self.stop = True + self.wakeup() diff --git a/src/pybind/mgr/cephadm/registry.py b/src/pybind/mgr/cephadm/registry.py new file mode 100644 index 000000000..31e5fb23e --- /dev/null +++ b/src/pybind/mgr/cephadm/registry.py @@ -0,0 +1,65 @@ +import requests +from typing import List, Dict, Tuple +from requests import Response + + +class Registry: + + def __init__(self, url: str): + self._url: str = url + + @property + def api_domain(self) -> str: + if self._url == 'docker.io': + return 'registry-1.docker.io' + return self._url + + def get_token(self, response: Response) -> str: + realm, params = self.parse_www_authenticate(response.headers['Www-Authenticate']) + r = requests.get(realm, params=params) + r.raise_for_status() + ret = r.json() + if 'access_token' in ret: + return ret['access_token'] + if 'token' in ret: + return ret['token'] + raise ValueError(f'Unknown token reply {ret}') + + def parse_www_authenticate(self, text: str) -> Tuple[str, Dict[str, str]]: + # 'Www-Authenticate': 'Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:ceph/ceph:pull"' + r: Dict[str, str] = {} + for token in text.split(','): + key, value = token.split('=', 1) + r[key] = value.strip('"') + realm = r.pop('Bearer realm') + return realm, r + + def get_tags(self, image: str) -> List[str]: + tags = [] + headers = {'Accept': 'application/json'} + url = f'https://{self.api_domain}/v2/{image}/tags/list' + while True: + try: + r = requests.get(url, headers=headers) + except requests.exceptions.ConnectionError as e: + msg = f"Cannot get tags from url '{url}': {e}" + raise ValueError(msg) from e + if r.status_code == 401: + if 'Authorization' in headers: + raise ValueError('failed authentication') + token = self.get_token(r) + headers['Authorization'] = f'Bearer {token}' + continue + r.raise_for_status() + + new_tags = r.json()['tags'] + tags.extend(new_tags) + + if 'Link' not in r.headers: + break + + # strip < > brackets off and prepend the domain + url = f'https://{self.api_domain}' + r.headers['Link'].split(';')[0][1:-1] + continue + + return tags diff --git a/src/pybind/mgr/cephadm/remotes.py b/src/pybind/mgr/cephadm/remotes.py new file mode 100644 index 000000000..e1ecf2dcb --- /dev/null +++ b/src/pybind/mgr/cephadm/remotes.py @@ -0,0 +1,50 @@ +# ceph-deploy ftw +import os +try: + from typing import Optional +except ImportError: + pass + +PYTHONS = ['python3', 'python2', 'python'] +PATH = [ + '/usr/bin', + '/usr/local/bin', + '/bin', + '/usr/sbin', + '/usr/local/sbin', + '/sbin', +] + + +def choose_python(): + # type: () -> Optional[str] + for e in PYTHONS: + for b in PATH: + p = os.path.join(b, e) + if os.path.exists(p): + return p + return None + + +def write_file(path: str, content: bytes, mode: int, uid: int, gid: int, + mkdir_p: bool = True) -> Optional[str]: + try: + if mkdir_p: + dirname = os.path.dirname(path) + if not os.path.exists(dirname): + os.makedirs(dirname) + tmp_path = path + '.new' + with open(tmp_path, 'wb') as f: + os.fchown(f.fileno(), uid, gid) + os.fchmod(f.fileno(), mode) + f.write(content) + os.fsync(f.fileno()) + os.rename(tmp_path, path) + except Exception as e: + return str(e) + return None + + +if __name__ == '__channelexec__': + for item in channel: # type: ignore # noqa: F821 + channel.send(eval(item)) # type: ignore # noqa: F821 diff --git a/src/pybind/mgr/cephadm/schedule.py b/src/pybind/mgr/cephadm/schedule.py new file mode 100644 index 000000000..a727f5049 --- /dev/null +++ b/src/pybind/mgr/cephadm/schedule.py @@ -0,0 +1,447 @@ +import hashlib +import logging +import random +from typing import List, Optional, Callable, TypeVar, Tuple, NamedTuple, Dict + +import orchestrator +from ceph.deployment.service_spec import ServiceSpec +from orchestrator._interface import DaemonDescription +from orchestrator import OrchestratorValidationError +from .utils import RESCHEDULE_FROM_OFFLINE_HOSTS_TYPES + +logger = logging.getLogger(__name__) +T = TypeVar('T') + + +class DaemonPlacement(NamedTuple): + daemon_type: str + hostname: str + network: str = '' # for mons only + name: str = '' + ip: Optional[str] = None + ports: List[int] = [] + rank: Optional[int] = None + rank_generation: Optional[int] = None + + def __str__(self) -> str: + res = self.daemon_type + ':' + self.hostname + other = [] + if self.rank is not None: + other.append(f'rank={self.rank}.{self.rank_generation}') + if self.network: + other.append(f'network={self.network}') + if self.name: + other.append(f'name={self.name}') + if self.ports: + other.append(f'{self.ip or "*"}:{",".join(map(str, self.ports))}') + if other: + res += '(' + ' '.join(other) + ')' + return res + + def renumber_ports(self, n: int) -> 'DaemonPlacement': + return DaemonPlacement( + self.daemon_type, + self.hostname, + self.network, + self.name, + self.ip, + [p + n for p in self.ports], + self.rank, + self.rank_generation, + ) + + def assign_rank(self, rank: int, gen: int) -> 'DaemonPlacement': + return DaemonPlacement( + self.daemon_type, + self.hostname, + self.network, + self.name, + self.ip, + self.ports, + rank, + gen, + ) + + def assign_name(self, name: str) -> 'DaemonPlacement': + return DaemonPlacement( + self.daemon_type, + self.hostname, + self.network, + name, + self.ip, + self.ports, + self.rank, + self.rank_generation, + ) + + def assign_rank_generation( + self, + rank: int, + rank_map: Dict[int, Dict[int, Optional[str]]] + ) -> 'DaemonPlacement': + if rank not in rank_map: + rank_map[rank] = {} + gen = 0 + else: + gen = max(rank_map[rank].keys()) + 1 + rank_map[rank][gen] = None + return DaemonPlacement( + self.daemon_type, + self.hostname, + self.network, + self.name, + self.ip, + self.ports, + rank, + gen, + ) + + def matches_daemon(self, dd: DaemonDescription) -> bool: + if self.daemon_type != dd.daemon_type: + return False + if self.hostname != dd.hostname: + return False + # fixme: how to match against network? + if self.name and self.name != dd.daemon_id: + return False + if self.ports: + if self.ports != dd.ports and dd.ports: + return False + if self.ip != dd.ip and dd.ip: + return False + return True + + def matches_rank_map( + self, + dd: DaemonDescription, + rank_map: Optional[Dict[int, Dict[int, Optional[str]]]], + ranks: List[int] + ) -> bool: + if rank_map is None: + # daemon should have no rank + return dd.rank is None + + if dd.rank is None: + return False + + if dd.rank not in rank_map: + return False + if dd.rank not in ranks: + return False + + # must be the highest/newest rank_generation + if dd.rank_generation != max(rank_map[dd.rank].keys()): + return False + + # must be *this* daemon + return rank_map[dd.rank][dd.rank_generation] == dd.daemon_id + + +class HostAssignment(object): + + def __init__(self, + spec: ServiceSpec, + hosts: List[orchestrator.HostSpec], + unreachable_hosts: List[orchestrator.HostSpec], + daemons: List[orchestrator.DaemonDescription], + networks: Dict[str, Dict[str, Dict[str, List[str]]]] = {}, + filter_new_host: Optional[Callable[[str], bool]] = None, + allow_colo: bool = False, + primary_daemon_type: Optional[str] = None, + per_host_daemon_type: Optional[str] = None, + rank_map: Optional[Dict[int, Dict[int, Optional[str]]]] = None, + ): + assert spec + self.spec = spec # type: ServiceSpec + self.primary_daemon_type = primary_daemon_type or spec.service_type + self.hosts: List[orchestrator.HostSpec] = hosts + self.unreachable_hosts: List[orchestrator.HostSpec] = unreachable_hosts + self.filter_new_host = filter_new_host + self.service_name = spec.service_name() + self.daemons = daemons + self.networks = networks + self.allow_colo = allow_colo + self.per_host_daemon_type = per_host_daemon_type + self.ports_start = spec.get_port_start() + self.rank_map = rank_map + + def hosts_by_label(self, label: str) -> List[orchestrator.HostSpec]: + return [h for h in self.hosts if label in h.labels] + + def get_hostnames(self) -> List[str]: + return [h.hostname for h in self.hosts] + + def validate(self) -> None: + self.spec.validate() + + if self.spec.placement.count == 0: + raise OrchestratorValidationError( + f' can not be 0 for {self.spec.one_line_str()}') + + if ( + self.spec.placement.count_per_host is not None + and self.spec.placement.count_per_host > 1 + and not self.allow_colo + ): + raise OrchestratorValidationError( + f'Cannot place more than one {self.spec.service_type} per host' + ) + + if self.spec.placement.hosts: + explicit_hostnames = {h.hostname for h in self.spec.placement.hosts} + unknown_hosts = explicit_hostnames.difference(set(self.get_hostnames())) + if unknown_hosts: + raise OrchestratorValidationError( + f'Cannot place {self.spec.one_line_str()} on {", ".join(sorted(unknown_hosts))}: Unknown hosts') + + if self.spec.placement.host_pattern: + pattern_hostnames = self.spec.placement.filter_matching_hostspecs(self.hosts) + if not pattern_hostnames: + raise OrchestratorValidationError( + f'Cannot place {self.spec.one_line_str()}: No matching hosts') + + if self.spec.placement.label: + label_hosts = self.hosts_by_label(self.spec.placement.label) + if not label_hosts: + raise OrchestratorValidationError( + f'Cannot place {self.spec.one_line_str()}: No matching ' + f'hosts for label {self.spec.placement.label}') + + def place_per_host_daemons( + self, + slots: List[DaemonPlacement], + to_add: List[DaemonPlacement], + to_remove: List[orchestrator.DaemonDescription], + ) -> Tuple[List[DaemonPlacement], List[DaemonPlacement], List[orchestrator.DaemonDescription]]: + if self.per_host_daemon_type: + host_slots = [ + DaemonPlacement(daemon_type=self.per_host_daemon_type, + hostname=hostname) + for hostname in set([s.hostname for s in slots]) + ] + existing = [ + d for d in self.daemons if d.daemon_type == self.per_host_daemon_type + ] + slots += host_slots + for dd in existing: + found = False + for p in host_slots: + if p.matches_daemon(dd): + host_slots.remove(p) + found = True + break + if not found: + to_remove.append(dd) + to_add += host_slots + + to_remove = [d for d in to_remove if d.hostname not in [ + h.hostname for h in self.unreachable_hosts]] + + return slots, to_add, to_remove + + def place(self): + # type: () -> Tuple[List[DaemonPlacement], List[DaemonPlacement], List[orchestrator.DaemonDescription]] + """ + Generate a list of HostPlacementSpec taking into account: + + * all known hosts + * hosts with existing daemons + * placement spec + * self.filter_new_host + """ + + self.validate() + + count = self.spec.placement.count + + # get candidate hosts based on [hosts, label, host_pattern] + candidates = self.get_candidates() # type: List[DaemonPlacement] + if self.primary_daemon_type in RESCHEDULE_FROM_OFFLINE_HOSTS_TYPES: + # remove unreachable hosts that are not in maintenance so daemons + # on these hosts will be rescheduled + candidates = self.remove_non_maintenance_unreachable_candidates(candidates) + + def expand_candidates(ls: List[DaemonPlacement], num: int) -> List[DaemonPlacement]: + r = [] + for offset in range(num): + r.extend([dp.renumber_ports(offset) for dp in ls]) + return r + + # consider enough slots to fulfill target count-per-host or count + if count is None: + if self.spec.placement.count_per_host: + per_host = self.spec.placement.count_per_host + else: + per_host = 1 + candidates = expand_candidates(candidates, per_host) + elif self.allow_colo and candidates: + per_host = 1 + ((count - 1) // len(candidates)) + candidates = expand_candidates(candidates, per_host) + + # consider (preserve) existing daemons in a particular order... + daemons = sorted( + [ + d for d in self.daemons if d.daemon_type == self.primary_daemon_type + ], + key=lambda d: ( + not d.is_active, # active before standby + d.rank is not None, # ranked first, then non-ranked + d.rank, # low ranks + 0 - (d.rank_generation or 0), # newer generations first + ) + ) + + # sort candidates into existing/used slots that already have a + # daemon, and others (the rest) + existing_active: List[orchestrator.DaemonDescription] = [] + existing_standby: List[orchestrator.DaemonDescription] = [] + existing_slots: List[DaemonPlacement] = [] + to_add: List[DaemonPlacement] = [] + to_remove: List[orchestrator.DaemonDescription] = [] + ranks: List[int] = list(range(len(candidates))) + others: List[DaemonPlacement] = candidates.copy() + for dd in daemons: + found = False + for p in others: + if p.matches_daemon(dd) and p.matches_rank_map(dd, self.rank_map, ranks): + others.remove(p) + if dd.is_active: + existing_active.append(dd) + else: + existing_standby.append(dd) + if dd.rank is not None: + assert dd.rank_generation is not None + p = p.assign_rank(dd.rank, dd.rank_generation) + ranks.remove(dd.rank) + existing_slots.append(p) + found = True + break + if not found: + to_remove.append(dd) + + # TODO: At some point we want to deploy daemons that are on offline hosts + # at what point we do this differs per daemon type. Stateless daemons we could + # do quickly to improve availability. Steful daemons we might want to wait longer + # to see if the host comes back online + + existing = existing_active + existing_standby + + # build to_add + if not count: + to_add = [dd for dd in others if dd.hostname not in [ + h.hostname for h in self.unreachable_hosts]] + else: + # The number of new slots that need to be selected in order to fulfill count + need = count - len(existing) + + # we don't need any additional placements + if need <= 0: + to_remove.extend(existing[count:]) + del existing_slots[count:] + return self.place_per_host_daemons(existing_slots, [], to_remove) + + for dp in others: + if need <= 0: + break + if dp.hostname not in [h.hostname for h in self.unreachable_hosts]: + to_add.append(dp) + need -= 1 # this is last use of need in this function so it can work as a counter + + if self.rank_map is not None: + # assign unused ranks (and rank_generations) to to_add + assert len(ranks) >= len(to_add) + for i in range(len(to_add)): + to_add[i] = to_add[i].assign_rank_generation(ranks[i], self.rank_map) + + logger.debug('Combine hosts with existing daemons %s + new hosts %s' % (existing, to_add)) + return self.place_per_host_daemons(existing_slots + to_add, to_add, to_remove) + + def find_ip_on_host(self, hostname: str, subnets: List[str]) -> Optional[str]: + for subnet in subnets: + ips: List[str] = [] + for iface, iface_ips in self.networks.get(hostname, {}).get(subnet, {}).items(): + ips.extend(iface_ips) + if ips: + return sorted(ips)[0] + return None + + def get_candidates(self) -> List[DaemonPlacement]: + if self.spec.placement.hosts: + ls = [ + DaemonPlacement(daemon_type=self.primary_daemon_type, + hostname=h.hostname, network=h.network, name=h.name, + ports=self.ports_start) + for h in self.spec.placement.hosts + ] + elif self.spec.placement.label: + ls = [ + DaemonPlacement(daemon_type=self.primary_daemon_type, + hostname=x.hostname, ports=self.ports_start) + for x in self.hosts_by_label(self.spec.placement.label) + ] + elif self.spec.placement.host_pattern: + ls = [ + DaemonPlacement(daemon_type=self.primary_daemon_type, + hostname=x, ports=self.ports_start) + for x in self.spec.placement.filter_matching_hostspecs(self.hosts) + ] + elif ( + self.spec.placement.count is not None + or self.spec.placement.count_per_host is not None + ): + ls = [ + DaemonPlacement(daemon_type=self.primary_daemon_type, + hostname=x.hostname, ports=self.ports_start) + for x in self.hosts + ] + else: + raise OrchestratorValidationError( + "placement spec is empty: no hosts, no label, no pattern, no count") + + # allocate an IP? + if self.spec.networks: + orig = ls.copy() + ls = [] + for p in orig: + ip = self.find_ip_on_host(p.hostname, self.spec.networks) + if ip: + ls.append(DaemonPlacement(daemon_type=self.primary_daemon_type, + hostname=p.hostname, network=p.network, + name=p.name, ports=p.ports, ip=ip)) + else: + logger.debug( + f'Skipping {p.hostname} with no IP in network(s) {self.spec.networks}' + ) + + if self.filter_new_host: + old = ls.copy() + ls = [] + for h in old: + if self.filter_new_host(h.hostname): + ls.append(h) + if len(old) > len(ls): + logger.debug('Filtered %s down to %s' % (old, ls)) + + # now that we have the list of nodes candidates based on the configured + # placement, let's shuffle the list for node pseudo-random selection. For this, + # we generate a seed from the service name and we use to shuffle the candidates. + # This makes shuffling deterministic for the same service name. + seed = int( + hashlib.sha1(self.spec.service_name().encode('utf-8')).hexdigest(), + 16 + ) % (2 ** 32) # truncate result to 32 bits + final = sorted(ls) + random.Random(seed).shuffle(final) + return final + + def remove_non_maintenance_unreachable_candidates(self, candidates: List[DaemonPlacement]) -> List[DaemonPlacement]: + in_maintenance: Dict[str, bool] = {} + for h in self.hosts: + if h.status.lower() == 'maintenance': + in_maintenance[h.hostname] = True + continue + in_maintenance[h.hostname] = False + unreachable_hosts = [h.hostname for h in self.unreachable_hosts] + candidates = [ + c for c in candidates if c.hostname not in unreachable_hosts or in_maintenance[c.hostname]] + return candidates diff --git a/src/pybind/mgr/cephadm/serve.py b/src/pybind/mgr/cephadm/serve.py new file mode 100644 index 000000000..7ac6fee88 --- /dev/null +++ b/src/pybind/mgr/cephadm/serve.py @@ -0,0 +1,1487 @@ +import hashlib +import json +import logging +import uuid +from collections import defaultdict +from contextlib import contextmanager +from typing import TYPE_CHECKING, Optional, List, cast, Dict, Any, Union, Tuple, Iterator, \ + DefaultDict + +from cephadm import remotes + +try: + import remoto + import execnet.gateway_bootstrap +except ImportError: + remoto = None + +from ceph.deployment import inventory +from ceph.deployment.drive_group import DriveGroupSpec +from ceph.deployment.service_spec import ServiceSpec, CustomContainerSpec, PlacementSpec +from ceph.utils import str_to_datetime, datetime_now + +import orchestrator +from orchestrator import OrchestratorError, set_exception_subject, OrchestratorEvent, \ + DaemonDescriptionStatus, daemon_type_to_service +from cephadm.services.cephadmservice import CephadmDaemonDeploySpec +from cephadm.schedule import HostAssignment +from cephadm.autotune import MemoryAutotuner +from cephadm.utils import forall_hosts, cephadmNoImage, is_repo_digest, \ + CephadmNoImage, CEPH_TYPES, ContainerInspectInfo +from mgr_module import MonCommandFailed +from mgr_util import format_bytes + +from . import utils + +if TYPE_CHECKING: + from cephadm.module import CephadmOrchestrator + from remoto.backends import BaseConnection + +logger = logging.getLogger(__name__) + +REQUIRES_POST_ACTIONS = ['grafana', 'iscsi', 'prometheus', 'alertmanager', 'rgw'] + + +class CephadmServe: + """ + This module contains functions that are executed in the + serve() thread. Thus they don't block the CLI. + + Please see the `Note regarding network calls from CLI handlers` + chapter in the cephadm developer guide. + + On the other hand, These function should *not* be called form + CLI handlers, to avoid blocking the CLI + """ + + def __init__(self, mgr: "CephadmOrchestrator"): + self.mgr: "CephadmOrchestrator" = mgr + self.log = logger + + def serve(self) -> None: + """ + The main loop of cephadm. + + A command handler will typically change the declarative state + of cephadm. This loop will then attempt to apply this new state. + """ + self.log.debug("serve starting") + self.mgr.config_checker.load_network_config() + + while self.mgr.run: + self.log.debug("serve loop start") + + try: + + self.convert_tags_to_repo_digest() + + # refresh daemons + self.log.debug('refreshing hosts and daemons') + self._refresh_hosts_and_daemons() + + self._check_for_strays() + + self._update_paused_health() + + if self.mgr.need_connect_dashboard_rgw and self.mgr.config_dashboard: + self.mgr.need_connect_dashboard_rgw = False + if 'dashboard' in self.mgr.get('mgr_map')['modules']: + self.log.info('Checking dashboard <-> RGW credentials') + self.mgr.remote('dashboard', 'set_rgw_credentials') + + if not self.mgr.paused: + self.mgr.to_remove_osds.process_removal_queue() + + self.mgr.migration.migrate() + if self.mgr.migration.is_migration_ongoing(): + continue + + if self._apply_all_services(): + continue # did something, refresh + + self._check_daemons() + + self._purge_deleted_services() + + self._check_for_moved_osds() + + if self.mgr.upgrade.continue_upgrade(): + continue + + except OrchestratorError as e: + if e.event_subject: + self.mgr.events.from_orch_error(e) + + self.log.debug("serve loop sleep") + self._serve_sleep() + self.log.debug("serve loop wake") + self.log.debug("serve exit") + + def _serve_sleep(self) -> None: + sleep_interval = max( + 30, + min( + self.mgr.host_check_interval, + self.mgr.facts_cache_timeout, + self.mgr.daemon_cache_timeout, + self.mgr.device_cache_timeout, + ) + ) + self.log.debug('Sleeping for %d seconds', sleep_interval) + self.mgr.event.wait(sleep_interval) + self.mgr.event.clear() + + def _update_paused_health(self) -> None: + self.log.debug('_update_paused_health') + if self.mgr.paused: + self.mgr.set_health_warning('CEPHADM_PAUSED', 'cephadm background work is paused', 1, ["'ceph orch resume' to resume"]) + else: + self.mgr.remove_health_warning('CEPHADM_PAUSED') + + def _autotune_host_memory(self, host: str) -> None: + total_mem = self.mgr.cache.get_facts(host).get('memory_total_kb', 0) + if not total_mem: + val = None + else: + total_mem *= 1024 # kb -> bytes + total_mem *= self.mgr.autotune_memory_target_ratio + a = MemoryAutotuner( + daemons=self.mgr.cache.get_daemons_by_host(host), + config_get=self.mgr.get_foreign_ceph_option, + total_mem=total_mem, + ) + val, osds = a.tune() + any_changed = False + for o in osds: + if self.mgr.get_foreign_ceph_option(o, 'osd_memory_target') != val: + self.mgr.check_mon_command({ + 'prefix': 'config rm', + 'who': o, + 'name': 'osd_memory_target', + }) + any_changed = True + if val is not None: + if any_changed: + self.mgr.log.info( + f'Adjusting osd_memory_target on {host} to {format_bytes(val, 6)}' + ) + ret, out, err = self.mgr.mon_command({ + 'prefix': 'config set', + 'who': f'osd/host:{host.split(".")[0]}', + 'name': 'osd_memory_target', + 'value': str(val), + }) + if ret: + self.log.warning( + f'Unable to set osd_memory_target on {host} to {val}: {err}' + ) + else: + # if osd memory autotuning is off, we don't want to remove these config + # options as users may be using them. Since there is no way to set autotuning + # on/off at a host level, best we can do is check if it is globally on. + if self.mgr.get_foreign_ceph_option('osd', 'osd_memory_target_autotune'): + self.mgr.check_mon_command({ + 'prefix': 'config rm', + 'who': f'osd/host:{host.split(".")[0]}', + 'name': 'osd_memory_target', + }) + self.mgr.cache.update_autotune(host) + + def _refresh_hosts_and_daemons(self) -> None: + self.log.debug('_refresh_hosts_and_daemons') + bad_hosts = [] + failures = [] + + if self.mgr.manage_etc_ceph_ceph_conf or self.mgr.keys.keys: + client_files = self._calc_client_files() + else: + client_files = {} + + @forall_hosts + def refresh(host: str) -> None: + + # skip hosts that are in maintenance - they could be powered off + if self.mgr.inventory._inventory[host].get("status", "").lower() == "maintenance": + return + + if self.mgr.cache.host_needs_check(host): + r = self._check_host(host) + if r is not None: + bad_hosts.append(r) + if self.mgr.cache.host_needs_daemon_refresh(host): + self.log.debug('refreshing %s daemons' % host) + r = self._refresh_host_daemons(host) + if r: + failures.append(r) + + if self.mgr.cache.host_needs_registry_login(host) and self.mgr.get_store('registry_credentials'): + self.log.debug(f"Logging `{host}` into custom registry") + r = self._registry_login( + host, json.loads(str(self.mgr.get_store('registry_credentials')))) + if r: + bad_hosts.append(r) + + if self.mgr.cache.host_needs_device_refresh(host): + self.log.debug('refreshing %s devices' % host) + r = self._refresh_host_devices(host) + if r: + failures.append(r) + + if self.mgr.cache.host_needs_facts_refresh(host): + self.log.debug(('Refreshing %s facts' % host)) + r = self._refresh_facts(host) + if r: + failures.append(r) + + if self.mgr.cache.host_needs_osdspec_preview_refresh(host): + self.log.debug(f"refreshing OSDSpec previews for {host}") + r = self._refresh_host_osdspec_previews(host) + if r: + failures.append(r) + + if ( + self.mgr.cache.host_needs_autotune_memory(host) + and not self.mgr.inventory.has_label(host, '_no_autotune_memory') + ): + self.log.debug(f"autotuning memory for {host}") + self._autotune_host_memory(host) + + self._write_client_files(client_files, host) + + refresh(self.mgr.cache.get_hosts()) + + self.mgr.config_checker.run_checks() + + for k in [ + 'CEPHADM_HOST_CHECK_FAILED', + 'CEPHADM_FAILED_DAEMON', + 'CEPHADM_REFRESH_FAILED', + ]: + self.mgr.remove_health_warning(k) + if bad_hosts: + self.mgr.set_health_warning('CEPHADM_HOST_CHECK_FAILED', f'{len(bad_hosts)} hosts fail cephadm check', len(bad_hosts), bad_hosts) + if failures: + self.mgr.set_health_warning('CEPHADM_REFRESH_FAILED', 'failed to probe daemons or devices', len(failures), failures) + failed_daemons = [] + for dd in self.mgr.cache.get_daemons(): + if dd.status is not None and dd.status == DaemonDescriptionStatus.error: + failed_daemons.append('daemon %s on %s is in %s state' % ( + dd.name(), dd.hostname, dd.status_desc + )) + if failed_daemons: + self.mgr.set_health_warning('CEPHADM_FAILED_DAEMON', f'{len(failed_daemons)} failed cephadm daemon(s)', len(failed_daemons), failed_daemons) + + def _check_host(self, host: str) -> Optional[str]: + if host not in self.mgr.inventory: + return None + self.log.debug(' checking %s' % host) + try: + addr = self.mgr.inventory.get_addr(host) if host in self.mgr.inventory else host + out, err, code = self._run_cephadm( + host, cephadmNoImage, 'check-host', [], + error_ok=True, no_fsid=True) + self.mgr.cache.update_last_host_check(host) + self.mgr.cache.save_host(host) + if code: + self.log.debug(' host %s (%s) failed check' % (host, addr)) + if self.mgr.warn_on_failed_host_check: + return 'host %s (%s) failed check: %s' % (host, addr, err) + else: + self.log.debug(' host %s (%s) ok' % (host, addr)) + except Exception as e: + self.log.debug(' host %s (%s) failed check' % (host, addr)) + return 'host %s (%s) failed check: %s' % (host, addr, e) + return None + + def _refresh_host_daemons(self, host: str) -> Optional[str]: + try: + ls = self._run_cephadm_json(host, 'mon', 'ls', [], no_fsid=True) + except OrchestratorError as e: + return str(e) + dm = {} + for d in ls: + if not d['style'].startswith('cephadm'): + continue + if d['fsid'] != self.mgr._cluster_fsid: + continue + if '.' not in d['name']: + continue + sd = orchestrator.DaemonDescription() + sd.last_refresh = datetime_now() + for k in ['created', 'started', 'last_configured', 'last_deployed']: + v = d.get(k, None) + if v: + setattr(sd, k, str_to_datetime(d[k])) + sd.daemon_type = d['name'].split('.')[0] + if sd.daemon_type not in orchestrator.KNOWN_DAEMON_TYPES: + logger.warning(f"Found unknown daemon type {sd.daemon_type} on host {host}") + continue + + sd.daemon_id = '.'.join(d['name'].split('.')[1:]) + sd.hostname = host + sd.container_id = d.get('container_id') + if sd.container_id: + # shorten the hash + sd.container_id = sd.container_id[0:12] + sd.container_image_name = d.get('container_image_name') + sd.container_image_id = d.get('container_image_id') + sd.container_image_digests = d.get('container_image_digests') + sd.memory_usage = d.get('memory_usage') + sd.memory_request = d.get('memory_request') + sd.memory_limit = d.get('memory_limit') + sd.cpu_percentage = d.get('cpu_percentage') + sd._service_name = d.get('service_name') + sd.deployed_by = d.get('deployed_by') + sd.version = d.get('version') + sd.ports = d.get('ports') + sd.ip = d.get('ip') + sd.rank = int(d['rank']) if d.get('rank') is not None else None + sd.rank_generation = int(d['rank_generation']) if d.get( + 'rank_generation') is not None else None + sd.extra_container_args = d.get('extra_container_args') + if 'state' in d: + sd.status_desc = d['state'] + sd.status = { + 'running': DaemonDescriptionStatus.running, + 'stopped': DaemonDescriptionStatus.stopped, + 'error': DaemonDescriptionStatus.error, + 'unknown': DaemonDescriptionStatus.error, + }[d['state']] + else: + sd.status_desc = 'unknown' + sd.status = None + dm[sd.name()] = sd + self.log.debug('Refreshed host %s daemons (%d)' % (host, len(dm))) + self.mgr.cache.update_host_daemons(host, dm) + self.mgr.cache.save_host(host) + return None + + def _refresh_facts(self, host: str) -> Optional[str]: + try: + val = self._run_cephadm_json(host, cephadmNoImage, 'gather-facts', [], no_fsid=True) + except OrchestratorError as e: + return str(e) + + self.mgr.cache.update_host_facts(host, val) + + return None + + def _refresh_host_devices(self, host: str) -> Optional[str]: + with_lsm = self.mgr.device_enhanced_scan + inventory_args = ['--', 'inventory', + '--format=json-pretty', + '--filter-for-batch'] + if with_lsm: + inventory_args.insert(-1, "--with-lsm") + + try: + try: + devices = self._run_cephadm_json(host, 'osd', 'ceph-volume', + inventory_args) + except OrchestratorError as e: + if 'unrecognized arguments: --filter-for-batch' in str(e): + rerun_args = inventory_args.copy() + rerun_args.remove('--filter-for-batch') + devices = self._run_cephadm_json(host, 'osd', 'ceph-volume', + rerun_args) + else: + raise + + networks = self._run_cephadm_json(host, 'mon', 'list-networks', [], no_fsid=True) + except OrchestratorError as e: + return str(e) + + self.log.debug('Refreshed host %s devices (%d) networks (%s)' % ( + host, len(devices), len(networks))) + ret = inventory.Devices.from_json(devices) + self.mgr.cache.update_host_devices_networks(host, ret.devices, networks) + self.update_osdspec_previews(host) + self.mgr.cache.save_host(host) + return None + + def _refresh_host_osdspec_previews(self, host: str) -> Optional[str]: + self.update_osdspec_previews(host) + self.mgr.cache.save_host(host) + self.log.debug(f'Refreshed OSDSpec previews for host <{host}>') + return None + + def update_osdspec_previews(self, search_host: str = '') -> None: + # Set global 'pending' flag for host + self.mgr.cache.loading_osdspec_preview.add(search_host) + previews = [] + # query OSDSpecs for host and generate/get the preview + # There can be multiple previews for one host due to multiple OSDSpecs. + previews.extend(self.mgr.osd_service.get_previews(search_host)) + self.log.debug(f'Loading OSDSpec previews to HostCache for host <{search_host}>') + self.mgr.cache.osdspec_previews[search_host] = previews + # Unset global 'pending' flag for host + self.mgr.cache.loading_osdspec_preview.remove(search_host) + + def _check_for_strays(self) -> None: + self.log.debug('_check_for_strays') + for k in ['CEPHADM_STRAY_HOST', + 'CEPHADM_STRAY_DAEMON']: + self.mgr.remove_health_warning(k) + if self.mgr.warn_on_stray_hosts or self.mgr.warn_on_stray_daemons: + ls = self.mgr.list_servers() + self.log.debug(ls) + managed = self.mgr.cache.get_daemon_names() + host_detail = [] # type: List[str] + host_num_daemons = 0 + daemon_detail = [] # type: List[str] + for item in ls: + host = item.get('hostname') + assert isinstance(host, str) + daemons = item.get('services') # misnomer! + assert isinstance(daemons, list) + missing_names = [] + for s in daemons: + daemon_id = s.get('id') + assert daemon_id + name = '%s.%s' % (s.get('type'), daemon_id) + if s.get('type') in ['rbd-mirror', 'cephfs-mirror', 'rgw', 'rgw-nfs']: + metadata = self.mgr.get_metadata( + cast(str, s.get('type')), daemon_id, {}) + assert metadata is not None + try: + if s.get('type') == 'rgw-nfs': + # https://tracker.ceph.com/issues/49573 + name = metadata['id'][:-4] + else: + name = '%s.%s' % (s.get('type'), metadata['id']) + except (KeyError, TypeError): + self.log.debug( + "Failed to find daemon id for %s service %s" % ( + s.get('type'), s.get('id') + ) + ) + if s.get('type') == 'tcmu-runner': + # because we don't track tcmu-runner daemons in the host cache + # and don't have a way to check if the daemon is part of iscsi service + # we assume that all tcmu-runner daemons are managed by cephadm + managed.append(name) + if host not in self.mgr.inventory: + missing_names.append(name) + host_num_daemons += 1 + if name not in managed: + daemon_detail.append( + 'stray daemon %s on host %s not managed by cephadm' % (name, host)) + if missing_names: + host_detail.append( + 'stray host %s has %d stray daemons: %s' % ( + host, len(missing_names), missing_names)) + if self.mgr.warn_on_stray_hosts and host_detail: + self.mgr.set_health_warning( + 'CEPHADM_STRAY_HOST', f'{len(host_detail)} stray host(s) with {host_num_daemons} daemon(s) not managed by cephadm', len(host_detail), host_detail) + if self.mgr.warn_on_stray_daemons and daemon_detail: + self.mgr.set_health_warning( + 'CEPHADM_STRAY_DAEMON', f'{len(daemon_detail)} stray daemon(s) not managed by cephadm', len(daemon_detail), daemon_detail) + + def _check_for_moved_osds(self) -> None: + self.log.debug('_check_for_moved_osds') + all_osds: DefaultDict[int, List[orchestrator.DaemonDescription]] = defaultdict(list) + for dd in self.mgr.cache.get_daemons_by_type('osd'): + assert dd.daemon_id + all_osds[int(dd.daemon_id)].append(dd) + for osd_id, dds in all_osds.items(): + if len(dds) <= 1: + continue + running = [dd for dd in dds if dd.status == DaemonDescriptionStatus.running] + error = [dd for dd in dds if dd.status == DaemonDescriptionStatus.error] + msg = f'Found duplicate OSDs: {", ".join(str(dd) for dd in dds)}' + logger.info(msg) + if len(running) != 1: + continue + osd = self.mgr.get_osd_by_id(osd_id) + if not osd or not osd['up']: + continue + for e in error: + assert e.hostname + try: + self._remove_daemon(e.name(), e.hostname, no_post_remove=True) + self.mgr.events.for_daemon( + e.name(), 'INFO', f"Removed duplicated daemon on host '{e.hostname}'") + except OrchestratorError as ex: + self.mgr.events.from_orch_error(ex) + logger.exception(f'failed to remove duplicated daemon {e}') + + def _apply_all_services(self) -> bool: + self.log.debug('_apply_all_services') + r = False + specs = [] # type: List[ServiceSpec] + for sn, spec in self.mgr.spec_store.active_specs.items(): + specs.append(spec) + for name in ['CEPHADM_APPLY_SPEC_FAIL', 'CEPHADM_DAEMON_PLACE_FAIL']: + self.mgr.remove_health_warning(name) + self.mgr.apply_spec_fails = [] + for spec in specs: + try: + if self._apply_service(spec): + r = True + except Exception as e: + msg = f'Failed to apply {spec.service_name()} spec {spec}: {str(e)}' + self.log.exception(msg) + self.mgr.events.for_service(spec, 'ERROR', 'Failed to apply: ' + str(e)) + self.mgr.apply_spec_fails.append((spec.service_name(), str(e))) + warnings = [] + for x in self.mgr.apply_spec_fails: + warnings.append(f'{x[0]}: {x[1]}') + self.mgr.set_health_warning('CEPHADM_APPLY_SPEC_FAIL', + f"Failed to apply {len(self.mgr.apply_spec_fails)} service(s): {','.join(x[0] for x in self.mgr.apply_spec_fails)}", + len(self.mgr.apply_spec_fails), + warnings) + self.mgr.update_watched_hosts() + return r + + def _apply_service_config(self, spec: ServiceSpec) -> None: + if spec.config: + section = utils.name_to_config_section(spec.service_name()) + for name in ['CEPHADM_INVALID_CONFIG_OPTION', 'CEPHADM_FAILED_SET_OPTION']: + self.mgr.remove_health_warning(name) + invalid_config_options = [] + options_failed_to_set = [] + for k, v in spec.config.items(): + try: + current = self.mgr.get_foreign_ceph_option(section, k) + except KeyError: + msg = f'Ignoring invalid {spec.service_name()} config option {k}' + self.log.warning(msg) + self.mgr.events.for_service( + spec, OrchestratorEvent.ERROR, f'Invalid config option {k}' + ) + invalid_config_options.append(msg) + continue + if current != v: + self.log.debug(f'setting [{section}] {k} = {v}') + try: + self.mgr.check_mon_command({ + 'prefix': 'config set', + 'name': k, + 'value': str(v), + 'who': section, + }) + except MonCommandFailed as e: + msg = f'Failed to set {spec.service_name()} option {k}: {e}' + self.log.warning(msg) + options_failed_to_set.append(msg) + + if invalid_config_options: + self.mgr.set_health_warning('CEPHADM_INVALID_CONFIG_OPTION', f'Ignoring {len(invalid_config_options)} invalid config option(s)', len(invalid_config_options), invalid_config_options) + if options_failed_to_set: + self.mgr.set_health_warning('CEPHADM_FAILED_SET_OPTION', f'Failed to set {len(options_failed_to_set)} option(s)', len(options_failed_to_set), options_failed_to_set) + + def _apply_service(self, spec: ServiceSpec) -> bool: + """ + Schedule a service. Deploy new daemons or remove old ones, depending + on the target label and count specified in the placement. + """ + self.mgr.migration.verify_no_migration() + + service_type = spec.service_type + service_name = spec.service_name() + if spec.unmanaged: + self.log.debug('Skipping unmanaged service %s' % service_name) + return False + if spec.preview_only: + self.log.debug('Skipping preview_only service %s' % service_name) + return False + self.log.debug('Applying service %s spec' % service_name) + + self._apply_service_config(spec) + + if service_type == 'osd': + self.mgr.osd_service.create_from_spec(cast(DriveGroupSpec, spec)) + # TODO: return True would result in a busy loop + # can't know if daemon count changed; create_from_spec doesn't + # return a solid indication + return False + + svc = self.mgr.cephadm_services[service_type] + daemons = self.mgr.cache.get_daemons_by_service(service_name) + + public_networks: List[str] = [] + if service_type == 'mon': + out = str(self.mgr.get_foreign_ceph_option('mon', 'public_network')) + if '/' in out: + public_networks = [x.strip() for x in out.split(',')] + self.log.debug('mon public_network(s) is %s' % public_networks) + + def matches_network(host): + # type: (str) -> bool + # make sure we have 1 or more IPs for any of those networks on that + # host + for network in public_networks: + if len(self.mgr.cache.networks[host].get(network, [])) > 0: + return True + self.log.info( + f"Filtered out host {host}: does not belong to mon public_network" + f" ({','.join(public_networks)})" + ) + return False + + rank_map = None + if svc.ranked(): + rank_map = self.mgr.spec_store[spec.service_name()].rank_map or {} + ha = HostAssignment( + spec=spec, + hosts=self.mgr._schedulable_hosts(), + unreachable_hosts=self.mgr._unreachable_hosts(), + daemons=daemons, + networks=self.mgr.cache.networks, + filter_new_host=( + matches_network if service_type == 'mon' + else None + ), + allow_colo=svc.allow_colo(), + primary_daemon_type=svc.primary_daemon_type(), + per_host_daemon_type=svc.per_host_daemon_type(), + rank_map=rank_map, + ) + + try: + all_slots, slots_to_add, daemons_to_remove = ha.place() + daemons_to_remove = [d for d in daemons_to_remove if (d.hostname and self.mgr.inventory._inventory[d.hostname].get( + 'status', '').lower() not in ['maintenance', 'offline'] and d.hostname not in self.mgr.offline_hosts)] + self.log.debug('Add %s, remove %s' % (slots_to_add, daemons_to_remove)) + except OrchestratorError as e: + msg = f'Failed to apply {spec.service_name()} spec {spec}: {str(e)}' + self.log.error(msg) + self.mgr.events.for_service(spec, 'ERROR', 'Failed to apply: ' + str(e)) + self.mgr.apply_spec_fails.append((spec.service_name(), str(e))) + warnings = [] + for x in self.mgr.apply_spec_fails: + warnings.append(f'{x[0]}: {x[1]}') + self.mgr.set_health_warning('CEPHADM_APPLY_SPEC_FAIL', + f"Failed to apply {len(self.mgr.apply_spec_fails)} service(s): {','.join(x[0] for x in self.mgr.apply_spec_fails)}", + len(self.mgr.apply_spec_fails), + warnings) + return False + + r = None + + # sanity check + final_count = len(daemons) + len(slots_to_add) - len(daemons_to_remove) + if service_type in ['mon', 'mgr'] and final_count < 1: + self.log.debug('cannot scale mon|mgr below 1)') + return False + + # progress + progress_id = str(uuid.uuid4()) + delta: List[str] = [] + if slots_to_add: + delta += [f'+{len(slots_to_add)}'] + if daemons_to_remove: + delta += [f'-{len(daemons_to_remove)}'] + progress_title = f'Updating {spec.service_name()} deployment ({" ".join(delta)} -> {len(all_slots)})' + progress_total = len(slots_to_add) + len(daemons_to_remove) + progress_done = 0 + + def update_progress() -> None: + self.mgr.remote( + 'progress', 'update', progress_id, + ev_msg=progress_title, + ev_progress=(progress_done / progress_total), + add_to_ceph_s=True, + ) + + if progress_total: + update_progress() + + # add any? + did_config = False + + self.log.debug('Hosts that will receive new daemons: %s' % slots_to_add) + self.log.debug('Daemons that will be removed: %s' % daemons_to_remove) + + try: + # assign names + for i in range(len(slots_to_add)): + slot = slots_to_add[i] + slot = slot.assign_name(self.mgr.get_unique_name( + slot.daemon_type, + slot.hostname, + [d for d in daemons if d not in daemons_to_remove], + prefix=spec.service_id, + forcename=slot.name, + rank=slot.rank, + rank_generation=slot.rank_generation, + )) + slots_to_add[i] = slot + if rank_map is not None: + assert slot.rank is not None + assert slot.rank_generation is not None + assert rank_map[slot.rank][slot.rank_generation] is None + rank_map[slot.rank][slot.rank_generation] = slot.name + + if rank_map: + # record the rank_map before we make changes so that if we fail the + # next mgr will clean up. + self.mgr.spec_store.save_rank_map(spec.service_name(), rank_map) + + # remove daemons now, since we are going to fence them anyway + for d in daemons_to_remove: + assert d.hostname is not None + self._remove_daemon(d.name(), d.hostname) + daemons_to_remove = [] + + # fence them + svc.fence_old_ranks(spec, rank_map, len(all_slots)) + + # create daemons + daemon_place_fails = [] + for slot in slots_to_add: + # first remove daemon with conflicting port or name? + if slot.ports or slot.name in [d.name() for d in daemons_to_remove]: + for d in daemons_to_remove: + if ( + d.hostname != slot.hostname + or not (set(d.ports or []) & set(slot.ports)) + or (d.ip and slot.ip and d.ip != slot.ip) + and d.name() != slot.name + ): + continue + if d.name() != slot.name: + self.log.info( + f'Removing {d.name()} before deploying to {slot} to avoid a port or conflict' + ) + # NOTE: we don't check ok-to-stop here to avoid starvation if + # there is only 1 gateway. + self._remove_daemon(d.name(), d.hostname) + daemons_to_remove.remove(d) + progress_done += 1 + break + + # deploy new daemon + daemon_id = slot.name + if not did_config: + svc.config(spec) + did_config = True + + daemon_spec = svc.make_daemon_spec( + slot.hostname, daemon_id, slot.network, spec, + daemon_type=slot.daemon_type, + ports=slot.ports, + ip=slot.ip, + rank=slot.rank, + rank_generation=slot.rank_generation, + ) + self.log.debug('Placing %s.%s on host %s' % ( + slot.daemon_type, daemon_id, slot.hostname)) + + try: + daemon_spec = svc.prepare_create(daemon_spec) + self._create_daemon(daemon_spec) + r = True + progress_done += 1 + update_progress() + except (RuntimeError, OrchestratorError) as e: + msg = (f"Failed while placing {slot.daemon_type}.{daemon_id} " + f"on {slot.hostname}: {e}") + self.mgr.events.for_service(spec, 'ERROR', msg) + self.mgr.log.error(msg) + daemon_place_fails.append(msg) + # only return "no change" if no one else has already succeeded. + # later successes will also change to True + if r is None: + r = False + progress_done += 1 + update_progress() + continue + + # add to daemon list so next name(s) will also be unique + sd = orchestrator.DaemonDescription( + hostname=slot.hostname, + daemon_type=slot.daemon_type, + daemon_id=daemon_id, + ) + daemons.append(sd) + + if daemon_place_fails: + self.mgr.set_health_warning('CEPHADM_DAEMON_PLACE_FAIL', f'Failed to place {len(daemon_place_fails)} daemon(s)', len(daemon_place_fails), daemon_place_fails) + + if service_type == 'mgr': + active_mgr = svc.get_active_daemon(self.mgr.cache.get_daemons_by_type('mgr')) + if active_mgr.daemon_id in [d.daemon_id for d in daemons_to_remove]: + # We can't just remove the active mgr like any other daemon. + # Need to fail over later so it can be removed on next pass. + # This can be accomplished by scheduling a restart of the active mgr. + self.mgr._schedule_daemon_action(active_mgr.name(), 'restart') + + # remove any? + def _ok_to_stop(remove_daemons: List[orchestrator.DaemonDescription]) -> bool: + daemon_ids = [d.daemon_id for d in remove_daemons] + assert None not in daemon_ids + # setting force flag retains previous behavior + r = svc.ok_to_stop(cast(List[str], daemon_ids), force=True) + return not r.retval + + while daemons_to_remove and not _ok_to_stop(daemons_to_remove): + # let's find a subset that is ok-to-stop + daemons_to_remove.pop() + for d in daemons_to_remove: + r = True + assert d.hostname is not None + self._remove_daemon(d.name(), d.hostname) + + progress_done += 1 + update_progress() + + self.mgr.remote('progress', 'complete', progress_id) + except Exception as e: + self.mgr.remote('progress', 'fail', progress_id, str(e)) + raise + + if r is None: + r = False + return r + + def _check_daemons(self) -> None: + self.log.debug('_check_daemons') + daemons = self.mgr.cache.get_daemons() + daemons_post: Dict[str, List[orchestrator.DaemonDescription]] = defaultdict(list) + for dd in daemons: + # orphan? + spec = self.mgr.spec_store.active_specs.get(dd.service_name(), None) + assert dd.hostname is not None + assert dd.daemon_type is not None + assert dd.daemon_id is not None + if not spec and dd.daemon_type not in ['mon', 'mgr', 'osd']: + # (mon and mgr specs should always exist; osds aren't matched + # to a service spec) + self.log.info('Removing orphan daemon %s...' % dd.name()) + self._remove_daemon(dd.name(), dd.hostname) + + # ignore unmanaged services + if spec and spec.unmanaged: + continue + + # ignore daemons for deleted services + if dd.service_name() in self.mgr.spec_store.spec_deleted: + continue + + # These daemon types require additional configs after creation + if dd.daemon_type in REQUIRES_POST_ACTIONS: + daemons_post[dd.daemon_type].append(dd) + + if self.mgr.cephadm_services[daemon_type_to_service(dd.daemon_type)].get_active_daemon( + self.mgr.cache.get_daemons_by_service(dd.service_name())).daemon_id == dd.daemon_id: + dd.is_active = True + else: + dd.is_active = False + + deps = self.mgr._calc_daemon_deps(spec, dd.daemon_type, dd.daemon_id) + last_deps, last_config = self.mgr.cache.get_daemon_last_config_deps( + dd.hostname, dd.name()) + if last_deps is None: + last_deps = [] + action = self.mgr.cache.get_scheduled_daemon_action(dd.hostname, dd.name()) + if not last_config: + self.log.info('Reconfiguring %s (unknown last config time)...' % ( + dd.name())) + action = 'reconfig' + elif last_deps != deps: + self.log.debug('%s deps %s -> %s' % (dd.name(), last_deps, + deps)) + self.log.info('Reconfiguring %s (dependencies changed)...' % ( + dd.name())) + action = 'reconfig' + elif spec is not None and hasattr(spec, 'extra_container_args') and dd.extra_container_args != spec.extra_container_args: + self.log.debug( + f'{dd.name()} container cli args {dd.extra_container_args} -> {spec.extra_container_args}') + self.log.info(f'Redeploying {dd.name()}, (container cli args changed) . . .') + dd.extra_container_args = spec.extra_container_args + action = 'redeploy' + elif self.mgr.last_monmap and \ + self.mgr.last_monmap > last_config and \ + dd.daemon_type in CEPH_TYPES: + self.log.info('Reconfiguring %s (monmap changed)...' % dd.name()) + action = 'reconfig' + elif self.mgr.extra_ceph_conf_is_newer(last_config) and \ + dd.daemon_type in CEPH_TYPES: + self.log.info('Reconfiguring %s (extra config changed)...' % dd.name()) + action = 'reconfig' + if action: + if self.mgr.cache.get_scheduled_daemon_action(dd.hostname, dd.name()) == 'redeploy' \ + and action == 'reconfig': + action = 'redeploy' + try: + daemon_spec = CephadmDaemonDeploySpec.from_daemon_description(dd) + self.mgr._daemon_action(daemon_spec, action=action) + if self.mgr.cache.rm_scheduled_daemon_action(dd.hostname, dd.name()): + self.mgr.cache.save_host(dd.hostname) + except OrchestratorError as e: + self.mgr.events.from_orch_error(e) + if dd.daemon_type in daemons_post: + del daemons_post[dd.daemon_type] + # continue... + except Exception as e: + self.mgr.events.for_daemon_from_exception(dd.name(), e) + if dd.daemon_type in daemons_post: + del daemons_post[dd.daemon_type] + # continue... + + # do daemon post actions + for daemon_type, daemon_descs in daemons_post.items(): + run_post = False + for d in daemon_descs: + if d.name() in self.mgr.requires_post_actions: + self.mgr.requires_post_actions.remove(d.name()) + run_post = True + if run_post: + self.mgr._get_cephadm_service(daemon_type_to_service( + daemon_type)).daemon_check_post(daemon_descs) + + def _purge_deleted_services(self) -> None: + self.log.debug('_purge_deleted_services') + existing_services = self.mgr.spec_store.all_specs.items() + for service_name, spec in list(existing_services): + if service_name not in self.mgr.spec_store.spec_deleted: + continue + if self.mgr.cache.get_daemons_by_service(service_name): + continue + if spec.service_type in ['mon', 'mgr']: + continue + + logger.info(f'Purge service {service_name}') + + self.mgr.cephadm_services[spec.service_type].purge(service_name) + self.mgr.spec_store.finally_rm(service_name) + + def convert_tags_to_repo_digest(self) -> None: + if not self.mgr.use_repo_digest: + return + settings = self.mgr.upgrade.get_distinct_container_image_settings() + digests: Dict[str, ContainerInspectInfo] = {} + for container_image_ref in set(settings.values()): + if not is_repo_digest(container_image_ref): + image_info = self._get_container_image_info(container_image_ref) + if image_info.repo_digests: + # FIXME: we assume the first digest here is the best + assert is_repo_digest(image_info.repo_digests[0]), image_info + digests[container_image_ref] = image_info + + for entity, container_image_ref in settings.items(): + if not is_repo_digest(container_image_ref): + image_info = digests[container_image_ref] + if image_info.repo_digests: + # FIXME: we assume the first digest here is the best + self.mgr.set_container_image(entity, image_info.repo_digests[0]) + + def _calc_client_files(self) -> Dict[str, Dict[str, Tuple[int, int, int, bytes, str]]]: + # host -> path -> (mode, uid, gid, content, digest) + client_files: Dict[str, Dict[str, Tuple[int, int, int, bytes, str]]] = {} + + # ceph.conf + config = self.mgr.get_minimal_ceph_conf().encode('utf-8') + config_digest = ''.join('%02x' % c for c in hashlib.sha256(config).digest()) + + if self.mgr.manage_etc_ceph_ceph_conf: + try: + pspec = PlacementSpec.from_string(self.mgr.manage_etc_ceph_ceph_conf_hosts) + ha = HostAssignment( + spec=ServiceSpec('mon', placement=pspec), + hosts=self.mgr._schedulable_hosts(), + unreachable_hosts=self.mgr._unreachable_hosts(), + daemons=[], + networks=self.mgr.cache.networks, + ) + all_slots, _, _ = ha.place() + for host in {s.hostname for s in all_slots}: + if host not in client_files: + client_files[host] = {} + client_files[host]['/etc/ceph/ceph.conf'] = ( + 0o644, 0, 0, bytes(config), str(config_digest) + ) + except Exception as e: + self.mgr.log.warning( + f'unable to calc conf hosts: {self.mgr.manage_etc_ceph_ceph_conf_hosts}: {e}') + + # client keyrings + for ks in self.mgr.keys.keys.values(): + try: + ret, keyring, err = self.mgr.mon_command({ + 'prefix': 'auth get', + 'entity': ks.entity, + }) + if ret: + self.log.warning(f'unable to fetch keyring for {ks.entity}') + continue + digest = ''.join('%02x' % c for c in hashlib.sha256( + keyring.encode('utf-8')).digest()) + ha = HostAssignment( + spec=ServiceSpec('mon', placement=ks.placement), + hosts=self.mgr._schedulable_hosts(), + unreachable_hosts=self.mgr._unreachable_hosts(), + daemons=[], + networks=self.mgr.cache.networks, + ) + all_slots, _, _ = ha.place() + for host in {s.hostname for s in all_slots}: + if host not in client_files: + client_files[host] = {} + client_files[host]['/etc/ceph/ceph.conf'] = ( + 0o644, 0, 0, bytes(config), str(config_digest) + ) + client_files[host][ks.path] = ( + ks.mode, ks.uid, ks.gid, keyring.encode('utf-8'), digest + ) + except Exception as e: + self.log.warning( + f'unable to calc client keyring {ks.entity} placement {ks.placement}: {e}') + return client_files + + def _write_client_files(self, + client_files: Dict[str, Dict[str, Tuple[int, int, int, bytes, str]]], + host: str) -> None: + updated_files = False + old_files = self.mgr.cache.get_host_client_files(host).copy() + for path, m in client_files.get(host, {}).items(): + mode, uid, gid, content, digest = m + if path in old_files: + match = old_files[path] == (digest, mode, uid, gid) + del old_files[path] + if match: + continue + self.log.info(f'Updating {host}:{path}') + self._write_remote_file(host, path, content, mode, uid, gid) + self.mgr.cache.update_client_file(host, path, digest, mode, uid, gid) + updated_files = True + for path in old_files.keys(): + if path == '/etc/ceph/ceph.conf': + continue + self.log.info(f'Removing {host}:{path}') + with self._remote_connection(host) as tpl: + conn, connr = tpl + out, err, code = remoto.process.check( + conn, + ['rm', '-f', path]) + updated_files = True + self.mgr.cache.removed_client_file(host, path) + if updated_files: + self.mgr.cache.save_host(host) + + def _create_daemon(self, + daemon_spec: CephadmDaemonDeploySpec, + reconfig: bool = False, + osd_uuid_map: Optional[Dict[str, Any]] = None, + ) -> str: + + with set_exception_subject('service', orchestrator.DaemonDescription( + daemon_type=daemon_spec.daemon_type, + daemon_id=daemon_spec.daemon_id, + hostname=daemon_spec.host, + ).service_id(), overwrite=True): + + try: + image = '' + start_time = datetime_now() + ports: List[int] = daemon_spec.ports if daemon_spec.ports else [] + + if daemon_spec.daemon_type == 'container': + spec = cast(CustomContainerSpec, + self.mgr.spec_store[daemon_spec.service_name].spec) + image = spec.image + if spec.ports: + ports.extend(spec.ports) + + if daemon_spec.daemon_type == 'cephadm-exporter': + if not reconfig: + assert daemon_spec.host + self._deploy_cephadm_binary(daemon_spec.host) + + # TCP port to open in the host firewall + if len(ports) > 0: + daemon_spec.extra_args.extend([ + '--tcp-ports', ' '.join(map(str, ports)) + ]) + + # osd deployments needs an --osd-uuid arg + if daemon_spec.daemon_type == 'osd': + if not osd_uuid_map: + osd_uuid_map = self.mgr.get_osd_uuid_map() + osd_uuid = osd_uuid_map.get(daemon_spec.daemon_id) + if not osd_uuid: + raise OrchestratorError('osd.%s not in osdmap' % daemon_spec.daemon_id) + daemon_spec.extra_args.extend(['--osd-fsid', osd_uuid]) + + if reconfig: + daemon_spec.extra_args.append('--reconfig') + if self.mgr.allow_ptrace: + daemon_spec.extra_args.append('--allow-ptrace') + + try: + eca = daemon_spec.extra_container_args + if eca: + for a in eca: + daemon_spec.extra_args.append(f'--extra-container-args={a}') + except AttributeError: + eca = None + + if self.mgr.cache.host_needs_registry_login(daemon_spec.host) and self.mgr.registry_url: + self._registry_login(daemon_spec.host, + json.loads(str(self.mgr.get_store('registry_credentials')))) + + self.log.info('%s daemon %s on %s' % ( + 'Reconfiguring' if reconfig else 'Deploying', + daemon_spec.name(), daemon_spec.host)) + + out, err, code = self._run_cephadm( + daemon_spec.host, daemon_spec.name(), 'deploy', + [ + '--name', daemon_spec.name(), + '--meta-json', json.dumps({ + 'service_name': daemon_spec.service_name, + 'ports': daemon_spec.ports, + 'ip': daemon_spec.ip, + 'deployed_by': self.mgr.get_active_mgr_digests(), + 'rank': daemon_spec.rank, + 'rank_generation': daemon_spec.rank_generation, + 'extra_container_args': eca + }), + '--config-json', '-', + ] + daemon_spec.extra_args, + stdin=json.dumps(daemon_spec.final_config), + image=image, + ) + + # refresh daemon state? (ceph daemon reconfig does not need it) + if not reconfig or daemon_spec.daemon_type not in CEPH_TYPES: + if not code and daemon_spec.host in self.mgr.cache.daemons: + # prime cached service state with what we (should have) + # just created + sd = daemon_spec.to_daemon_description( + DaemonDescriptionStatus.starting, 'starting') + self.mgr.cache.add_daemon(daemon_spec.host, sd) + if daemon_spec.daemon_type in REQUIRES_POST_ACTIONS: + self.mgr.requires_post_actions.add(daemon_spec.name()) + self.mgr.cache.invalidate_host_daemons(daemon_spec.host) + + self.mgr.cache.update_daemon_config_deps( + daemon_spec.host, daemon_spec.name(), daemon_spec.deps, start_time) + self.mgr.cache.save_host(daemon_spec.host) + msg = "{} {} on host '{}'".format( + 'Reconfigured' if reconfig else 'Deployed', daemon_spec.name(), daemon_spec.host) + if not code: + self.mgr.events.for_daemon(daemon_spec.name(), OrchestratorEvent.INFO, msg) + else: + what = 'reconfigure' if reconfig else 'deploy' + self.mgr.events.for_daemon( + daemon_spec.name(), OrchestratorEvent.ERROR, f'Failed to {what}: {err}') + return msg + except OrchestratorError: + redeploy = daemon_spec.name() in self.mgr.cache.get_daemon_names() + if not reconfig and not redeploy: + # we have to clean up the daemon. E.g. keyrings. + servict_type = daemon_type_to_service(daemon_spec.daemon_type) + dd = daemon_spec.to_daemon_description(DaemonDescriptionStatus.error, 'failed') + self.mgr.cephadm_services[servict_type].post_remove(dd, is_failed_deploy=True) + raise + + def _remove_daemon(self, name: str, host: str, no_post_remove: bool = False) -> str: + """ + Remove a daemon + """ + (daemon_type, daemon_id) = name.split('.', 1) + daemon = orchestrator.DaemonDescription( + daemon_type=daemon_type, + daemon_id=daemon_id, + hostname=host) + + with set_exception_subject('service', daemon.service_id(), overwrite=True): + + self.mgr.cephadm_services[daemon_type_to_service(daemon_type)].pre_remove(daemon) + # NOTE: we are passing the 'force' flag here, which means + # we can delete a mon instances data. + dd = self.mgr.cache.get_daemon(daemon.daemon_name) + if dd.ports: + args = ['--name', name, '--force', '--tcp-ports', ' '.join(map(str, dd.ports))] + else: + args = ['--name', name, '--force'] + + self.log.info('Removing daemon %s from %s -- ports %s' % (name, host, dd.ports)) + out, err, code = self._run_cephadm( + host, name, 'rm-daemon', args) + if not code: + # remove item from cache + self.mgr.cache.rm_daemon(host, name) + self.mgr.cache.invalidate_host_daemons(host) + + if not no_post_remove: + self.mgr.cephadm_services[daemon_type_to_service( + daemon_type)].post_remove(daemon, is_failed_deploy=False) + + return "Removed {} from host '{}'".format(name, host) + + def _run_cephadm_json(self, + host: str, + entity: Union[CephadmNoImage, str], + command: str, + args: List[str], + no_fsid: Optional[bool] = False, + image: Optional[str] = "", + ) -> Any: + try: + out, err, code = self._run_cephadm( + host, entity, command, args, no_fsid=no_fsid, image=image) + if code: + raise OrchestratorError(f'host {host} `cephadm {command}` returned {code}: {err}') + except Exception as e: + raise OrchestratorError(f'host {host} `cephadm {command}` failed: {e}') + try: + return json.loads(''.join(out)) + except (ValueError, KeyError): + msg = f'host {host} `cephadm {command}` failed: Cannot decode JSON' + self.log.exception(f'{msg}: {"".join(out)}') + raise OrchestratorError(msg) + + def _run_cephadm(self, + host: str, + entity: Union[CephadmNoImage, str], + command: str, + args: List[str], + addr: Optional[str] = "", + stdin: Optional[str] = "", + no_fsid: Optional[bool] = False, + error_ok: Optional[bool] = False, + image: Optional[str] = "", + env_vars: Optional[List[str]] = None, + ) -> Tuple[List[str], List[str], int]: + """ + Run cephadm on the remote host with the given command + args + + Important: You probably don't want to run _run_cephadm from CLI handlers + + :env_vars: in format -> [KEY=VALUE, ..] + """ + self.log.debug(f"_run_cephadm : command = {command}") + self.log.debug(f"_run_cephadm : args = {args}") + + bypass_image = ('cephadm-exporter',) + + with self._remote_connection(host, addr) as tpl: + conn, connr = tpl + assert image or entity + # Skip the image check for daemons deployed that are not ceph containers + if not str(entity).startswith(bypass_image): + if not image and entity is not cephadmNoImage: + image = self.mgr._get_container_image(entity) + + final_args = [] + + # global args + if env_vars: + for env_var_pair in env_vars: + final_args.extend(['--env', env_var_pair]) + + if image: + final_args.extend(['--image', image]) + + if not self.mgr.container_init: + final_args += ['--no-container-init'] + + # subcommand + final_args.append(command) + + # subcommand args + if not no_fsid: + final_args += ['--fsid', self.mgr._cluster_fsid] + + final_args += args + + # exec + self.log.debug('args: %s' % (' '.join(final_args))) + if self.mgr.mode == 'root': + if stdin: + self.log.debug('stdin: %s' % stdin) + + try: + # if host has gone offline this is likely where we'll fail first + python = connr.choose_python() + except RuntimeError as e: + self.mgr.offline_hosts.add(host) + self.mgr._reset_con(host) + if error_ok: + return [], [str(e)], 1 + raise + if not python: + raise RuntimeError( + 'unable to find python on %s (tried %s in %s)' % ( + host, remotes.PYTHONS, remotes.PATH)) + try: + out, err, code = remoto.process.check( + conn, + [python, self.mgr.cephadm_binary_path] + final_args, + stdin=stdin.encode('utf-8') if stdin is not None else None) + if code == 2: + out_ls, err_ls, code_ls = remoto.process.check( + conn, ['ls', self.mgr.cephadm_binary_path]) + if code_ls == 2: + self._deploy_cephadm_binary_conn(conn, host) + out, err, code = remoto.process.check( + conn, + [python, self.mgr.cephadm_binary_path] + final_args, + stdin=stdin.encode('utf-8') if stdin is not None else None) + + except RuntimeError as e: + self.mgr._reset_con(host) + if error_ok: + return [], [str(e)], 1 + raise + + elif self.mgr.mode == 'cephadm-package': + try: + out, err, code = remoto.process.check( + conn, + ['sudo', '/usr/bin/cephadm'] + final_args, + stdin=stdin) + except RuntimeError as e: + self.mgr._reset_con(host) + if error_ok: + return [], [str(e)], 1 + raise + else: + assert False, 'unsupported mode' + + self.log.debug('code: %d' % code) + if out: + self.log.debug('out: %s' % '\n'.join(out)) + if err: + self.log.debug('err: %s' % '\n'.join(err)) + if code and not error_ok: + raise OrchestratorError( + 'cephadm exited with an error code: %d, stderr:%s' % ( + code, '\n'.join(err))) + return out, err, code + + def _get_container_image_info(self, image_name: str) -> ContainerInspectInfo: + # pick a random host... + host = None + for host_name in self.mgr.inventory.keys(): + host = host_name + break + if not host: + raise OrchestratorError('no hosts defined') + if self.mgr.cache.host_needs_registry_login(host) and self.mgr.registry_url: + self._registry_login(host, + json.loads(str(self.mgr.get_store('registry_credentials')))) + + pullargs: List[str] = [] + if self.mgr.registry_insecure: + pullargs.append("--insecure") + + j = self._run_cephadm_json(host, '', 'pull', pullargs, image=image_name, no_fsid=True) + + r = ContainerInspectInfo( + j['image_id'], + j.get('ceph_version'), + j.get('repo_digests') + ) + self.log.debug(f'image {image_name} -> {r}') + return r + + # function responsible for logging single host into custom registry + def _registry_login(self, host: str, registry_json: Dict[str, str]) -> Optional[str]: + self.log.debug( + f"Attempting to log host {host} into custom registry @ {registry_json['url']}") + # want to pass info over stdin rather than through normal list of args + out, err, code = self._run_cephadm( + host, 'mon', 'registry-login', + ['--registry-json', '-'], stdin=json.dumps(registry_json), error_ok=True) + if code: + return f"Host {host} failed to login to {registry_json['url']} as {registry_json['username']} with given password" + return None + + def _deploy_cephadm_binary(self, host: str) -> None: + # Use tee (from coreutils) to create a copy of cephadm on the target machine + self.log.info(f"Deploying cephadm binary to {host}") + with self._remote_connection(host) as tpl: + conn, _connr = tpl + return self._deploy_cephadm_binary_conn(conn, host) + + def _deploy_cephadm_binary_conn(self, conn: "BaseConnection", host: str) -> None: + _out, _err, code = remoto.process.check( + conn, + ['mkdir', '-p', f'/var/lib/ceph/{self.mgr._cluster_fsid}']) + if code: + msg = f"Unable to deploy the cephadm binary to {host}: {_err}" + self.log.warning(msg) + raise OrchestratorError(msg) + _out, _err, code = remoto.process.check( + conn, + ['tee', '-', self.mgr.cephadm_binary_path], + stdin=self.mgr._cephadm.encode('utf-8')) + if code: + msg = f"Unable to deploy the cephadm binary to {host}: {_err}" + self.log.warning(msg) + raise OrchestratorError(msg) + + def _write_remote_file(self, + host: str, + path: str, + content: bytes, + mode: int, + uid: int, + gid: int) -> None: + with self._remote_connection(host) as tpl: + conn, connr = tpl + try: + errmsg = connr.write_file(path, content, mode, uid, gid) + if errmsg is not None: + raise OrchestratorError(errmsg) + except Exception as e: + msg = f"Unable to write {host}:{path}: {e}" + self.log.warning(msg) + raise OrchestratorError(msg) + + @contextmanager + def _remote_connection(self, + host: str, + addr: Optional[str] = None, + ) -> Iterator[Tuple["BaseConnection", Any]]: + if not addr and host in self.mgr.inventory: + addr = self.mgr.inventory.get_addr(host) + + self.mgr.offline_hosts_remove(host) + + try: + try: + if not addr: + raise OrchestratorError("host address is empty") + conn, connr = self.mgr._get_connection(addr) + except OSError as e: + self.mgr._reset_con(host) + msg = f"Can't communicate with remote host `{addr}`, possibly because python3 is not installed there: {str(e)}" + raise execnet.gateway_bootstrap.HostNotFound(msg) + + yield (conn, connr) + + except execnet.gateway_bootstrap.HostNotFound as e: + # this is a misleading exception as it seems to be thrown for + # any sort of connection failure, even those having nothing to + # do with "host not found" (e.g., ssh key permission denied). + self.mgr.offline_hosts.add(host) + self.mgr._reset_con(host) + + user = self.mgr.ssh_user if self.mgr.mode == 'root' else 'cephadm' + if str(e).startswith("Can't communicate"): + msg = str(e) + else: + msg = f'''Failed to connect to {host} ({addr}). +Please make sure that the host is reachable and accepts connections using the cephadm SSH key + +To add the cephadm SSH key to the host: +> ceph cephadm get-pub-key > ~/ceph.pub +> ssh-copy-id -f -i ~/ceph.pub {user}@{addr} + +To check that the host is reachable open a new shell with the --no-hosts flag: +> cephadm shell --no-hosts + +Then run the following: +> ceph cephadm get-ssh-config > ssh_config +> ceph config-key get mgr/cephadm/ssh_identity_key > ~/cephadm_private_key +> chmod 0600 ~/cephadm_private_key +> ssh -F ssh_config -i ~/cephadm_private_key {user}@{addr}''' + raise OrchestratorError(msg) from e + except Exception as ex: + self.log.exception(ex) + raise diff --git a/src/pybind/mgr/cephadm/services/__init__.py b/src/pybind/mgr/cephadm/services/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/pybind/mgr/cephadm/services/cephadmservice.py b/src/pybind/mgr/cephadm/services/cephadmservice.py new file mode 100644 index 000000000..92d293fcd --- /dev/null +++ b/src/pybind/mgr/cephadm/services/cephadmservice.py @@ -0,0 +1,1043 @@ +import errno +import json +import logging +import re +import socket +import time +from abc import ABCMeta, abstractmethod +from typing import TYPE_CHECKING, List, Callable, TypeVar, \ + Optional, Dict, Any, Tuple, NewType, cast + +from mgr_module import HandleCommandResult, MonCommandFailed + +from ceph.deployment.service_spec import ServiceSpec, RGWSpec +from ceph.deployment.utils import is_ipv6, unwrap_ipv6 +from mgr_util import build_url +from orchestrator import OrchestratorError, DaemonDescription, DaemonDescriptionStatus +from orchestrator._interface import daemon_type_to_service +from cephadm import utils + +if TYPE_CHECKING: + from cephadm.module import CephadmOrchestrator + +logger = logging.getLogger(__name__) + +ServiceSpecs = TypeVar('ServiceSpecs', bound=ServiceSpec) +AuthEntity = NewType('AuthEntity', str) + + +class CephadmDaemonDeploySpec: + # typing.NamedTuple + Generic is broken in py36 + def __init__(self, host: str, daemon_id: str, + service_name: str, + network: Optional[str] = None, + keyring: Optional[str] = None, + extra_args: Optional[List[str]] = None, + ceph_conf: str = '', + extra_files: Optional[Dict[str, Any]] = None, + daemon_type: Optional[str] = None, + ip: Optional[str] = None, + ports: Optional[List[int]] = None, + rank: Optional[int] = None, + rank_generation: Optional[int] = None, + extra_container_args: Optional[List[str]] = None): + """ + A data struction to encapsulate `cephadm deploy ... + """ + self.host: str = host + self.daemon_id = daemon_id + self.service_name = service_name + daemon_type = daemon_type or (service_name.split('.')[0]) + assert daemon_type is not None + self.daemon_type: str = daemon_type + + # mons + self.network = network + + # for run_cephadm. + self.keyring: Optional[str] = keyring + + # For run_cephadm. Would be great to have more expressive names. + self.extra_args: List[str] = extra_args or [] + + self.ceph_conf = ceph_conf + self.extra_files = extra_files or {} + + # TCP ports used by the daemon + self.ports: List[int] = ports or [] + self.ip: Optional[str] = ip + + # values to be populated during generate_config calls + # and then used in _run_cephadm + self.final_config: Dict[str, Any] = {} + self.deps: List[str] = [] + + self.rank: Optional[int] = rank + self.rank_generation: Optional[int] = rank_generation + + self.extra_container_args = extra_container_args + + def name(self) -> str: + return '%s.%s' % (self.daemon_type, self.daemon_id) + + def config_get_files(self) -> Dict[str, Any]: + files = self.extra_files + if self.ceph_conf: + files['config'] = self.ceph_conf + + return files + + @staticmethod + def from_daemon_description(dd: DaemonDescription) -> 'CephadmDaemonDeploySpec': + assert dd.hostname + assert dd.daemon_id + assert dd.daemon_type + return CephadmDaemonDeploySpec( + host=dd.hostname, + daemon_id=dd.daemon_id, + daemon_type=dd.daemon_type, + service_name=dd.service_name(), + ip=dd.ip, + ports=dd.ports, + rank=dd.rank, + rank_generation=dd.rank_generation, + extra_container_args=dd.extra_container_args, + ) + + def to_daemon_description(self, status: DaemonDescriptionStatus, status_desc: str) -> DaemonDescription: + return DaemonDescription( + daemon_type=self.daemon_type, + daemon_id=self.daemon_id, + service_name=self.service_name, + hostname=self.host, + status=status, + status_desc=status_desc, + ip=self.ip, + ports=self.ports, + rank=self.rank, + rank_generation=self.rank_generation, + extra_container_args=self.extra_container_args, + ) + + +class CephadmService(metaclass=ABCMeta): + """ + Base class for service types. Often providing a create() and config() fn. + """ + + @property + @abstractmethod + def TYPE(self) -> str: + pass + + def __init__(self, mgr: "CephadmOrchestrator"): + self.mgr: "CephadmOrchestrator" = mgr + + def allow_colo(self) -> bool: + """ + Return True if multiple daemons of the same type can colocate on + the same host. + """ + return False + + def primary_daemon_type(self) -> str: + """ + This is the type of the primary (usually only) daemon to be deployed. + """ + return self.TYPE + + def per_host_daemon_type(self) -> Optional[str]: + """ + If defined, this type of daemon will be deployed once for each host + containing one or more daemons of the primary type. + """ + return None + + def ranked(self) -> bool: + """ + If True, we will assign a stable rank (0, 1, ...) and monotonically increasing + generation (0, 1, ...) to each daemon we create/deploy. + """ + return False + + def fence_old_ranks(self, + spec: ServiceSpec, + rank_map: Dict[int, Dict[int, Optional[str]]], + num_ranks: int) -> None: + assert False + + def make_daemon_spec( + self, + host: str, + daemon_id: str, + network: str, + spec: ServiceSpecs, + daemon_type: Optional[str] = None, + ports: Optional[List[int]] = None, + ip: Optional[str] = None, + rank: Optional[int] = None, + rank_generation: Optional[int] = None, + ) -> CephadmDaemonDeploySpec: + try: + eca = spec.extra_container_args + except AttributeError: + eca = None + return CephadmDaemonDeploySpec( + host=host, + daemon_id=daemon_id, + service_name=spec.service_name(), + network=network, + daemon_type=daemon_type, + ports=ports, + ip=ip, + rank=rank, + rank_generation=rank_generation, + extra_container_args=eca, + ) + + def prepare_create(self, daemon_spec: CephadmDaemonDeploySpec) -> CephadmDaemonDeploySpec: + raise NotImplementedError() + + def generate_config(self, daemon_spec: CephadmDaemonDeploySpec) -> Tuple[Dict[str, Any], List[str]]: + raise NotImplementedError() + + def config(self, spec: ServiceSpec) -> None: + """ + Configure the cluster for this service. Only called *once* per + service apply. Not for every daemon. + """ + pass + + def daemon_check_post(self, daemon_descrs: List[DaemonDescription]) -> None: + """The post actions needed to be done after daemons are checked""" + if self.mgr.config_dashboard: + if 'dashboard' in self.mgr.get('mgr_map')['modules']: + self.config_dashboard(daemon_descrs) + else: + logger.debug('Dashboard is not enabled. Skip configuration.') + + def config_dashboard(self, daemon_descrs: List[DaemonDescription]) -> None: + """Config dashboard settings.""" + raise NotImplementedError() + + def get_active_daemon(self, daemon_descrs: List[DaemonDescription]) -> DaemonDescription: + # if this is called for a service type where it hasn't explcitly been + # defined, return empty Daemon Desc + return DaemonDescription() + + def get_keyring_with_caps(self, entity: AuthEntity, caps: List[str]) -> str: + ret, keyring, err = self.mgr.mon_command({ + 'prefix': 'auth get-or-create', + 'entity': entity, + 'caps': caps, + }) + if err: + ret, out, err = self.mgr.mon_command({ + 'prefix': 'auth caps', + 'entity': entity, + 'caps': caps, + }) + if err: + self.mgr.log.warning(f"Unable to update caps for {entity}") + return keyring + + def _inventory_get_fqdn(self, hostname: str) -> str: + """Get a host's FQDN with its hostname. + + If the FQDN can't be resolved, the address from the inventory will + be returned instead. + """ + addr = self.mgr.inventory.get_addr(hostname) + return socket.getfqdn(addr) + + def _set_service_url_on_dashboard(self, + service_name: str, + get_mon_cmd: str, + set_mon_cmd: str, + service_url: str) -> None: + """A helper to get and set service_url via Dashboard's MON command. + + If result of get_mon_cmd differs from service_url, set_mon_cmd will + be sent to set the service_url. + """ + def get_set_cmd_dicts(out: str) -> List[dict]: + cmd_dict = { + 'prefix': set_mon_cmd, + 'value': service_url + } + return [cmd_dict] if service_url != out else [] + + self._check_and_set_dashboard( + service_name=service_name, + get_cmd=get_mon_cmd, + get_set_cmd_dicts=get_set_cmd_dicts + ) + + def _check_and_set_dashboard(self, + service_name: str, + get_cmd: str, + get_set_cmd_dicts: Callable[[str], List[dict]]) -> None: + """A helper to set configs in the Dashboard. + + The method is useful for the pattern: + - Getting a config from Dashboard by using a Dashboard command. e.g. current iSCSI + gateways. + - Parse or deserialize previous output. e.g. Dashboard command returns a JSON string. + - Determine if the config need to be update. NOTE: This step is important because if a + Dashboard command modified Ceph config, cephadm's config_notify() is called. Which + kicks the serve() loop and the logic using this method is likely to be called again. + A config should be updated only when needed. + - Update a config in Dashboard by using a Dashboard command. + + :param service_name: the service name to be used for logging + :type service_name: str + :param get_cmd: Dashboard command prefix to get config. e.g. dashboard get-grafana-api-url + :type get_cmd: str + :param get_set_cmd_dicts: function to create a list, and each item is a command dictionary. + e.g. + [ + { + 'prefix': 'dashboard iscsi-gateway-add', + 'service_url': 'http://admin:admin@aaa:5000', + 'name': 'aaa' + }, + { + 'prefix': 'dashboard iscsi-gateway-add', + 'service_url': 'http://admin:admin@bbb:5000', + 'name': 'bbb' + } + ] + The function should return empty list if no command need to be sent. + :type get_set_cmd_dicts: Callable[[str], List[dict]] + """ + + try: + _, out, _ = self.mgr.check_mon_command({ + 'prefix': get_cmd + }) + except MonCommandFailed as e: + logger.warning('Failed to get Dashboard config for %s: %s', service_name, e) + return + cmd_dicts = get_set_cmd_dicts(out.strip()) + for cmd_dict in list(cmd_dicts): + try: + inbuf = cmd_dict.pop('inbuf', None) + _, out, _ = self.mgr.check_mon_command(cmd_dict, inbuf) + except MonCommandFailed as e: + logger.warning('Failed to set Dashboard config for %s: %s', service_name, e) + + def ok_to_stop_osd( + self, + osds: List[str], + known: Optional[List[str]] = None, # output argument + force: bool = False) -> HandleCommandResult: + r = HandleCommandResult(*self.mgr.mon_command({ + 'prefix': "osd ok-to-stop", + 'ids': osds, + 'max': 16, + })) + j = None + try: + j = json.loads(r.stdout) + except json.decoder.JSONDecodeError: + self.mgr.log.warning("osd ok-to-stop didn't return structured result") + raise + if r.retval: + return r + if known is not None and j and j.get('ok_to_stop'): + self.mgr.log.debug(f"got {j}") + known.extend([f'osd.{x}' for x in j.get('osds', [])]) + return HandleCommandResult( + 0, + f'{",".join(["osd.%s" % o for o in osds])} {"is" if len(osds) == 1 else "are"} safe to restart', + '' + ) + + def ok_to_stop( + self, + daemon_ids: List[str], + force: bool = False, + known: Optional[List[str]] = None # output argument + ) -> HandleCommandResult: + names = [f'{self.TYPE}.{d_id}' for d_id in daemon_ids] + out = f'It appears safe to stop {",".join(names)}' + err = f'It is NOT safe to stop {",".join(names)} at this time' + + if self.TYPE not in ['mon', 'osd', 'mds']: + logger.debug(out) + return HandleCommandResult(0, out) + + if self.TYPE == 'osd': + return self.ok_to_stop_osd(daemon_ids, known, force) + + r = HandleCommandResult(*self.mgr.mon_command({ + 'prefix': f'{self.TYPE} ok-to-stop', + 'ids': daemon_ids, + })) + + if r.retval: + err = f'{err}: {r.stderr}' if r.stderr else err + logger.debug(err) + return HandleCommandResult(r.retval, r.stdout, err) + + out = f'{out}: {r.stdout}' if r.stdout else out + logger.debug(out) + return HandleCommandResult(r.retval, out, r.stderr) + + def _enough_daemons_to_stop(self, daemon_type: str, daemon_ids: List[str], service: str, low_limit: int, alert: bool = False) -> Tuple[bool, str]: + # Provides a warning about if it possible or not to stop daemons in a service + names = [f'{daemon_type}.{d_id}' for d_id in daemon_ids] + number_of_running_daemons = len( + [daemon + for daemon in self.mgr.cache.get_daemons_by_type(daemon_type) + if daemon.status == DaemonDescriptionStatus.running]) + if (number_of_running_daemons - len(daemon_ids)) >= low_limit: + return False, f'It is presumed safe to stop {names}' + + num_daemons_left = number_of_running_daemons - len(daemon_ids) + + def plural(count: int) -> str: + return 'daemon' if count == 1 else 'daemons' + + left_count = "no" if num_daemons_left == 0 else num_daemons_left + + if alert: + out = (f'ALERT: Cannot stop {names} in {service} service. ' + f'Not enough remaining {service} daemons. ' + f'Please deploy at least {low_limit + 1} {service} daemons before stopping {names}. ') + else: + out = (f'WARNING: Stopping {len(daemon_ids)} out of {number_of_running_daemons} daemons in {service} service. ' + f'Service will not be operational with {left_count} {plural(num_daemons_left)} left. ' + f'At least {low_limit} {plural(low_limit)} must be running to guarantee service. ') + return True, out + + def pre_remove(self, daemon: DaemonDescription) -> None: + """ + Called before the daemon is removed. + """ + assert daemon.daemon_type is not None + assert self.TYPE == daemon_type_to_service(daemon.daemon_type) + logger.debug(f'Pre remove daemon {self.TYPE}.{daemon.daemon_id}') + + def post_remove(self, daemon: DaemonDescription, is_failed_deploy: bool) -> None: + """ + Called after the daemon is removed. + """ + assert daemon.daemon_type is not None + assert self.TYPE == daemon_type_to_service(daemon.daemon_type) + logger.debug(f'Post remove daemon {self.TYPE}.{daemon.daemon_id}') + + def purge(self, service_name: str) -> None: + """Called to carry out any purge tasks following service removal""" + logger.debug(f'Purge called for {self.TYPE} - no action taken') + + +class CephService(CephadmService): + def generate_config(self, daemon_spec: CephadmDaemonDeploySpec) -> Tuple[Dict[str, Any], List[str]]: + # Ceph.daemons (mon, mgr, mds, osd, etc) + cephadm_config = self.get_config_and_keyring( + daemon_spec.daemon_type, + daemon_spec.daemon_id, + host=daemon_spec.host, + keyring=daemon_spec.keyring, + extra_ceph_config=daemon_spec.ceph_conf) + + if daemon_spec.config_get_files(): + cephadm_config.update({'files': daemon_spec.config_get_files()}) + + return cephadm_config, [] + + def post_remove(self, daemon: DaemonDescription, is_failed_deploy: bool) -> None: + super().post_remove(daemon, is_failed_deploy=is_failed_deploy) + self.remove_keyring(daemon) + + def get_auth_entity(self, daemon_id: str, host: str = "") -> AuthEntity: + """ + Map the daemon id to a cephx keyring entity name + """ + # despite this mapping entity names to daemons, self.TYPE within + # the CephService class refers to service types, not daemon types + if self.TYPE in ['rgw', 'rbd-mirror', 'cephfs-mirror', 'nfs', "iscsi", 'ingress']: + return AuthEntity(f'client.{self.TYPE}.{daemon_id}') + elif self.TYPE == 'crash': + if host == "": + raise OrchestratorError("Host not provided to generate auth entity name") + return AuthEntity(f'client.{self.TYPE}.{host}') + elif self.TYPE == 'mon': + return AuthEntity('mon.') + elif self.TYPE in ['mgr', 'osd', 'mds']: + return AuthEntity(f'{self.TYPE}.{daemon_id}') + else: + raise OrchestratorError("unknown daemon type") + + def get_config_and_keyring(self, + daemon_type: str, + daemon_id: str, + host: str, + keyring: Optional[str] = None, + extra_ceph_config: Optional[str] = None + ) -> Dict[str, Any]: + # keyring + if not keyring: + entity: AuthEntity = self.get_auth_entity(daemon_id, host=host) + ret, keyring, err = self.mgr.check_mon_command({ + 'prefix': 'auth get', + 'entity': entity, + }) + + config = self.mgr.get_minimal_ceph_conf() + + if extra_ceph_config: + config += extra_ceph_config + + return { + 'config': config, + 'keyring': keyring, + } + + def remove_keyring(self, daemon: DaemonDescription) -> None: + assert daemon.daemon_id is not None + assert daemon.hostname is not None + daemon_id: str = daemon.daemon_id + host: str = daemon.hostname + + assert daemon.daemon_type != 'mon' + + entity = self.get_auth_entity(daemon_id, host=host) + + logger.info(f'Removing key for {entity}') + ret, out, err = self.mgr.mon_command({ + 'prefix': 'auth rm', + 'entity': entity, + }) + + +class MonService(CephService): + TYPE = 'mon' + + def prepare_create(self, daemon_spec: CephadmDaemonDeploySpec) -> CephadmDaemonDeploySpec: + """ + Create a new monitor on the given host. + """ + assert self.TYPE == daemon_spec.daemon_type + name, _, network = daemon_spec.daemon_id, daemon_spec.host, daemon_spec.network + + # get mon. key + ret, keyring, err = self.mgr.check_mon_command({ + 'prefix': 'auth get', + 'entity': self.get_auth_entity(name), + }) + + extra_config = '[mon.%s]\n' % name + if network: + # infer whether this is a CIDR network, addrvec, or plain IP + if '/' in network: + extra_config += 'public network = %s\n' % network + elif network.startswith('[v') and network.endswith(']'): + extra_config += 'public addrv = %s\n' % network + elif is_ipv6(network): + extra_config += 'public addr = %s\n' % unwrap_ipv6(network) + elif ':' not in network: + extra_config += 'public addr = %s\n' % network + else: + raise OrchestratorError( + 'Must specify a CIDR network, ceph addrvec, or plain IP: \'%s\'' % network) + else: + # try to get the public_network from the config + ret, network, err = self.mgr.check_mon_command({ + 'prefix': 'config get', + 'who': 'mon', + 'key': 'public_network', + }) + network = network.strip() if network else network + if not network: + raise OrchestratorError( + 'Must set public_network config option or specify a CIDR network, ceph addrvec, or plain IP') + if '/' not in network: + raise OrchestratorError( + 'public_network is set but does not look like a CIDR network: \'%s\'' % network) + extra_config += 'public network = %s\n' % network + + daemon_spec.ceph_conf = extra_config + daemon_spec.keyring = keyring + + daemon_spec.final_config, daemon_spec.deps = self.generate_config(daemon_spec) + + return daemon_spec + + def _check_safe_to_destroy(self, mon_id: str) -> None: + ret, out, err = self.mgr.check_mon_command({ + 'prefix': 'quorum_status', + }) + try: + j = json.loads(out) + except Exception: + raise OrchestratorError('failed to parse quorum status') + + mons = [m['name'] for m in j['monmap']['mons']] + if mon_id not in mons: + logger.info('Safe to remove mon.%s: not in monmap (%s)' % ( + mon_id, mons)) + return + new_mons = [m for m in mons if m != mon_id] + new_quorum = [m for m in j['quorum_names'] if m != mon_id] + if len(new_quorum) > len(new_mons) / 2: + logger.info('Safe to remove mon.%s: new quorum should be %s (from %s)' % + (mon_id, new_quorum, new_mons)) + return + raise OrchestratorError( + 'Removing %s would break mon quorum (new quorum %s, new mons %s)' % (mon_id, new_quorum, new_mons)) + + def pre_remove(self, daemon: DaemonDescription) -> None: + super().pre_remove(daemon) + + assert daemon.daemon_id is not None + daemon_id: str = daemon.daemon_id + self._check_safe_to_destroy(daemon_id) + + # remove mon from quorum before we destroy the daemon + logger.info('Removing monitor %s from monmap...' % daemon_id) + ret, out, err = self.mgr.check_mon_command({ + 'prefix': 'mon rm', + 'name': daemon_id, + }) + + def post_remove(self, daemon: DaemonDescription, is_failed_deploy: bool) -> None: + # Do not remove the mon keyring. + # super().post_remove(daemon) + pass + + +class MgrService(CephService): + TYPE = 'mgr' + + def allow_colo(self) -> bool: + if self.mgr.get_ceph_option('mgr_standby_modules'): + # traditional mgr mode: standby daemons' modules listen on + # ports and redirect to the primary. we must not schedule + # multiple mgrs on the same host or else ports will + # conflict. + return False + else: + # standby daemons do nothing, and therefore port conflicts + # are not a concern. + return True + + def prepare_create(self, daemon_spec: CephadmDaemonDeploySpec) -> CephadmDaemonDeploySpec: + """ + Create a new manager instance on a host. + """ + assert self.TYPE == daemon_spec.daemon_type + mgr_id, _ = daemon_spec.daemon_id, daemon_spec.host + + # get mgr. key + keyring = self.get_keyring_with_caps(self.get_auth_entity(mgr_id), + ['mon', 'profile mgr', + 'osd', 'allow *', + 'mds', 'allow *']) + + # Retrieve ports used by manager modules + # In the case of the dashboard port and with several manager daemons + # running in different hosts, it exists the possibility that the + # user has decided to use different dashboard ports in each server + # If this is the case then the dashboard port opened will be only the used + # as default. + ports = [] + ret, mgr_services, err = self.mgr.check_mon_command({ + 'prefix': 'mgr services', + }) + if mgr_services: + mgr_endpoints = json.loads(mgr_services) + for end_point in mgr_endpoints.values(): + port = re.search(r'\:\d+\/', end_point) + if port: + ports.append(int(port[0][1:-1])) + + if ports: + daemon_spec.ports = ports + + daemon_spec.keyring = keyring + + daemon_spec.final_config, daemon_spec.deps = self.generate_config(daemon_spec) + + return daemon_spec + + def get_active_daemon(self, daemon_descrs: List[DaemonDescription]) -> DaemonDescription: + for daemon in daemon_descrs: + assert daemon.daemon_type is not None + assert daemon.daemon_id is not None + if self.mgr.daemon_is_self(daemon.daemon_type, daemon.daemon_id): + return daemon + # if no active mgr found, return empty Daemon Desc + return DaemonDescription() + + def fail_over(self) -> None: + # this has been seen to sometimes transiently fail even when there are multiple + # mgr daemons. As long as there are multiple known mgr daemons, we should retry. + class NoStandbyError(OrchestratorError): + pass + no_standby_exc = NoStandbyError('Need standby mgr daemon', event_kind_subject=( + 'daemon', 'mgr' + self.mgr.get_mgr_id())) + for sleep_secs in [2, 8, 15]: + try: + if not self.mgr_map_has_standby(): + raise no_standby_exc + self.mgr.events.for_daemon('mgr' + self.mgr.get_mgr_id(), + 'INFO', 'Failing over to other MGR') + logger.info('Failing over to other MGR') + + # fail over + ret, out, err = self.mgr.check_mon_command({ + 'prefix': 'mgr fail', + 'who': self.mgr.get_mgr_id(), + }) + return + except NoStandbyError: + logger.info( + f'Failed to find standby mgr for failover. Retrying in {sleep_secs} seconds') + time.sleep(sleep_secs) + raise no_standby_exc + + def mgr_map_has_standby(self) -> bool: + """ + This is a bit safer than asking our inventory. If the mgr joined the mgr map, + we know it joined the cluster + """ + mgr_map = self.mgr.get('mgr_map') + num = len(mgr_map.get('standbys')) + return bool(num) + + def ok_to_stop( + self, + daemon_ids: List[str], + force: bool = False, + known: Optional[List[str]] = None # output argument + ) -> HandleCommandResult: + # ok to stop if there is more than 1 mgr and not trying to stop the active mgr + + warn, warn_message = self._enough_daemons_to_stop(self.TYPE, daemon_ids, 'Mgr', 1, True) + if warn: + return HandleCommandResult(-errno.EBUSY, '', warn_message) + + mgr_daemons = self.mgr.cache.get_daemons_by_type(self.TYPE) + active = self.get_active_daemon(mgr_daemons).daemon_id + if active in daemon_ids: + warn_message = 'ALERT: Cannot stop active Mgr daemon, Please switch active Mgrs with \'ceph mgr fail %s\'' % active + return HandleCommandResult(-errno.EBUSY, '', warn_message) + + return HandleCommandResult(0, warn_message, '') + + +class MdsService(CephService): + TYPE = 'mds' + + def allow_colo(self) -> bool: + return True + + def config(self, spec: ServiceSpec) -> None: + assert self.TYPE == spec.service_type + assert spec.service_id + + # ensure mds_join_fs is set for these daemons + ret, out, err = self.mgr.check_mon_command({ + 'prefix': 'config set', + 'who': 'mds.' + spec.service_id, + 'name': 'mds_join_fs', + 'value': spec.service_id, + }) + + def prepare_create(self, daemon_spec: CephadmDaemonDeploySpec) -> CephadmDaemonDeploySpec: + assert self.TYPE == daemon_spec.daemon_type + mds_id, _ = daemon_spec.daemon_id, daemon_spec.host + + # get mds. key + keyring = self.get_keyring_with_caps(self.get_auth_entity(mds_id), + ['mon', 'profile mds', + 'osd', 'allow rw tag cephfs *=*', + 'mds', 'allow']) + daemon_spec.keyring = keyring + + daemon_spec.final_config, daemon_spec.deps = self.generate_config(daemon_spec) + + return daemon_spec + + def get_active_daemon(self, daemon_descrs: List[DaemonDescription]) -> DaemonDescription: + active_mds_strs = list() + for fs in self.mgr.get('fs_map')['filesystems']: + mds_map = fs['mdsmap'] + if mds_map is not None: + for mds_id, mds_status in mds_map['info'].items(): + if mds_status['state'] == 'up:active': + active_mds_strs.append(mds_status['name']) + if len(active_mds_strs) != 0: + for daemon in daemon_descrs: + if daemon.daemon_id in active_mds_strs: + return daemon + # if no mds found, return empty Daemon Desc + return DaemonDescription() + + def purge(self, service_name: str) -> None: + self.mgr.check_mon_command({ + 'prefix': 'config rm', + 'who': service_name, + 'name': 'mds_join_fs', + }) + + +class RgwService(CephService): + TYPE = 'rgw' + + def allow_colo(self) -> bool: + return True + + def config(self, spec: RGWSpec) -> None: # type: ignore + assert self.TYPE == spec.service_type + + # set rgw_realm and rgw_zone, if present + if spec.rgw_realm: + ret, out, err = self.mgr.check_mon_command({ + 'prefix': 'config set', + 'who': f"{utils.name_to_config_section('rgw')}.{spec.service_id}", + 'name': 'rgw_realm', + 'value': spec.rgw_realm, + }) + if spec.rgw_zone: + ret, out, err = self.mgr.check_mon_command({ + 'prefix': 'config set', + 'who': f"{utils.name_to_config_section('rgw')}.{spec.service_id}", + 'name': 'rgw_zone', + 'value': spec.rgw_zone, + }) + + if spec.rgw_frontend_ssl_certificate: + if isinstance(spec.rgw_frontend_ssl_certificate, list): + cert_data = '\n'.join(spec.rgw_frontend_ssl_certificate) + elif isinstance(spec.rgw_frontend_ssl_certificate, str): + cert_data = spec.rgw_frontend_ssl_certificate + else: + raise OrchestratorError( + 'Invalid rgw_frontend_ssl_certificate: %s' + % spec.rgw_frontend_ssl_certificate) + ret, out, err = self.mgr.check_mon_command({ + 'prefix': 'config-key set', + 'key': f'rgw/cert/{spec.service_name()}', + 'val': cert_data, + }) + + # TODO: fail, if we don't have a spec + logger.info('Saving service %s spec with placement %s' % ( + spec.service_name(), spec.placement.pretty_str())) + self.mgr.spec_store.save(spec) + self.mgr.trigger_connect_dashboard_rgw() + + def prepare_create(self, daemon_spec: CephadmDaemonDeploySpec) -> CephadmDaemonDeploySpec: + assert self.TYPE == daemon_spec.daemon_type + rgw_id, _ = daemon_spec.daemon_id, daemon_spec.host + spec = cast(RGWSpec, self.mgr.spec_store[daemon_spec.service_name].spec) + + keyring = self.get_keyring(rgw_id) + + if daemon_spec.ports: + port = daemon_spec.ports[0] + else: + # this is a redeploy of older instance that doesn't have an explicitly + # assigned port, in which case we can assume there is only 1 per host + # and it matches the spec. + port = spec.get_port() + + # configure frontend + args = [] + ftype = spec.rgw_frontend_type or "beast" + if ftype == 'beast': + if spec.ssl: + if daemon_spec.ip: + args.append( + f"ssl_endpoint={build_url(host=daemon_spec.ip, port=port).lstrip('/')}") + else: + args.append(f"ssl_port={port}") + args.append(f"ssl_certificate=config://rgw/cert/{spec.service_name()}") + else: + if daemon_spec.ip: + args.append(f"endpoint={build_url(host=daemon_spec.ip, port=port).lstrip('/')}") + else: + args.append(f"port={port}") + elif ftype == 'civetweb': + if spec.ssl: + if daemon_spec.ip: + # note the 's' suffix on port + args.append(f"port={build_url(host=daemon_spec.ip, port=port).lstrip('/')}s") + else: + args.append(f"port={port}s") # note the 's' suffix on port + args.append(f"ssl_certificate=config://rgw/cert/{spec.service_name()}") + else: + if daemon_spec.ip: + args.append(f"port={build_url(host=daemon_spec.ip, port=port).lstrip('/')}") + else: + args.append(f"port={port}") + frontend = f'{ftype} {" ".join(args)}' + + ret, out, err = self.mgr.check_mon_command({ + 'prefix': 'config set', + 'who': utils.name_to_config_section(daemon_spec.name()), + 'name': 'rgw_frontends', + 'value': frontend + }) + + daemon_spec.keyring = keyring + daemon_spec.final_config, daemon_spec.deps = self.generate_config(daemon_spec) + + return daemon_spec + + def get_keyring(self, rgw_id: str) -> str: + keyring = self.get_keyring_with_caps(self.get_auth_entity(rgw_id), + ['mon', 'allow *', + 'mgr', 'allow rw', + 'osd', 'allow rwx tag rgw *=*']) + return keyring + + def purge(self, service_name: str) -> None: + self.mgr.check_mon_command({ + 'prefix': 'config rm', + 'who': utils.name_to_config_section(service_name), + 'name': 'rgw_realm', + }) + self.mgr.check_mon_command({ + 'prefix': 'config rm', + 'who': utils.name_to_config_section(service_name), + 'name': 'rgw_zone', + }) + self.mgr.check_mon_command({ + 'prefix': 'config-key rm', + 'key': f'rgw/cert/{service_name}', + }) + self.mgr.trigger_connect_dashboard_rgw() + + def post_remove(self, daemon: DaemonDescription, is_failed_deploy: bool) -> None: + super().post_remove(daemon, is_failed_deploy=is_failed_deploy) + self.mgr.check_mon_command({ + 'prefix': 'config rm', + 'who': utils.name_to_config_section(daemon.name()), + 'name': 'rgw_frontends', + }) + + def ok_to_stop( + self, + daemon_ids: List[str], + force: bool = False, + known: Optional[List[str]] = None # output argument + ) -> HandleCommandResult: + # if load balancer (ingress) is present block if only 1 daemon up otherwise ok + # if no load balancer, warn if > 1 daemon, block if only 1 daemon + def ingress_present() -> bool: + running_ingress_daemons = [ + daemon for daemon in self.mgr.cache.get_daemons_by_type('ingress') if daemon.status == 1] + running_haproxy_daemons = [ + daemon for daemon in running_ingress_daemons if daemon.daemon_type == 'haproxy'] + running_keepalived_daemons = [ + daemon for daemon in running_ingress_daemons if daemon.daemon_type == 'keepalived'] + # check that there is at least one haproxy and keepalived daemon running + if running_haproxy_daemons and running_keepalived_daemons: + return True + return False + + # if only 1 rgw, alert user (this is not passable with --force) + warn, warn_message = self._enough_daemons_to_stop(self.TYPE, daemon_ids, 'RGW', 1, True) + if warn: + return HandleCommandResult(-errno.EBUSY, '', warn_message) + + # if reached here, there is > 1 rgw daemon. + # Say okay if load balancer present or force flag set + if ingress_present() or force: + return HandleCommandResult(0, warn_message, '') + + # if reached here, > 1 RGW daemon, no load balancer and no force flag. + # Provide warning + warn_message = "WARNING: Removing RGW daemons can cause clients to lose connectivity. " + return HandleCommandResult(-errno.EBUSY, '', warn_message) + + def config_dashboard(self, daemon_descrs: List[DaemonDescription]) -> None: + self.mgr.trigger_connect_dashboard_rgw() + + +class RbdMirrorService(CephService): + TYPE = 'rbd-mirror' + + def allow_colo(self) -> bool: + return True + + def prepare_create(self, daemon_spec: CephadmDaemonDeploySpec) -> CephadmDaemonDeploySpec: + assert self.TYPE == daemon_spec.daemon_type + daemon_id, _ = daemon_spec.daemon_id, daemon_spec.host + + keyring = self.get_keyring_with_caps(self.get_auth_entity(daemon_id), + ['mon', 'profile rbd-mirror', + 'osd', 'profile rbd']) + + daemon_spec.keyring = keyring + + daemon_spec.final_config, daemon_spec.deps = self.generate_config(daemon_spec) + + return daemon_spec + + def ok_to_stop( + self, + daemon_ids: List[str], + force: bool = False, + known: Optional[List[str]] = None # output argument + ) -> HandleCommandResult: + # if only 1 rbd-mirror, alert user (this is not passable with --force) + warn, warn_message = self._enough_daemons_to_stop( + self.TYPE, daemon_ids, 'Rbdmirror', 1, True) + if warn: + return HandleCommandResult(-errno.EBUSY, '', warn_message) + return HandleCommandResult(0, warn_message, '') + + +class CrashService(CephService): + TYPE = 'crash' + + def prepare_create(self, daemon_spec: CephadmDaemonDeploySpec) -> CephadmDaemonDeploySpec: + assert self.TYPE == daemon_spec.daemon_type + daemon_id, host = daemon_spec.daemon_id, daemon_spec.host + + keyring = self.get_keyring_with_caps(self.get_auth_entity(daemon_id, host=host), + ['mon', 'profile crash', + 'mgr', 'profile crash']) + + daemon_spec.keyring = keyring + + daemon_spec.final_config, daemon_spec.deps = self.generate_config(daemon_spec) + + return daemon_spec + + +class CephfsMirrorService(CephService): + TYPE = 'cephfs-mirror' + + def config(self, spec: ServiceSpec) -> None: + # make sure mirroring module is enabled + mgr_map = self.mgr.get('mgr_map') + mod_name = 'mirroring' + if mod_name not in mgr_map.get('services', {}): + self.mgr.check_mon_command({ + 'prefix': 'mgr module enable', + 'module': mod_name + }) + # we shouldn't get here (mon will tell the mgr to respawn), but no + # harm done if we do. + + def prepare_create(self, daemon_spec: CephadmDaemonDeploySpec) -> CephadmDaemonDeploySpec: + assert self.TYPE == daemon_spec.daemon_type + + ret, keyring, err = self.mgr.check_mon_command({ + 'prefix': 'auth get-or-create', + 'entity': self.get_auth_entity(daemon_spec.daemon_id), + 'caps': ['mon', 'profile cephfs-mirror', + 'mds', 'allow r', + 'osd', 'allow rw tag cephfs metadata=*, allow r tag cephfs data=*', + 'mgr', 'allow r'], + }) + + daemon_spec.keyring = keyring + daemon_spec.final_config, daemon_spec.deps = self.generate_config(daemon_spec) + return daemon_spec diff --git a/src/pybind/mgr/cephadm/services/container.py b/src/pybind/mgr/cephadm/services/container.py new file mode 100644 index 000000000..b9cdfad5e --- /dev/null +++ b/src/pybind/mgr/cephadm/services/container.py @@ -0,0 +1,29 @@ +import logging +from typing import List, Any, Tuple, Dict, cast + +from ceph.deployment.service_spec import CustomContainerSpec + +from .cephadmservice import CephadmService, CephadmDaemonDeploySpec + +logger = logging.getLogger(__name__) + + +class CustomContainerService(CephadmService): + TYPE = 'container' + + def prepare_create(self, daemon_spec: CephadmDaemonDeploySpec) \ + -> CephadmDaemonDeploySpec: + assert self.TYPE == daemon_spec.daemon_type + daemon_spec.final_config, daemon_spec.deps = self.generate_config(daemon_spec) + return daemon_spec + + def generate_config(self, daemon_spec: CephadmDaemonDeploySpec) \ + -> Tuple[Dict[str, Any], List[str]]: + assert self.TYPE == daemon_spec.daemon_type + deps: List[str] = [] + spec = cast(CustomContainerSpec, self.mgr.spec_store[daemon_spec.service_name].spec) + config: Dict[str, Any] = spec.config_json() + logger.debug( + 'Generated configuration for \'%s\' service: config-json=%s, dependencies=%s' % + (self.TYPE, config, deps)) + return config, deps diff --git a/src/pybind/mgr/cephadm/services/exporter.py b/src/pybind/mgr/cephadm/services/exporter.py new file mode 100644 index 000000000..b9c7d85e6 --- /dev/null +++ b/src/pybind/mgr/cephadm/services/exporter.py @@ -0,0 +1,147 @@ +import json +import logging +from typing import TYPE_CHECKING, List, Dict, Any, Tuple + +from orchestrator import OrchestratorError +from mgr_util import ServerConfigException, verify_tls + +from .cephadmservice import CephadmService, CephadmDaemonDeploySpec + +if TYPE_CHECKING: + from cephadm.module import CephadmOrchestrator + +logger = logging.getLogger(__name__) + + +class CephadmExporterConfig: + required_keys = ['crt', 'key', 'token', 'port'] + DEFAULT_PORT = '9443' + + def __init__(self, mgr: "CephadmOrchestrator", crt: str = "", key: str = "", + token: str = "", port: str = "") -> None: + self.mgr = mgr + self.crt = crt + self.key = key + self.token = token + self.port = port + + @property + def ready(self) -> bool: + return all([self.crt, self.key, self.token, self.port]) + + def load_from_store(self) -> None: + cfg = self.mgr._get_exporter_config() + + assert isinstance(cfg, dict) + self.crt = cfg.get('crt', "") + self.key = cfg.get('key', "") + self.token = cfg.get('token', "") + self.port = cfg.get('port', "") + + def load_from_json(self, json_str: str) -> Tuple[int, str]: + try: + cfg = json.loads(json_str) + except ValueError: + return 1, "Invalid JSON provided - unable to load" + + if not all([k in cfg for k in CephadmExporterConfig.required_keys]): + return 1, "JSON file must contain crt, key, token and port" + + self.crt = cfg.get('crt') + self.key = cfg.get('key') + self.token = cfg.get('token') + self.port = cfg.get('port') + + return 0, "" + + def validate_config(self) -> Tuple[int, str]: + if not self.ready: + return 1, "Incomplete configuration. cephadm-exporter needs crt, key, token and port to be set" + + for check in [self._validate_tls, self._validate_token, self._validate_port]: + rc, reason = check() + if rc: + return 1, reason + + return 0, "" + + def _validate_tls(self) -> Tuple[int, str]: + + try: + verify_tls(self.crt, self.key) + except ServerConfigException as e: + return 1, str(e) + + return 0, "" + + def _validate_token(self) -> Tuple[int, str]: + if not isinstance(self.token, str): + return 1, "token must be a string" + if len(self.token) < 8: + return 1, "Token must be a string of at least 8 chars in length" + + return 0, "" + + def _validate_port(self) -> Tuple[int, str]: + try: + p = int(str(self.port)) + if p <= 1024: + raise ValueError + except ValueError: + return 1, "Port must be a integer (>1024)" + + return 0, "" + + +class CephadmExporter(CephadmService): + TYPE = 'cephadm-exporter' + + def prepare_create(self, daemon_spec: CephadmDaemonDeploySpec) -> CephadmDaemonDeploySpec: + assert self.TYPE == daemon_spec.daemon_type + + cfg = CephadmExporterConfig(self.mgr) + cfg.load_from_store() + + if cfg.ready: + rc, reason = cfg.validate_config() + if rc: + raise OrchestratorError(reason) + else: + logger.info( + "Incomplete/Missing configuration, applying defaults") + self.mgr._set_exporter_defaults() + cfg.load_from_store() + + if not daemon_spec.ports: + daemon_spec.ports = [int(cfg.port)] + + daemon_spec.final_config, daemon_spec.deps = self.generate_config(daemon_spec) + + return daemon_spec + + def generate_config(self, daemon_spec: CephadmDaemonDeploySpec) -> Tuple[Dict[str, Any], List[str]]: + assert self.TYPE == daemon_spec.daemon_type + deps: List[str] = [] + + cfg = CephadmExporterConfig(self.mgr) + cfg.load_from_store() + + if cfg.ready: + rc, reason = cfg.validate_config() + if rc: + raise OrchestratorError(reason) + else: + logger.info("Using default configuration for cephadm-exporter") + self.mgr._set_exporter_defaults() + cfg.load_from_store() + + config = { + "crt": cfg.crt, + "key": cfg.key, + "token": cfg.token + } + return config, deps + + def purge(self, service_name: str) -> None: + logger.info("Purging cephadm-exporter settings from mon K/V store") + self.mgr._clear_exporter_config_settings() diff --git a/src/pybind/mgr/cephadm/services/ingress.py b/src/pybind/mgr/cephadm/services/ingress.py new file mode 100644 index 000000000..99fde1c43 --- /dev/null +++ b/src/pybind/mgr/cephadm/services/ingress.py @@ -0,0 +1,296 @@ +import ipaddress +import logging +import random +import string +from typing import List, Dict, Any, Tuple, cast, Optional + +from ceph.deployment.service_spec import IngressSpec +from mgr_util import build_url +from cephadm.utils import resolve_ip +from orchestrator import OrchestratorError +from cephadm.services.cephadmservice import CephadmDaemonDeploySpec, CephService + +logger = logging.getLogger(__name__) + + +class IngressService(CephService): + TYPE = 'ingress' + + def primary_daemon_type(self) -> str: + return 'haproxy' + + def per_host_daemon_type(self) -> Optional[str]: + return 'keepalived' + + def prepare_create( + self, + daemon_spec: CephadmDaemonDeploySpec, + ) -> CephadmDaemonDeploySpec: + if daemon_spec.daemon_type == 'haproxy': + return self.haproxy_prepare_create(daemon_spec) + if daemon_spec.daemon_type == 'keepalived': + return self.keepalived_prepare_create(daemon_spec) + assert False, "unexpected daemon type" + + def generate_config( + self, + daemon_spec: CephadmDaemonDeploySpec + ) -> Tuple[Dict[str, Any], List[str]]: + if daemon_spec.daemon_type == 'haproxy': + return self.haproxy_generate_config(daemon_spec) + else: + return self.keepalived_generate_config(daemon_spec) + assert False, "unexpected daemon type" + + def haproxy_prepare_create( + self, + daemon_spec: CephadmDaemonDeploySpec, + ) -> CephadmDaemonDeploySpec: + assert daemon_spec.daemon_type == 'haproxy' + + daemon_id = daemon_spec.daemon_id + host = daemon_spec.host + spec = cast(IngressSpec, self.mgr.spec_store[daemon_spec.service_name].spec) + + logger.debug('prepare_create haproxy.%s on host %s with spec %s' % ( + daemon_id, host, spec)) + + daemon_spec.final_config, daemon_spec.deps = self.haproxy_generate_config(daemon_spec) + + return daemon_spec + + def haproxy_generate_config( + self, + daemon_spec: CephadmDaemonDeploySpec, + ) -> Tuple[Dict[str, Any], List[str]]: + spec = cast(IngressSpec, self.mgr.spec_store[daemon_spec.service_name].spec) + assert spec.backend_service + if spec.backend_service not in self.mgr.spec_store: + raise RuntimeError( + f'{spec.service_name()} backend service {spec.backend_service} does not exist') + backend_spec = self.mgr.spec_store[spec.backend_service].spec + daemons = self.mgr.cache.get_daemons_by_service(spec.backend_service) + deps = [d.name() for d in daemons] + + # generate password? + pw_key = f'{spec.service_name()}/monitor_password' + password = self.mgr.get_store(pw_key) + if password is None: + if not spec.monitor_password: + password = ''.join(random.choice(string.ascii_lowercase) for _ in range(20)) + self.mgr.set_store(pw_key, password) + else: + if spec.monitor_password: + self.mgr.set_store(pw_key, None) + if spec.monitor_password: + password = spec.monitor_password + + if backend_spec.service_type == 'nfs': + mode = 'tcp' + by_rank = {d.rank: d for d in daemons if d.rank is not None} + servers = [] + + # try to establish how many ranks we *should* have + num_ranks = backend_spec.placement.count + if not num_ranks: + num_ranks = 1 + max(by_rank.keys()) + + for rank in range(num_ranks): + if rank in by_rank: + d = by_rank[rank] + assert d.ports + servers.append({ + 'name': f"{spec.backend_service}.{rank}", + 'ip': d.ip or resolve_ip(self.mgr.inventory.get_addr(str(d.hostname))), + 'port': d.ports[0], + }) + else: + # offline/missing server; leave rank in place + servers.append({ + 'name': f"{spec.backend_service}.{rank}", + 'ip': '0.0.0.0', + 'port': 0, + }) + else: + mode = 'http' + servers = [ + { + 'name': d.name(), + 'ip': d.ip or resolve_ip(self.mgr.inventory.get_addr(str(d.hostname))), + 'port': d.ports[0], + } for d in daemons if d.ports + ] + + haproxy_conf = self.mgr.template.render( + 'services/ingress/haproxy.cfg.j2', + { + 'spec': spec, + 'mode': mode, + 'servers': servers, + 'user': spec.monitor_user or 'admin', + 'password': password, + 'ip': "*" if spec.virtual_ips_list else str(spec.virtual_ip).split('/')[0] or daemon_spec.ip or '*', + 'frontend_port': daemon_spec.ports[0] if daemon_spec.ports else spec.frontend_port, + 'monitor_port': daemon_spec.ports[1] if daemon_spec.ports else spec.monitor_port, + } + ) + config_files = { + 'files': { + "haproxy.cfg": haproxy_conf, + } + } + if spec.ssl_cert: + ssl_cert = spec.ssl_cert + if isinstance(ssl_cert, list): + ssl_cert = '\n'.join(ssl_cert) + config_files['files']['haproxy.pem'] = ssl_cert + + return config_files, sorted(deps) + + def keepalived_prepare_create( + self, + daemon_spec: CephadmDaemonDeploySpec, + ) -> CephadmDaemonDeploySpec: + assert daemon_spec.daemon_type == 'keepalived' + + daemon_id = daemon_spec.daemon_id + host = daemon_spec.host + spec = cast(IngressSpec, self.mgr.spec_store[daemon_spec.service_name].spec) + + logger.debug('prepare_create keepalived.%s on host %s with spec %s' % ( + daemon_id, host, spec)) + + daemon_spec.final_config, daemon_spec.deps = self.keepalived_generate_config(daemon_spec) + + return daemon_spec + + def keepalived_generate_config( + self, + daemon_spec: CephadmDaemonDeploySpec, + ) -> Tuple[Dict[str, Any], List[str]]: + spec = cast(IngressSpec, self.mgr.spec_store[daemon_spec.service_name].spec) + assert spec.backend_service + + # generate password? + pw_key = f'{spec.service_name()}/keepalived_password' + password = self.mgr.get_store(pw_key) + if password is None: + if not spec.keepalived_password: + password = ''.join(random.choice(string.ascii_lowercase) for _ in range(20)) + self.mgr.set_store(pw_key, password) + else: + if spec.keepalived_password: + self.mgr.set_store(pw_key, None) + if spec.keepalived_password: + password = spec.keepalived_password + + daemons = self.mgr.cache.get_daemons_by_service(spec.service_name()) + + if not daemons: + raise OrchestratorError( + f'Failed to generate keepalived.conf: No daemons deployed for {spec.service_name()}') + + deps = sorted([d.name() for d in daemons if d.daemon_type == 'haproxy']) + + host = daemon_spec.host + hosts = sorted(list(set([host] + [str(d.hostname) for d in daemons]))) + + # interface + bare_ips = [] + if spec.virtual_ip: + bare_ips.append(str(spec.virtual_ip).split('/')[0]) + elif spec.virtual_ips_list: + bare_ips = [str(vip).split('/')[0] for vip in spec.virtual_ips_list] + interface = None + for bare_ip in bare_ips: + for subnet, ifaces in self.mgr.cache.networks.get(host, {}).items(): + if ifaces and ipaddress.ip_address(bare_ip) in ipaddress.ip_network(subnet): + interface = list(ifaces.keys())[0] + logger.info( + f'{bare_ip} is in {subnet} on {host} interface {interface}' + ) + break + else: # nobreak + continue + break + # try to find interface by matching spec.virtual_interface_networks + if not interface and spec.virtual_interface_networks: + for subnet, ifaces in self.mgr.cache.networks.get(host, {}).items(): + if subnet in spec.virtual_interface_networks: + interface = list(ifaces.keys())[0] + logger.info( + f'{spec.virtual_ip} will be configured on {host} interface ' + f'{interface} (which has guiding subnet {subnet})' + ) + break + if not interface: + raise OrchestratorError( + f"Unable to identify interface for {spec.virtual_ip} on {host}" + ) + + # script to monitor health + script = '/usr/bin/false' + for d in daemons: + if d.hostname == host: + if d.daemon_type == 'haproxy': + assert d.ports + port = d.ports[1] # monitoring port + script = f'/usr/bin/curl {build_url(scheme="http", host=d.ip or "localhost", port=port)}/health' + assert script + + states = [] + priorities = [] + virtual_ips = [] + + # Set state and priority. Have one master for each VIP. Or at least the first one as master if only one VIP. + if spec.virtual_ip: + virtual_ips.append(spec.virtual_ip) + if hosts[0] == host: + states.append('MASTER') + priorities.append(100) + else: + states.append('BACKUP') + priorities.append(90) + + elif spec.virtual_ips_list: + virtual_ips = spec.virtual_ips_list + if len(virtual_ips) > len(hosts): + raise OrchestratorError( + "Number of virtual IPs for ingress is greater than number of available hosts" + ) + for x in range(len(virtual_ips)): + if hosts[x] == host: + states.append('MASTER') + priorities.append(100) + else: + states.append('BACKUP') + priorities.append(90) + + # remove host, daemon is being deployed on from hosts list for + # other_ips in conf file and converter to ips + if host in hosts: + hosts.remove(host) + other_ips = [resolve_ip(self.mgr.inventory.get_addr(h)) for h in hosts] + + keepalived_conf = self.mgr.template.render( + 'services/ingress/keepalived.conf.j2', + { + 'spec': spec, + 'script': script, + 'password': password, + 'interface': interface, + 'virtual_ips': virtual_ips, + 'states': states, + 'priorities': priorities, + 'other_ips': other_ips, + 'host_ip': resolve_ip(self.mgr.inventory.get_addr(host)), + } + ) + + config_file = { + 'files': { + "keepalived.conf": keepalived_conf, + } + } + + return config_file, deps diff --git a/src/pybind/mgr/cephadm/services/iscsi.py b/src/pybind/mgr/cephadm/services/iscsi.py new file mode 100644 index 000000000..c42eff683 --- /dev/null +++ b/src/pybind/mgr/cephadm/services/iscsi.py @@ -0,0 +1,210 @@ +import errno +import json +import logging +import subprocess +from typing import List, cast, Optional +from ipaddress import ip_address, IPv6Address + +from mgr_module import HandleCommandResult +from ceph.deployment.service_spec import IscsiServiceSpec + +from orchestrator import DaemonDescription, DaemonDescriptionStatus +from .cephadmservice import CephadmDaemonDeploySpec, CephService +from .. import utils + +logger = logging.getLogger(__name__) + + +class IscsiService(CephService): + TYPE = 'iscsi' + + def config(self, spec: IscsiServiceSpec) -> None: # type: ignore + assert self.TYPE == spec.service_type + assert spec.pool + self.mgr._check_pool_exists(spec.pool, spec.service_name()) + + def get_trusted_ips(self, spec: IscsiServiceSpec) -> str: + # add active mgr ip address to trusted list so dashboard can access + trusted_ip_list = spec.trusted_ip_list if spec.trusted_ip_list else '' + if trusted_ip_list: + trusted_ip_list += ',' + trusted_ip_list += self.mgr.get_mgr_ip() + return trusted_ip_list + + def prepare_create(self, daemon_spec: CephadmDaemonDeploySpec) -> CephadmDaemonDeploySpec: + assert self.TYPE == daemon_spec.daemon_type + + spec = cast(IscsiServiceSpec, self.mgr.spec_store[daemon_spec.service_name].spec) + igw_id = daemon_spec.daemon_id + + keyring = self.get_keyring_with_caps(self.get_auth_entity(igw_id), + ['mon', 'profile rbd, ' + 'allow command "osd blocklist", ' + 'allow command "config-key get" with "key" prefix "iscsi/"', + 'mgr', 'allow command "service status"', + 'osd', 'allow rwx']) + + if spec.ssl_cert: + if isinstance(spec.ssl_cert, list): + cert_data = '\n'.join(spec.ssl_cert) + else: + cert_data = spec.ssl_cert + ret, out, err = self.mgr.check_mon_command({ + 'prefix': 'config-key set', + 'key': f'iscsi/{utils.name_to_config_section("iscsi")}.{igw_id}/iscsi-gateway.crt', + 'val': cert_data, + }) + + if spec.ssl_key: + if isinstance(spec.ssl_key, list): + key_data = '\n'.join(spec.ssl_key) + else: + key_data = spec.ssl_key + ret, out, err = self.mgr.check_mon_command({ + 'prefix': 'config-key set', + 'key': f'iscsi/{utils.name_to_config_section("iscsi")}.{igw_id}/iscsi-gateway.key', + 'val': key_data, + }) + + trusted_ip_list = self.get_trusted_ips(spec) + + context = { + 'client_name': '{}.{}'.format(utils.name_to_config_section('iscsi'), igw_id), + 'trusted_ip_list': trusted_ip_list, + 'spec': spec + } + igw_conf = self.mgr.template.render('services/iscsi/iscsi-gateway.cfg.j2', context) + + daemon_spec.keyring = keyring + daemon_spec.extra_files = {'iscsi-gateway.cfg': igw_conf} + daemon_spec.final_config, daemon_spec.deps = self.generate_config(daemon_spec) + daemon_spec.deps = [trusted_ip_list] + return daemon_spec + + def config_dashboard(self, daemon_descrs: List[DaemonDescription]) -> None: + def get_set_cmd_dicts(out: str) -> List[dict]: + gateways = json.loads(out)['gateways'] + cmd_dicts = [] + # TODO: fail, if we don't have a spec + spec = cast(IscsiServiceSpec, + self.mgr.spec_store.all_specs.get(daemon_descrs[0].service_name(), None)) + if spec.api_secure and spec.ssl_cert and spec.ssl_key: + cmd_dicts.append({ + 'prefix': 'dashboard set-iscsi-api-ssl-verification', + 'value': "false" + }) + else: + cmd_dicts.append({ + 'prefix': 'dashboard set-iscsi-api-ssl-verification', + 'value': "true" + }) + for dd in daemon_descrs: + assert dd.hostname is not None + # todo: this can fail: + spec = cast(IscsiServiceSpec, + self.mgr.spec_store.all_specs.get(dd.service_name(), None)) + if not spec: + logger.warning('No ServiceSpec found for %s', dd) + continue + ip = utils.resolve_ip(self.mgr.inventory.get_addr(dd.hostname)) + # IPv6 URL encoding requires square brackets enclosing the ip + if type(ip_address(ip)) is IPv6Address: + ip = f'[{ip}]' + protocol = "http" + if spec.api_secure and spec.ssl_cert and spec.ssl_key: + protocol = "https" + service_url = '{}://{}:{}@{}:{}'.format( + protocol, spec.api_user or 'admin', spec.api_password or 'admin', ip, spec.api_port or '5000') + gw = gateways.get(dd.hostname) + if not gw or gw['service_url'] != service_url: + safe_service_url = '{}://{}:{}@{}:{}'.format( + protocol, '', '', ip, spec.api_port or '5000') + logger.info('Adding iSCSI gateway %s to Dashboard', safe_service_url) + cmd_dicts.append({ + 'prefix': 'dashboard iscsi-gateway-add', + 'inbuf': service_url, + 'name': dd.hostname + }) + return cmd_dicts + + self._check_and_set_dashboard( + service_name='iSCSI', + get_cmd='dashboard iscsi-gateway-list', + get_set_cmd_dicts=get_set_cmd_dicts + ) + + def ok_to_stop(self, + daemon_ids: List[str], + force: bool = False, + known: Optional[List[str]] = None) -> HandleCommandResult: + # if only 1 iscsi, alert user (this is not passable with --force) + warn, warn_message = self._enough_daemons_to_stop(self.TYPE, daemon_ids, 'Iscsi', 1, True) + if warn: + return HandleCommandResult(-errno.EBUSY, '', warn_message) + + # if reached here, there is > 1 nfs daemon. make sure none are down + warn_message = ( + 'ALERT: 1 iscsi daemon is already down. Please bring it back up before stopping this one') + iscsi_daemons = self.mgr.cache.get_daemons_by_type(self.TYPE) + for i in iscsi_daemons: + if i.status != DaemonDescriptionStatus.running: + return HandleCommandResult(-errno.EBUSY, '', warn_message) + + names = [f'{self.TYPE}.{d_id}' for d_id in daemon_ids] + warn_message = f'It is presumed safe to stop {names}' + return HandleCommandResult(0, warn_message, '') + + def post_remove(self, daemon: DaemonDescription, is_failed_deploy: bool) -> None: + """ + Called after the daemon is removed. + """ + logger.debug(f'Post remove daemon {self.TYPE}.{daemon.daemon_id}') + + # remove config for dashboard iscsi gateways + ret, out, err = self.mgr.mon_command({ + 'prefix': 'dashboard iscsi-gateway-rm', + 'name': daemon.hostname, + }) + if not ret: + logger.info(f'{daemon.hostname} removed from iscsi gateways dashboard config') + + # needed to know if we have ssl stuff for iscsi in ceph config + iscsi_config_dict = {} + ret, iscsi_config, err = self.mgr.mon_command({ + 'prefix': 'config-key dump', + 'key': 'iscsi', + }) + if iscsi_config: + iscsi_config_dict = json.loads(iscsi_config) + + # remove iscsi cert and key from ceph config + for iscsi_key, value in iscsi_config_dict.items(): + if f'iscsi/client.{daemon.name()}/' in iscsi_key: + ret, out, err = self.mgr.mon_command({ + 'prefix': 'config-key rm', + 'key': iscsi_key, + }) + logger.info(f'{iscsi_key} removed from ceph config') + + def purge(self, service_name: str) -> None: + """Removes configuration + """ + spec = cast(IscsiServiceSpec, self.mgr.spec_store[service_name].spec) + try: + # remove service configuration from the pool + try: + subprocess.run(['rados', + '-k', str(self.mgr.get_ceph_option('keyring')), + '-n', f'mgr.{self.mgr.get_mgr_id()}', + '-p', cast(str, spec.pool), + 'rm', + 'gateway.conf'], + timeout=5) + logger.info(f' removed from {spec.pool}') + except subprocess.CalledProcessError as ex: + logger.error(f'Error executing <<{ex.cmd}>>: {ex.output}') + except subprocess.TimeoutExpired: + logger.error(f'timeout (5s) trying to remove from {spec.pool}') + + except Exception: + logger.exception(f'failed to purge {service_name}') diff --git a/src/pybind/mgr/cephadm/services/monitoring.py b/src/pybind/mgr/cephadm/services/monitoring.py new file mode 100644 index 000000000..8de7195a3 --- /dev/null +++ b/src/pybind/mgr/cephadm/services/monitoring.py @@ -0,0 +1,502 @@ +import errno +import ipaddress +import logging +import os +import socket +from typing import List, Any, Tuple, Dict, Optional, cast +from urllib.parse import urlparse + +from mgr_module import HandleCommandResult + +from orchestrator import DaemonDescription +from ceph.deployment.service_spec import AlertManagerSpec, GrafanaSpec, ServiceSpec, \ + SNMPGatewaySpec, PrometheusSpec +from cephadm.services.cephadmservice import CephadmService, CephadmDaemonDeploySpec +from cephadm.services.ingress import IngressSpec +from mgr_util import verify_tls, ServerConfigException, create_self_signed_cert, build_url + +logger = logging.getLogger(__name__) + + +class GrafanaService(CephadmService): + TYPE = 'grafana' + DEFAULT_SERVICE_PORT = 3000 + + def prepare_create(self, daemon_spec: CephadmDaemonDeploySpec) -> CephadmDaemonDeploySpec: + assert self.TYPE == daemon_spec.daemon_type + daemon_spec.final_config, daemon_spec.deps = self.generate_config(daemon_spec) + return daemon_spec + + def generate_config(self, daemon_spec: CephadmDaemonDeploySpec) -> Tuple[Dict[str, Any], List[str]]: + assert self.TYPE == daemon_spec.daemon_type + deps = [] # type: List[str] + + prom_services = [] # type: List[str] + for dd in self.mgr.cache.get_daemons_by_service('prometheus'): + assert dd.hostname is not None + addr = dd.ip if dd.ip else self._inventory_get_fqdn(dd.hostname) + port = dd.ports[0] if dd.ports else 9095 + prom_services.append(build_url(scheme='http', host=addr, port=port)) + + deps.append(dd.name()) + grafana_data_sources = self.mgr.template.render( + 'services/grafana/ceph-dashboard.yml.j2', {'hosts': prom_services}) + + cert_path = f'{daemon_spec.host}/grafana_crt' + key_path = f'{daemon_spec.host}/grafana_key' + cert = self.mgr.get_store(cert_path) + pkey = self.mgr.get_store(key_path) + if cert and pkey: + try: + verify_tls(cert, pkey) + except ServerConfigException as e: + logger.warning('Provided grafana TLS certificates invalid: %s', str(e)) + cert, pkey = None, None + if not (cert and pkey): + cert, pkey = create_self_signed_cert('Ceph', daemon_spec.host) + self.mgr.set_store(cert_path, cert) + self.mgr.set_store(key_path, pkey) + if 'dashboard' in self.mgr.get('mgr_map')['modules']: + self.mgr.check_mon_command({ + 'prefix': 'dashboard set-grafana-api-ssl-verify', + 'value': 'false', + }) + + spec: GrafanaSpec = cast( + GrafanaSpec, self.mgr.spec_store.active_specs[daemon_spec.service_name]) + grafana_ini = self.mgr.template.render( + 'services/grafana/grafana.ini.j2', { + 'initial_admin_password': spec.initial_admin_password, + 'http_port': daemon_spec.ports[0] if daemon_spec.ports else self.DEFAULT_SERVICE_PORT, + 'http_addr': daemon_spec.ip if daemon_spec.ip else '' + }) + + if 'dashboard' in self.mgr.get('mgr_map')['modules'] and spec.initial_admin_password: + self.mgr.check_mon_command( + {'prefix': 'dashboard set-grafana-api-password'}, inbuf=spec.initial_admin_password) + + config_file = { + 'files': { + "grafana.ini": grafana_ini, + 'provisioning/datasources/ceph-dashboard.yml': grafana_data_sources, + 'certs/cert_file': '# generated by cephadm\n%s' % cert, + 'certs/cert_key': '# generated by cephadm\n%s' % pkey, + } + } + return config_file, sorted(deps) + + def get_active_daemon(self, daemon_descrs: List[DaemonDescription]) -> DaemonDescription: + # Use the least-created one as the active daemon + if daemon_descrs: + return daemon_descrs[-1] + # if empty list provided, return empty Daemon Desc + return DaemonDescription() + + def config_dashboard(self, daemon_descrs: List[DaemonDescription]) -> None: + # TODO: signed cert + dd = self.get_active_daemon(daemon_descrs) + assert dd.hostname is not None + addr = dd.ip if dd.ip else self._inventory_get_fqdn(dd.hostname) + port = dd.ports[0] if dd.ports else self.DEFAULT_SERVICE_PORT + service_url = build_url(scheme='https', host=addr, port=port) + self._set_service_url_on_dashboard( + 'Grafana', + 'dashboard get-grafana-api-url', + 'dashboard set-grafana-api-url', + service_url + ) + + def pre_remove(self, daemon: DaemonDescription) -> None: + """ + Called before grafana daemon is removed. + """ + if daemon.hostname is not None: + # delete cert/key entires for this grafana daemon + cert_path = f'{daemon.hostname}/grafana_crt' + key_path = f'{daemon.hostname}/grafana_key' + self.mgr.set_store(cert_path, None) + self.mgr.set_store(key_path, None) + + def ok_to_stop(self, + daemon_ids: List[str], + force: bool = False, + known: Optional[List[str]] = None) -> HandleCommandResult: + warn, warn_message = self._enough_daemons_to_stop(self.TYPE, daemon_ids, 'Grafana', 1) + if warn and not force: + return HandleCommandResult(-errno.EBUSY, '', warn_message) + return HandleCommandResult(0, warn_message, '') + + +class AlertmanagerService(CephadmService): + TYPE = 'alertmanager' + DEFAULT_SERVICE_PORT = 9093 + + def prepare_create(self, daemon_spec: CephadmDaemonDeploySpec) -> CephadmDaemonDeploySpec: + assert self.TYPE == daemon_spec.daemon_type + daemon_spec.final_config, daemon_spec.deps = self.generate_config(daemon_spec) + return daemon_spec + + def generate_config(self, daemon_spec: CephadmDaemonDeploySpec) -> Tuple[Dict[str, Any], List[str]]: + assert self.TYPE == daemon_spec.daemon_type + deps: List[str] = [] + default_webhook_urls: List[str] = [] + + spec = cast(AlertManagerSpec, self.mgr.spec_store[daemon_spec.service_name].spec) + try: + secure = spec.secure + except AttributeError: + secure = False + user_data = spec.user_data + if 'default_webhook_urls' in user_data and isinstance( + user_data['default_webhook_urls'], list): + default_webhook_urls.extend(user_data['default_webhook_urls']) + + # dashboard(s) + dashboard_urls: List[str] = [] + snmp_gateway_urls: List[str] = [] + mgr_map = self.mgr.get('mgr_map') + port = None + proto = None # http: or https: + url = mgr_map.get('services', {}).get('dashboard', None) + if url: + p_result = urlparse(url.rstrip('/')) + hostname = socket.getfqdn(p_result.hostname) + + try: + ip = ipaddress.ip_address(hostname) + except ValueError: + pass + else: + if ip.version == 6: + hostname = f'[{hostname}]' + + dashboard_urls.append( + f'{p_result.scheme}://{hostname}:{p_result.port}{p_result.path}') + proto = p_result.scheme + port = p_result.port + # scan all mgrs to generate deps and to get standbys too. + # assume that they are all on the same port as the active mgr. + for dd in self.mgr.cache.get_daemons_by_service('mgr'): + # we consider mgr a dep even if the dashboard is disabled + # in order to be consistent with _calc_daemon_deps(). + deps.append(dd.name()) + if not port: + continue + if dd.daemon_id == self.mgr.get_mgr_id(): + continue + assert dd.hostname is not None + addr = self._inventory_get_fqdn(dd.hostname) + dashboard_urls.append(build_url(scheme=proto, host=addr, port=port).rstrip('/')) + + for dd in self.mgr.cache.get_daemons_by_service('snmp-gateway'): + assert dd.hostname is not None + assert dd.ports + addr = dd.ip if dd.ip else self._inventory_get_fqdn(dd.hostname) + deps.append(dd.name()) + + snmp_gateway_urls.append(build_url(scheme='http', host=addr, + port=dd.ports[0], path='/alerts')) + + context = { + 'dashboard_urls': dashboard_urls, + 'default_webhook_urls': default_webhook_urls, + 'snmp_gateway_urls': snmp_gateway_urls, + 'secure': secure, + } + yml = self.mgr.template.render('services/alertmanager/alertmanager.yml.j2', context) + + peers = [] + port = 9094 + for dd in self.mgr.cache.get_daemons_by_service('alertmanager'): + assert dd.hostname is not None + deps.append(dd.name()) + addr = self._inventory_get_fqdn(dd.hostname) + peers.append(build_url(host=addr, port=port).lstrip('/')) + + return { + "files": { + "alertmanager.yml": yml + }, + "peers": peers + }, sorted(deps) + + def get_active_daemon(self, daemon_descrs: List[DaemonDescription]) -> DaemonDescription: + # TODO: if there are multiple daemons, who is the active one? + if daemon_descrs: + return daemon_descrs[0] + # if empty list provided, return empty Daemon Desc + return DaemonDescription() + + def config_dashboard(self, daemon_descrs: List[DaemonDescription]) -> None: + dd = self.get_active_daemon(daemon_descrs) + assert dd.hostname is not None + addr = dd.ip if dd.ip else self._inventory_get_fqdn(dd.hostname) + port = dd.ports[0] if dd.ports else self.DEFAULT_SERVICE_PORT + service_url = build_url(scheme='http', host=addr, port=port) + self._set_service_url_on_dashboard( + 'AlertManager', + 'dashboard get-alertmanager-api-host', + 'dashboard set-alertmanager-api-host', + service_url + ) + + def ok_to_stop(self, + daemon_ids: List[str], + force: bool = False, + known: Optional[List[str]] = None) -> HandleCommandResult: + warn, warn_message = self._enough_daemons_to_stop(self.TYPE, daemon_ids, 'Alertmanager', 1) + if warn and not force: + return HandleCommandResult(-errno.EBUSY, '', warn_message) + return HandleCommandResult(0, warn_message, '') + + +class PrometheusService(CephadmService): + TYPE = 'prometheus' + DEFAULT_SERVICE_PORT = 9095 + DEFAULT_MGR_PROMETHEUS_PORT = 9283 + + def config(self, spec: ServiceSpec) -> None: + # make sure module is enabled + mgr_map = self.mgr.get('mgr_map') + if 'prometheus' not in mgr_map.get('services', {}): + self.mgr.check_mon_command({ + 'prefix': 'mgr module enable', + 'module': 'prometheus' + }) + # we shouldn't get here (mon will tell the mgr to respawn), but no + # harm done if we do. + + def prepare_create( + self, + daemon_spec: CephadmDaemonDeploySpec, + ) -> CephadmDaemonDeploySpec: + assert self.TYPE == daemon_spec.daemon_type + daemon_spec.final_config, daemon_spec.deps = self.generate_config(daemon_spec) + return daemon_spec + + def generate_config( + self, + daemon_spec: CephadmDaemonDeploySpec, + ) -> Tuple[Dict[str, Any], List[str]]: + assert self.TYPE == daemon_spec.daemon_type + deps = [] # type: List[str] + + prom_spec = cast(PrometheusSpec, self.mgr.spec_store[daemon_spec.service_name].spec) + + try: + retention_time = prom_spec.retention_time if prom_spec.retention_time else '15d' + except AttributeError: + retention_time = '15d' + + # scrape mgrs + mgr_scrape_list = [] + mgr_map = self.mgr.get('mgr_map') + port = cast(int, self.mgr.get_module_option_ex( + 'prometheus', 'server_port', self.DEFAULT_MGR_PROMETHEUS_PORT)) + deps.append(str(port)) + t = mgr_map.get('services', {}).get('prometheus', None) + if t: + p_result = urlparse(t) + # urlparse .hostname removes '[]' from the hostname in case + # of ipv6 addresses so if this is the case then we just + # append the brackets when building the final scrape endpoint + if '[' in p_result.netloc and ']' in p_result.netloc: + mgr_scrape_list.append(f"[{p_result.hostname}]:{port}") + else: + mgr_scrape_list.append(f"{p_result.hostname}:{port}") + # scan all mgrs to generate deps and to get standbys too. + # assume that they are all on the same port as the active mgr. + for dd in self.mgr.cache.get_daemons_by_service('mgr'): + # we consider the mgr a dep even if the prometheus module is + # disabled in order to be consistent with _calc_daemon_deps(). + deps.append(dd.name()) + if not port: + continue + if dd.daemon_id == self.mgr.get_mgr_id(): + continue + assert dd.hostname is not None + addr = self._inventory_get_fqdn(dd.hostname) + mgr_scrape_list.append(build_url(host=addr, port=port).lstrip('/')) + + # scrape node exporters + nodes = [] + for dd in self.mgr.cache.get_daemons_by_service('node-exporter'): + assert dd.hostname is not None + deps.append(dd.name()) + addr = dd.ip if dd.ip else self._inventory_get_fqdn(dd.hostname) + port = dd.ports[0] if dd.ports else 9100 + nodes.append({ + 'hostname': dd.hostname, + 'url': build_url(host=addr, port=port).lstrip('/') + }) + + # scrape alert managers + alertmgr_targets = [] + for dd in self.mgr.cache.get_daemons_by_service('alertmanager'): + assert dd.hostname is not None + deps.append(dd.name()) + addr = dd.ip if dd.ip else self._inventory_get_fqdn(dd.hostname) + port = dd.ports[0] if dd.ports else 9093 + alertmgr_targets.append("'{}'".format(build_url(host=addr, port=port).lstrip('/'))) + + # scrape haproxies + haproxy_targets = [] + for dd in self.mgr.cache.get_daemons_by_type('ingress'): + if dd.service_name() in self.mgr.spec_store: + spec = cast(IngressSpec, self.mgr.spec_store[dd.service_name()].spec) + assert dd.hostname is not None + deps.append(dd.name()) + if dd.daemon_type == 'haproxy': + addr = self._inventory_get_fqdn(dd.hostname) + haproxy_targets.append({ + "url": f"'{build_url(host=addr, port=spec.monitor_port).lstrip('/')}'", + "service": dd.service_name(), + }) + + # generate the prometheus configuration + context = { + 'alertmgr_targets': alertmgr_targets, + 'mgr_scrape_list': mgr_scrape_list, + 'haproxy_targets': haproxy_targets, + 'nodes': nodes, + } + r: Dict[str, Any] = { + 'files': { + 'prometheus.yml': + self.mgr.template.render( + 'services/prometheus/prometheus.yml.j2', context) + }, + 'retention_time': retention_time + } + + # include alerts, if present in the container + if os.path.exists(self.mgr.prometheus_alerts_path): + with open(self.mgr.prometheus_alerts_path, 'r', encoding='utf-8') as f: + alerts = f.read() + r['files']['/etc/prometheus/alerting/ceph_alerts.yml'] = alerts + + # Include custom alerts if present in key value store. This enables the + # users to add custom alerts. Write the file in any case, so that if the + # content of the key value store changed, that file is overwritten + # (emptied in case they value has been removed from the key value + # store). This prevents the necessity to adapt `cephadm` binary to + # remove the file. + # + # Don't use the template engine for it as + # + # 1. the alerts are always static and + # 2. they are a template themselves for the Go template engine, which + # use curly braces and escaping that is cumbersome and unnecessary + # for the user. + # + r['files']['/etc/prometheus/alerting/custom_alerts.yml'] = \ + self.mgr.get_store('services/prometheus/alerting/custom_alerts.yml', '') + + return r, sorted(deps) + + def get_active_daemon(self, daemon_descrs: List[DaemonDescription]) -> DaemonDescription: + # TODO: if there are multiple daemons, who is the active one? + if daemon_descrs: + return daemon_descrs[0] + # if empty list provided, return empty Daemon Desc + return DaemonDescription() + + def config_dashboard(self, daemon_descrs: List[DaemonDescription]) -> None: + dd = self.get_active_daemon(daemon_descrs) + assert dd.hostname is not None + addr = dd.ip if dd.ip else self._inventory_get_fqdn(dd.hostname) + port = dd.ports[0] if dd.ports else self.DEFAULT_SERVICE_PORT + service_url = build_url(scheme='http', host=addr, port=port) + self._set_service_url_on_dashboard( + 'Prometheus', + 'dashboard get-prometheus-api-host', + 'dashboard set-prometheus-api-host', + service_url + ) + + def ok_to_stop(self, + daemon_ids: List[str], + force: bool = False, + known: Optional[List[str]] = None) -> HandleCommandResult: + warn, warn_message = self._enough_daemons_to_stop(self.TYPE, daemon_ids, 'Prometheus', 1) + if warn and not force: + return HandleCommandResult(-errno.EBUSY, '', warn_message) + return HandleCommandResult(0, warn_message, '') + + +class NodeExporterService(CephadmService): + TYPE = 'node-exporter' + + def prepare_create(self, daemon_spec: CephadmDaemonDeploySpec) -> CephadmDaemonDeploySpec: + assert self.TYPE == daemon_spec.daemon_type + daemon_spec.final_config, daemon_spec.deps = self.generate_config(daemon_spec) + return daemon_spec + + def generate_config(self, daemon_spec: CephadmDaemonDeploySpec) -> Tuple[Dict[str, Any], List[str]]: + assert self.TYPE == daemon_spec.daemon_type + return {}, [] + + def ok_to_stop(self, + daemon_ids: List[str], + force: bool = False, + known: Optional[List[str]] = None) -> HandleCommandResult: + # since node exporter runs on each host and cannot compromise data, no extra checks required + names = [f'{self.TYPE}.{d_id}' for d_id in daemon_ids] + out = f'It is presumed safe to stop {names}' + return HandleCommandResult(0, out, '') + + +class SNMPGatewayService(CephadmService): + TYPE = 'snmp-gateway' + + def prepare_create(self, daemon_spec: CephadmDaemonDeploySpec) -> CephadmDaemonDeploySpec: + assert self.TYPE == daemon_spec.daemon_type + daemon_spec.final_config, daemon_spec.deps = self.generate_config(daemon_spec) + return daemon_spec + + def generate_config(self, daemon_spec: CephadmDaemonDeploySpec) -> Tuple[Dict[str, Any], List[str]]: + assert self.TYPE == daemon_spec.daemon_type + deps: List[str] = [] + + spec = cast(SNMPGatewaySpec, self.mgr.spec_store[daemon_spec.service_name].spec) + config = { + "destination": spec.snmp_destination, + "snmp_version": spec.snmp_version, + } + if spec.snmp_version == 'V2c': + community = spec.credentials.get('snmp_community', None) + assert community is not None + + config.update({ + "snmp_community": community + }) + else: + # SNMP v3 settings can be either authNoPriv or authPriv + auth_protocol = 'SHA' if not spec.auth_protocol else spec.auth_protocol + + auth_username = spec.credentials.get('snmp_v3_auth_username', None) + auth_password = spec.credentials.get('snmp_v3_auth_password', None) + assert auth_username is not None + assert auth_password is not None + assert spec.engine_id is not None + + config.update({ + "snmp_v3_auth_protocol": auth_protocol, + "snmp_v3_auth_username": auth_username, + "snmp_v3_auth_password": auth_password, + "snmp_v3_engine_id": spec.engine_id, + }) + # authPriv adds encryption + if spec.privacy_protocol: + priv_password = spec.credentials.get('snmp_v3_priv_password', None) + assert priv_password is not None + + config.update({ + "snmp_v3_priv_protocol": spec.privacy_protocol, + "snmp_v3_priv_password": priv_password, + }) + + logger.debug( + f"Generated configuration for '{self.TYPE}' service. Dependencies={deps}") + + return config, sorted(deps) diff --git a/src/pybind/mgr/cephadm/services/nfs.py b/src/pybind/mgr/cephadm/services/nfs.py new file mode 100644 index 000000000..ee53283bd --- /dev/null +++ b/src/pybind/mgr/cephadm/services/nfs.py @@ -0,0 +1,290 @@ +import errno +import logging +import os +import subprocess +import tempfile +from typing import Dict, Tuple, Any, List, cast, Optional + +from mgr_module import HandleCommandResult +from mgr_module import NFS_POOL_NAME as POOL_NAME + +from ceph.deployment.service_spec import ServiceSpec, NFSServiceSpec + +from orchestrator import DaemonDescription + +from cephadm.services.cephadmservice import AuthEntity, CephadmDaemonDeploySpec, CephService + +logger = logging.getLogger(__name__) + + +class NFSService(CephService): + TYPE = 'nfs' + + def ranked(self) -> bool: + return True + + def fence(self, daemon_id: str) -> None: + logger.info(f'Fencing old nfs.{daemon_id}') + ret, out, err = self.mgr.mon_command({ + 'prefix': 'auth rm', + 'entity': f'client.nfs.{daemon_id}', + }) + + # TODO: block/fence this entity (in case it is still running somewhere) + + def fence_old_ranks(self, + spec: ServiceSpec, + rank_map: Dict[int, Dict[int, Optional[str]]], + num_ranks: int) -> None: + for rank, m in list(rank_map.items()): + if rank >= num_ranks: + for daemon_id in m.values(): + if daemon_id is not None: + self.fence(daemon_id) + del rank_map[rank] + nodeid = f'{spec.service_name()}.{rank}' + self.mgr.log.info(f'Removing {nodeid} from the ganesha grace table') + self.run_grace_tool(cast(NFSServiceSpec, spec), 'remove', nodeid) + self.mgr.spec_store.save_rank_map(spec.service_name(), rank_map) + else: + max_gen = max(m.keys()) + for gen, daemon_id in list(m.items()): + if gen < max_gen: + if daemon_id is not None: + self.fence(daemon_id) + del rank_map[rank][gen] + self.mgr.spec_store.save_rank_map(spec.service_name(), rank_map) + + def config(self, spec: NFSServiceSpec) -> None: # type: ignore + from nfs.cluster import create_ganesha_pool + + assert self.TYPE == spec.service_type + create_ganesha_pool(self.mgr) + + def prepare_create(self, daemon_spec: CephadmDaemonDeploySpec) -> CephadmDaemonDeploySpec: + assert self.TYPE == daemon_spec.daemon_type + daemon_spec.final_config, daemon_spec.deps = self.generate_config(daemon_spec) + return daemon_spec + + def generate_config(self, daemon_spec: CephadmDaemonDeploySpec) -> Tuple[Dict[str, Any], List[str]]: + assert self.TYPE == daemon_spec.daemon_type + + daemon_type = daemon_spec.daemon_type + daemon_id = daemon_spec.daemon_id + host = daemon_spec.host + spec = cast(NFSServiceSpec, self.mgr.spec_store[daemon_spec.service_name].spec) + + deps: List[str] = [] + + nodeid = f'{daemon_spec.service_name}.{daemon_spec.rank}' + + # create the RADOS recovery pool keyring + rados_user = f'{daemon_type}.{daemon_id}' + rados_keyring = self.create_keyring(daemon_spec) + + # ensure rank is known to ganesha + self.mgr.log.info(f'Ensuring {nodeid} is in the ganesha grace table') + self.run_grace_tool(spec, 'add', nodeid) + + # create the rados config object + self.create_rados_config_obj(spec) + + # create the RGW keyring + rgw_user = f'{rados_user}-rgw' + rgw_keyring = self.create_rgw_keyring(daemon_spec) + + # generate the ganesha config + def get_ganesha_conf() -> str: + context = { + "user": rados_user, + "nodeid": nodeid, + "pool": POOL_NAME, + "namespace": spec.service_id, + "rgw_user": rgw_user, + "url": f'rados://{POOL_NAME}/{spec.service_id}/{spec.rados_config_name()}', + # fall back to default NFS port if not present in daemon_spec + "port": daemon_spec.ports[0] if daemon_spec.ports else 2049, + "bind_addr": daemon_spec.ip if daemon_spec.ip else '', + } + return self.mgr.template.render('services/nfs/ganesha.conf.j2', context) + + # generate the cephadm config json + def get_cephadm_config() -> Dict[str, Any]: + config: Dict[str, Any] = {} + config['pool'] = POOL_NAME + config['namespace'] = spec.service_id + config['userid'] = rados_user + config['extra_args'] = ['-N', 'NIV_EVENT'] + config['files'] = { + 'ganesha.conf': get_ganesha_conf(), + } + config.update( + self.get_config_and_keyring( + daemon_type, daemon_id, + keyring=rados_keyring, + host=host + ) + ) + config['rgw'] = { + 'cluster': 'ceph', + 'user': rgw_user, + 'keyring': rgw_keyring, + } + logger.debug('Generated cephadm config-json: %s' % config) + return config + + return get_cephadm_config(), deps + + def create_rados_config_obj(self, + spec: NFSServiceSpec, + clobber: bool = False) -> None: + objname = spec.rados_config_name() + cmd = [ + 'rados', + '-n', f"mgr.{self.mgr.get_mgr_id()}", + '-k', str(self.mgr.get_ceph_option('keyring')), + '-p', POOL_NAME, + '--namespace', cast(str, spec.service_id), + ] + result = subprocess.run( + cmd + ['get', objname, '-'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + timeout=10) + if not result.returncode and not clobber: + logger.info('Rados config object exists: %s' % objname) + else: + logger.info('Creating rados config object: %s' % objname) + result = subprocess.run( + cmd + ['put', objname, '-'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + timeout=10) + if result.returncode: + self.mgr.log.warning( + f'Unable to create rados config object {objname}: {result.stderr.decode("utf-8")}' + ) + raise RuntimeError(result.stderr.decode("utf-8")) + + def create_keyring(self, daemon_spec: CephadmDaemonDeploySpec) -> str: + daemon_id = daemon_spec.daemon_id + spec = cast(NFSServiceSpec, self.mgr.spec_store[daemon_spec.service_name].spec) + entity: AuthEntity = self.get_auth_entity(daemon_id) + + osd_caps = 'allow rw pool=%s namespace=%s' % (POOL_NAME, spec.service_id) + + logger.info('Creating key for %s' % entity) + keyring = self.get_keyring_with_caps(entity, + ['mon', 'allow r', + 'osd', osd_caps]) + + return keyring + + def create_rgw_keyring(self, daemon_spec: CephadmDaemonDeploySpec) -> str: + daemon_id = daemon_spec.daemon_id + entity: AuthEntity = self.get_auth_entity(f'{daemon_id}-rgw') + + logger.info('Creating key for %s' % entity) + keyring = self.get_keyring_with_caps(entity, + ['mon', 'allow r', + 'osd', 'allow rwx tag rgw *=*']) + + return keyring + + def run_grace_tool(self, + spec: NFSServiceSpec, + action: str, + nodeid: str) -> None: + # write a temp keyring and referencing config file. this is a kludge + # because the ganesha-grace-tool can only authenticate as a client (and + # not a mgr). Also, it doesn't allow you to pass a keyring location via + # the command line, nor does it parse the CEPH_ARGS env var. + tmp_id = f'mgr.nfs.grace.{spec.service_name()}' + entity = AuthEntity(f'client.{tmp_id}') + keyring = self.get_keyring_with_caps( + entity, + ['mon', 'allow r', 'osd', f'allow rwx pool {POOL_NAME}'] + ) + tmp_keyring = tempfile.NamedTemporaryFile(mode='w', prefix='mgr-grace-keyring') + os.fchmod(tmp_keyring.fileno(), 0o600) + tmp_keyring.write(keyring) + tmp_keyring.flush() + tmp_conf = tempfile.NamedTemporaryFile(mode='w', prefix='mgr-grace-conf') + tmp_conf.write(self.mgr.get_minimal_ceph_conf()) + tmp_conf.write(f'\tkeyring = {tmp_keyring.name}\n') + tmp_conf.flush() + try: + cmd: List[str] = [ + 'ganesha-rados-grace', + '--cephconf', tmp_conf.name, + '--userid', tmp_id, + '--pool', POOL_NAME, + '--ns', cast(str, spec.service_id), + action, nodeid, + ] + self.mgr.log.debug(cmd) + result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + timeout=10) + if result.returncode: + self.mgr.log.warning( + f'ganesha-rados-grace tool failed: {result.stderr.decode("utf-8")}' + ) + raise RuntimeError(f'grace tool failed: {result.stderr.decode("utf-8")}') + + finally: + self.mgr.check_mon_command({ + 'prefix': 'auth rm', + 'entity': entity, + }) + + def remove_rgw_keyring(self, daemon: DaemonDescription) -> None: + assert daemon.daemon_id is not None + daemon_id: str = daemon.daemon_id + entity: AuthEntity = self.get_auth_entity(f'{daemon_id}-rgw') + + logger.info(f'Removing key for {entity}') + self.mgr.check_mon_command({ + 'prefix': 'auth rm', + 'entity': entity, + }) + + def post_remove(self, daemon: DaemonDescription, is_failed_deploy: bool) -> None: + super().post_remove(daemon, is_failed_deploy=is_failed_deploy) + self.remove_rgw_keyring(daemon) + + def ok_to_stop(self, + daemon_ids: List[str], + force: bool = False, + known: Optional[List[str]] = None) -> HandleCommandResult: + # if only 1 nfs, alert user (this is not passable with --force) + warn, warn_message = self._enough_daemons_to_stop(self.TYPE, daemon_ids, 'NFS', 1, True) + if warn: + return HandleCommandResult(-errno.EBUSY, '', warn_message) + + # if reached here, there is > 1 nfs daemon. + if force: + return HandleCommandResult(0, warn_message, '') + + # if reached here, > 1 nfs daemon and no force flag. + # Provide warning + warn_message = "WARNING: Removing NFS daemons can cause clients to lose connectivity. " + return HandleCommandResult(-errno.EBUSY, '', warn_message) + + def purge(self, service_name: str) -> None: + if service_name not in self.mgr.spec_store: + return + spec = cast(NFSServiceSpec, self.mgr.spec_store[service_name].spec) + + logger.info(f'Removing grace file for {service_name}') + cmd = [ + 'rados', + '-n', f"mgr.{self.mgr.get_mgr_id()}", + '-k', str(self.mgr.get_ceph_option('keyring')), + '-p', POOL_NAME, + '--namespace', cast(str, spec.service_id), + 'rm', 'grace', + ] + subprocess.run( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=10 + ) diff --git a/src/pybind/mgr/cephadm/services/osd.py b/src/pybind/mgr/cephadm/services/osd.py new file mode 100644 index 000000000..5899ba49a --- /dev/null +++ b/src/pybind/mgr/cephadm/services/osd.py @@ -0,0 +1,956 @@ +import json +import logging +from threading import Lock +from typing import List, Dict, Any, Set, Tuple, cast, Optional, TYPE_CHECKING + +from ceph.deployment import translate +from ceph.deployment.drive_group import DriveGroupSpec +from ceph.deployment.drive_selection import DriveSelection +from ceph.deployment.inventory import Device +from ceph.utils import datetime_to_str, str_to_datetime + +from datetime import datetime +import orchestrator +from cephadm.serve import CephadmServe +from cephadm.utils import forall_hosts +from ceph.utils import datetime_now +from orchestrator import OrchestratorError, DaemonDescription +from mgr_module import MonCommandFailed + +from cephadm.services.cephadmservice import CephadmDaemonDeploySpec, CephService + +if TYPE_CHECKING: + from cephadm.module import CephadmOrchestrator + +logger = logging.getLogger(__name__) + + +class OSDService(CephService): + TYPE = 'osd' + + def create_from_spec(self, drive_group: DriveGroupSpec) -> str: + logger.debug(f"Processing DriveGroup {drive_group}") + osd_id_claims = OsdIdClaims(self.mgr) + if osd_id_claims.get(): + logger.info( + f"Found osd claims for drivegroup {drive_group.service_id} -> {osd_id_claims.get()}") + + @forall_hosts + def create_from_spec_one(host: str, drive_selection: DriveSelection) -> Optional[str]: + # skip this host if there has been no change in inventory + if not self.mgr.cache.osdspec_needs_apply(host, drive_group): + self.mgr.log.debug("skipping apply of %s on %s (no change)" % ( + host, drive_group)) + return None + # skip this host if we cannot schedule here + if self.mgr.inventory.has_label(host, '_no_schedule'): + return None + + osd_id_claims_for_host = osd_id_claims.filtered_by_host(host) + + cmds: List[str] = self.driveselection_to_ceph_volume(drive_selection, + osd_id_claims_for_host) + if not cmds: + logger.debug("No data_devices, skipping DriveGroup: {}".format( + drive_group.service_id)) + return None + + logger.debug('Applying service osd.%s on host %s...' % ( + drive_group.service_id, host + )) + start_ts = datetime_now() + env_vars: List[str] = [f"CEPH_VOLUME_OSDSPEC_AFFINITY={drive_group.service_id}"] + ret_msg = self.create_single_host( + drive_group, host, cmds, + replace_osd_ids=osd_id_claims_for_host, env_vars=env_vars + ) + self.mgr.cache.update_osdspec_last_applied( + host, drive_group.service_name(), start_ts + ) + self.mgr.cache.save_host(host) + return ret_msg + + ret = create_from_spec_one(self.prepare_drivegroup(drive_group)) + return ", ".join(filter(None, ret)) + + def create_single_host(self, + drive_group: DriveGroupSpec, + host: str, cmds: List[str], replace_osd_ids: List[str], + env_vars: Optional[List[str]] = None) -> str: + for cmd in cmds: + out, err, code = self._run_ceph_volume_command(host, cmd, env_vars=env_vars) + if code == 1 and ', it is already prepared' in '\n'.join(err): + # HACK: when we create against an existing LV, ceph-volume + # returns an error and the above message. To make this + # command idempotent, tolerate this "error" and continue. + logger.debug('the device was already prepared; continuing') + code = 0 + if code: + raise RuntimeError( + 'cephadm exited with an error code: %d, stderr:%s' % ( + code, '\n'.join(err))) + return self.deploy_osd_daemons_for_existing_osds(host, drive_group.service_name(), + replace_osd_ids) + + def deploy_osd_daemons_for_existing_osds(self, host: str, service_name: str, + replace_osd_ids: Optional[List[str]] = None) -> str: + + if replace_osd_ids is None: + replace_osd_ids = OsdIdClaims(self.mgr).filtered_by_host(host) + assert replace_osd_ids is not None + + # check result: lvm + osds_elems: dict = CephadmServe(self.mgr)._run_cephadm_json( + host, 'osd', 'ceph-volume', + [ + '--', + 'lvm', 'list', + '--format', 'json', + ]) + before_osd_uuid_map = self.mgr.get_osd_uuid_map(only_up=True) + fsid = self.mgr._cluster_fsid + osd_uuid_map = self.mgr.get_osd_uuid_map() + created = [] + for osd_id, osds in osds_elems.items(): + for osd in osds: + if osd['type'] == 'db': + continue + if osd['tags']['ceph.cluster_fsid'] != fsid: + logger.debug('mismatched fsid, skipping %s' % osd) + continue + if osd_id in before_osd_uuid_map and osd_id not in replace_osd_ids: + # if it exists but is part of the replacement operation, don't skip + continue + if self.mgr.cache.has_daemon(f'osd.{osd_id}', host): + # cephadm daemon instance already exists + logger.debug(f'osd id {osd_id} daemon already exists') + continue + if osd_id not in osd_uuid_map: + logger.debug('osd id {} does not exist in cluster'.format(osd_id)) + continue + if osd_uuid_map.get(osd_id) != osd['tags']['ceph.osd_fsid']: + logger.debug('mismatched osd uuid (cluster has %s, osd ' + 'has %s)' % ( + osd_uuid_map.get(osd_id), + osd['tags']['ceph.osd_fsid'])) + continue + + created.append(osd_id) + daemon_spec: CephadmDaemonDeploySpec = CephadmDaemonDeploySpec( + service_name=service_name, + daemon_id=str(osd_id), + host=host, + daemon_type='osd', + ) + daemon_spec.final_config, daemon_spec.deps = self.generate_config(daemon_spec) + CephadmServe(self.mgr)._create_daemon( + daemon_spec, + osd_uuid_map=osd_uuid_map) + + # check result: raw + raw_elems: dict = CephadmServe(self.mgr)._run_cephadm_json( + host, 'osd', 'ceph-volume', + [ + '--', + 'raw', 'list', + '--format', 'json', + ]) + for osd_uuid, osd in raw_elems.items(): + if osd.get('ceph_fsid') != fsid: + continue + osd_id = str(osd.get('osd_id', '-1')) + if osd_id in before_osd_uuid_map and osd_id not in replace_osd_ids: + # if it exists but is part of the replacement operation, don't skip + continue + if self.mgr.cache.has_daemon(f'osd.{osd_id}', host): + # cephadm daemon instance already exists + logger.debug(f'osd id {osd_id} daemon already exists') + continue + if osd_id not in osd_uuid_map: + logger.debug('osd id {} does not exist in cluster'.format(osd_id)) + continue + if osd_uuid_map.get(osd_id) != osd_uuid: + logger.debug('mismatched osd uuid (cluster has %s, osd ' + 'has %s)' % (osd_uuid_map.get(osd_id), osd_uuid)) + continue + if osd_id in created: + continue + + created.append(osd_id) + daemon_spec = CephadmDaemonDeploySpec( + service_name=service_name, + daemon_id=osd_id, + host=host, + daemon_type='osd', + ) + daemon_spec.final_config, daemon_spec.deps = self.generate_config(daemon_spec) + CephadmServe(self.mgr)._create_daemon( + daemon_spec, + osd_uuid_map=osd_uuid_map) + + if created: + self.mgr.cache.invalidate_host_devices(host) + self.mgr.cache.invalidate_autotune(host) + return "Created osd(s) %s on host '%s'" % (','.join(created), host) + else: + return "Created no osd(s) on host %s; already created?" % host + + def prepare_drivegroup(self, drive_group: DriveGroupSpec) -> List[Tuple[str, DriveSelection]]: + # 1) use fn_filter to determine matching_hosts + matching_hosts = drive_group.placement.filter_matching_hostspecs( + self.mgr._schedulable_hosts()) + # 2) Map the inventory to the InventoryHost object + host_ds_map = [] + + # set osd_id_claims + + def _find_inv_for_host(hostname: str, inventory_dict: dict) -> List[Device]: + # This is stupid and needs to be loaded with the host + for _host, _inventory in inventory_dict.items(): + if _host == hostname: + return _inventory + raise OrchestratorError("No inventory found for host: {}".format(hostname)) + + # 3) iterate over matching_host and call DriveSelection + logger.debug(f"Checking matching hosts -> {matching_hosts}") + for host in matching_hosts: + inventory_for_host = _find_inv_for_host(host, self.mgr.cache.devices) + logger.debug(f"Found inventory for host {inventory_for_host}") + + # List of Daemons on that host + dd_for_spec = self.mgr.cache.get_daemons_by_service(drive_group.service_name()) + dd_for_spec_and_host = [dd for dd in dd_for_spec if dd.hostname == host] + + drive_selection = DriveSelection(drive_group, inventory_for_host, + existing_daemons=len(dd_for_spec_and_host)) + logger.debug(f"Found drive selection {drive_selection}") + if drive_group.method and drive_group.method == 'raw': + # ceph-volume can currently only handle a 1:1 mapping + # of data/db/wal devices for raw mode osds. If db/wal devices + # are defined and the number does not match the number of data + # devices, we need to bail out + if drive_selection.data_devices() and drive_selection.db_devices(): + if len(drive_selection.data_devices()) != len(drive_selection.db_devices()): + raise OrchestratorError('Raw mode only supports a 1:1 ratio of data to db devices. Found ' + f'{len(drive_selection.data_devices())} potential data device(s) and ' + f'{len(drive_selection.db_devices())} potential db device(s) on host {host}') + if drive_selection.data_devices() and drive_selection.wal_devices(): + if len(drive_selection.data_devices()) != len(drive_selection.wal_devices()): + raise OrchestratorError('Raw mode only supports a 1:1 ratio of data to wal devices. Found ' + f'{len(drive_selection.data_devices())} potential data device(s) and ' + f'{len(drive_selection.wal_devices())} potential wal device(s) on host {host}') + host_ds_map.append((host, drive_selection)) + return host_ds_map + + @staticmethod + def driveselection_to_ceph_volume(drive_selection: DriveSelection, + osd_id_claims: Optional[List[str]] = None, + preview: bool = False) -> List[str]: + logger.debug(f"Translating DriveGroup <{drive_selection.spec}> to ceph-volume command") + cmds: List[str] = translate.to_ceph_volume(drive_selection, + osd_id_claims, preview=preview).run() + logger.debug(f"Resulting ceph-volume cmds: {cmds}") + return cmds + + def get_previews(self, host: str) -> List[Dict[str, Any]]: + # Find OSDSpecs that match host. + osdspecs = self.resolve_osdspecs_for_host(host) + return self.generate_previews(osdspecs, host) + + def generate_previews(self, osdspecs: List[DriveGroupSpec], for_host: str) -> List[Dict[str, Any]]: + """ + + The return should look like this: + + [ + {'data': {}, + 'osdspec': , + 'host': , + 'notes': + }, + + {'data': ..., + 'osdspec': .., + 'host': ..., + 'notes': ... + } + ] + + Note: One host can have multiple previews based on its assigned OSDSpecs. + """ + self.mgr.log.debug(f"Generating OSDSpec previews for {osdspecs}") + ret_all: List[Dict[str, Any]] = [] + if not osdspecs: + return ret_all + for osdspec in osdspecs: + + # populate osd_id_claims + osd_id_claims = OsdIdClaims(self.mgr) + + # prepare driveselection + for host, ds in self.prepare_drivegroup(osdspec): + if host != for_host: + continue + + # driveselection for host + cmds: List[str] = self.driveselection_to_ceph_volume(ds, + osd_id_claims.filtered_by_host(host), + preview=True) + if not cmds: + logger.debug("No data_devices, skipping DriveGroup: {}".format( + osdspec.service_name())) + continue + + # get preview data from ceph-volume + for cmd in cmds: + out, err, code = self._run_ceph_volume_command(host, cmd) + if out: + try: + concat_out: Dict[str, Any] = json.loads(' '.join(out)) + except ValueError: + logger.exception('Cannot decode JSON: \'%s\'' % ' '.join(out)) + concat_out = {} + notes = [] + if osdspec.data_devices is not None and osdspec.data_devices.limit and len(concat_out) < osdspec.data_devices.limit: + found = len(concat_out) + limit = osdspec.data_devices.limit + notes.append( + f'NOTE: Did not find enough disks matching filter on host {host} to reach data device limit (Found: {found} | Limit: {limit})') + ret_all.append({'data': concat_out, + 'osdspec': osdspec.service_id, + 'host': host, + 'notes': notes}) + return ret_all + + def resolve_hosts_for_osdspecs(self, + specs: Optional[List[DriveGroupSpec]] = None + ) -> List[str]: + osdspecs = [] + if specs: + osdspecs = [cast(DriveGroupSpec, spec) for spec in specs] + if not osdspecs: + self.mgr.log.debug("No OSDSpecs found") + return [] + return sum([spec.placement.filter_matching_hostspecs(self.mgr._schedulable_hosts()) for spec in osdspecs], []) + + def resolve_osdspecs_for_host(self, host: str, + specs: Optional[List[DriveGroupSpec]] = None) -> List[DriveGroupSpec]: + matching_specs = [] + self.mgr.log.debug(f"Finding OSDSpecs for host: <{host}>") + if not specs: + specs = [cast(DriveGroupSpec, spec) for (sn, spec) in self.mgr.spec_store.spec_preview.items() + if spec.service_type == 'osd'] + for spec in specs: + if host in spec.placement.filter_matching_hostspecs(self.mgr._schedulable_hosts()): + self.mgr.log.debug(f"Found OSDSpecs for host: <{host}> -> <{spec}>") + matching_specs.append(spec) + return matching_specs + + def _run_ceph_volume_command(self, host: str, + cmd: str, env_vars: Optional[List[str]] = None + ) -> Tuple[List[str], List[str], int]: + self.mgr.inventory.assert_host(host) + + # get bootstrap key + ret, keyring, err = self.mgr.check_mon_command({ + 'prefix': 'auth get', + 'entity': 'client.bootstrap-osd', + }) + + j = json.dumps({ + 'config': self.mgr.get_minimal_ceph_conf(), + 'keyring': keyring, + }) + + split_cmd = cmd.split(' ') + _cmd = ['--config-json', '-', '--'] + _cmd.extend(split_cmd) + out, err, code = CephadmServe(self.mgr)._run_cephadm( + host, 'osd', 'ceph-volume', + _cmd, + env_vars=env_vars, + stdin=j, + error_ok=True) + return out, err, code + + def post_remove(self, daemon: DaemonDescription, is_failed_deploy: bool) -> None: + # Do not remove the osd.N keyring, if we failed to deploy the OSD, because + # we cannot recover from it. The OSD keys are created by ceph-volume and not by + # us. + if not is_failed_deploy: + super().post_remove(daemon, is_failed_deploy=is_failed_deploy) + + +class OsdIdClaims(object): + """ + Retrieve and provide osd ids that can be reused in the cluster + """ + + def __init__(self, mgr: "CephadmOrchestrator") -> None: + self.mgr: "CephadmOrchestrator" = mgr + self.osd_host_map: Dict[str, List[str]] = dict() + self.refresh() + + def refresh(self) -> None: + try: + ret, out, err = self.mgr.check_mon_command({ + 'prefix': 'osd tree', + 'states': ['destroyed'], + 'format': 'json' + }) + except MonCommandFailed as e: + logger.exception('osd tree failed') + raise OrchestratorError(str(e)) + try: + tree = json.loads(out) + except ValueError: + logger.exception(f'Cannot decode JSON: \'{out}\'') + return + + nodes = tree.get('nodes', {}) + for node in nodes: + if node.get('type') == 'host': + self.osd_host_map.update( + {node.get('name'): [str(_id) for _id in node.get('children', list())]} + ) + if self.osd_host_map: + self.mgr.log.info(f"Found osd claims -> {self.osd_host_map}") + + def get(self) -> Dict[str, List[str]]: + return self.osd_host_map + + def filtered_by_host(self, host: str) -> List[str]: + """ + Return the list of osd ids that can be reused in a host + + OSD id claims in CRUSH map are linked to the bare name of + the hostname. In case of FQDN hostnames the host is searched by the + bare name + """ + return self.osd_host_map.get(host.split(".")[0], []) + + +class RemoveUtil(object): + def __init__(self, mgr: "CephadmOrchestrator") -> None: + self.mgr: "CephadmOrchestrator" = mgr + + def get_osds_in_cluster(self) -> List[str]: + osd_map = self.mgr.get_osdmap() + return [str(x.get('osd')) for x in osd_map.dump().get('osds', [])] + + def osd_df(self) -> dict: + base_cmd = 'osd df' + ret, out, err = self.mgr.mon_command({ + 'prefix': base_cmd, + 'format': 'json' + }) + try: + return json.loads(out) + except ValueError: + logger.exception(f'Cannot decode JSON: \'{out}\'') + return {} + + def get_pg_count(self, osd_id: int, osd_df: Optional[dict] = None) -> int: + if not osd_df: + osd_df = self.osd_df() + osd_nodes = osd_df.get('nodes', []) + for osd_node in osd_nodes: + if osd_node.get('id') == int(osd_id): + return osd_node.get('pgs', -1) + return -1 + + def find_osd_stop_threshold(self, osds: List["OSD"]) -> Optional[List["OSD"]]: + """ + Cut osd_id list in half until it's ok-to-stop + + :param osds: list of osd_ids + :return: list of ods_ids that can be stopped at once + """ + if not osds: + return [] + while not self.ok_to_stop(osds): + if len(osds) <= 1: + # can't even stop one OSD, aborting + self.mgr.log.debug( + "Can't even stop one OSD. Cluster is probably busy. Retrying later..") + return [] + + # This potentially prolongs the global wait time. + self.mgr.event.wait(1) + # splitting osd_ids in half until ok_to_stop yields success + # maybe popping ids off one by one is better here..depends on the cluster size I guess.. + # There's a lot of room for micro adjustments here + osds = osds[len(osds) // 2:] + return osds + + # todo start draining + # return all([osd.start_draining() for osd in osds]) + + def ok_to_stop(self, osds: List["OSD"]) -> bool: + cmd_args = { + 'prefix': "osd ok-to-stop", + 'ids': [str(osd.osd_id) for osd in osds] + } + return self._run_mon_cmd(cmd_args, error_ok=True) + + def set_osd_flag(self, osds: List["OSD"], flag: str) -> bool: + base_cmd = f"osd {flag}" + self.mgr.log.debug(f"running cmd: {base_cmd} on ids {osds}") + ret, out, err = self.mgr.mon_command({ + 'prefix': base_cmd, + 'ids': [str(osd.osd_id) for osd in osds] + }) + if ret != 0: + self.mgr.log.error(f"Could not set {flag} flag for {osds}. <{err}>") + return False + self.mgr.log.info(f"{','.join([str(o) for o in osds])} now {flag}") + return True + + def get_weight(self, osd: "OSD") -> Optional[float]: + ret, out, err = self.mgr.mon_command({ + 'prefix': 'osd crush tree', + 'format': 'json', + }) + if ret != 0: + self.mgr.log.error(f"Could not dump crush weights. <{err}>") + return None + j = json.loads(out) + for n in j.get("nodes", []): + if n.get("name") == f"osd.{osd.osd_id}": + self.mgr.log.info(f"{osd} crush weight is {n.get('crush_weight')}") + return n.get("crush_weight") + return None + + def reweight_osd(self, osd: "OSD", weight: float) -> bool: + self.mgr.log.debug(f"running cmd: osd crush reweight on {osd}") + ret, out, err = self.mgr.mon_command({ + 'prefix': "osd crush reweight", + 'name': f"osd.{osd.osd_id}", + 'weight': weight, + }) + if ret != 0: + self.mgr.log.error(f"Could not reweight {osd} to {weight}. <{err}>") + return False + self.mgr.log.info(f"{osd} weight is now {weight}") + return True + + def zap_osd(self, osd: "OSD") -> str: + "Zaps all devices that are associated with an OSD" + if osd.hostname is not None: + out, err, code = CephadmServe(self.mgr)._run_cephadm( + osd.hostname, 'osd', 'ceph-volume', + ['--', 'lvm', 'zap', '--destroy', '--osd-id', str(osd.osd_id)], + error_ok=True) + self.mgr.cache.invalidate_host_devices(osd.hostname) + if code: + raise OrchestratorError('Zap failed: %s' % '\n'.join(out + err)) + return '\n'.join(out + err) + raise OrchestratorError(f"Failed to zap OSD {osd.osd_id} because host was unknown") + + def safe_to_destroy(self, osd_ids: List[int]) -> bool: + """ Queries the safe-to-destroy flag for OSDs """ + cmd_args = {'prefix': 'osd safe-to-destroy', + 'ids': [str(x) for x in osd_ids]} + return self._run_mon_cmd(cmd_args, error_ok=True) + + def destroy_osd(self, osd_id: int) -> bool: + """ Destroys an OSD (forcefully) """ + cmd_args = {'prefix': 'osd destroy-actual', + 'id': int(osd_id), + 'yes_i_really_mean_it': True} + return self._run_mon_cmd(cmd_args) + + def purge_osd(self, osd_id: int) -> bool: + """ Purges an OSD from the cluster (forcefully) """ + cmd_args = { + 'prefix': 'osd purge-actual', + 'id': int(osd_id), + 'yes_i_really_mean_it': True + } + return self._run_mon_cmd(cmd_args) + + def _run_mon_cmd(self, cmd_args: dict, error_ok: bool = False) -> bool: + """ + Generic command to run mon_command and evaluate/log the results + """ + ret, out, err = self.mgr.mon_command(cmd_args) + if ret != 0: + self.mgr.log.debug(f"ran {cmd_args} with mon_command") + if not error_ok: + self.mgr.log.error(f"cmd: {cmd_args.get('prefix')} failed with: {err}. (errno:{ret})") + return False + self.mgr.log.debug(f"cmd: {cmd_args.get('prefix')} returns: {out}") + return True + + +class NotFoundError(Exception): + pass + + +class OSD: + + def __init__(self, + osd_id: int, + remove_util: RemoveUtil, + drain_started_at: Optional[datetime] = None, + process_started_at: Optional[datetime] = None, + drain_stopped_at: Optional[datetime] = None, + drain_done_at: Optional[datetime] = None, + draining: bool = False, + started: bool = False, + stopped: bool = False, + replace: bool = False, + force: bool = False, + hostname: Optional[str] = None, + zap: bool = False): + # the ID of the OSD + self.osd_id = osd_id + + # when did process (not the actual draining) start + self.process_started_at = process_started_at + + # when did the drain start + self.drain_started_at = drain_started_at + + # when did the drain stop + self.drain_stopped_at = drain_stopped_at + + # when did the drain finish + self.drain_done_at = drain_done_at + + # did the draining start + self.draining = draining + + # was the operation started + self.started = started + + # was the operation stopped + self.stopped = stopped + + # If this is a replace or remove operation + self.replace = replace + # If we wait for the osd to be drained + self.force = force + # The name of the node + self.hostname = hostname + + # mgr obj to make mgr/mon calls + self.rm_util: RemoveUtil = remove_util + + self.original_weight: Optional[float] = None + + # Whether devices associated with the OSD should be zapped (DATA ERASED) + self.zap = zap + + def start(self) -> None: + if self.started: + logger.debug(f"Already started draining {self}") + return None + self.started = True + self.stopped = False + + def start_draining(self) -> bool: + if self.stopped: + logger.debug(f"Won't start draining {self}. OSD draining is stopped.") + return False + if self.replace: + self.rm_util.set_osd_flag([self], 'out') + else: + self.original_weight = self.rm_util.get_weight(self) + self.rm_util.reweight_osd(self, 0.0) + self.drain_started_at = datetime.utcnow() + self.draining = True + logger.debug(f"Started draining {self}.") + return True + + def stop_draining(self) -> bool: + if self.replace: + self.rm_util.set_osd_flag([self], 'in') + else: + if self.original_weight: + self.rm_util.reweight_osd(self, self.original_weight) + self.drain_stopped_at = datetime.utcnow() + self.draining = False + logger.debug(f"Stopped draining {self}.") + return True + + def stop(self) -> None: + if self.stopped: + logger.debug(f"Already stopped draining {self}") + return None + self.started = False + self.stopped = True + self.stop_draining() + + @property + def is_draining(self) -> bool: + """ + Consider an OSD draining when it is + actively draining but not yet empty + """ + return self.draining and not self.is_empty + + @property + def is_ok_to_stop(self) -> bool: + return self.rm_util.ok_to_stop([self]) + + @property + def is_empty(self) -> bool: + if self.get_pg_count() == 0: + if not self.drain_done_at: + self.drain_done_at = datetime.utcnow() + self.draining = False + return True + return False + + def safe_to_destroy(self) -> bool: + return self.rm_util.safe_to_destroy([self.osd_id]) + + def down(self) -> bool: + return self.rm_util.set_osd_flag([self], 'down') + + def destroy(self) -> bool: + return self.rm_util.destroy_osd(self.osd_id) + + def do_zap(self) -> str: + return self.rm_util.zap_osd(self) + + def purge(self) -> bool: + return self.rm_util.purge_osd(self.osd_id) + + def get_pg_count(self) -> int: + return self.rm_util.get_pg_count(self.osd_id) + + @property + def exists(self) -> bool: + return str(self.osd_id) in self.rm_util.get_osds_in_cluster() + + def drain_status_human(self) -> str: + default_status = 'not started' + status = 'started' if self.started and not self.draining else default_status + status = 'draining' if self.draining else status + status = 'done, waiting for purge' if self.drain_done_at and not self.draining else status + return status + + def pg_count_str(self) -> str: + return 'n/a' if self.get_pg_count() < 0 else str(self.get_pg_count()) + + def to_json(self) -> dict: + out: Dict[str, Any] = dict() + out['osd_id'] = self.osd_id + out['started'] = self.started + out['draining'] = self.draining + out['stopped'] = self.stopped + out['replace'] = self.replace + out['force'] = self.force + out['zap'] = self.zap + out['hostname'] = self.hostname # type: ignore + + for k in ['drain_started_at', 'drain_stopped_at', 'drain_done_at', 'process_started_at']: + if getattr(self, k): + out[k] = datetime_to_str(getattr(self, k)) + else: + out[k] = getattr(self, k) + return out + + @classmethod + def from_json(cls, inp: Optional[Dict[str, Any]], rm_util: RemoveUtil) -> Optional["OSD"]: + if not inp: + return None + for date_field in ['drain_started_at', 'drain_stopped_at', 'drain_done_at', 'process_started_at']: + if inp.get(date_field): + inp.update({date_field: str_to_datetime(inp.get(date_field, ''))}) + inp.update({'remove_util': rm_util}) + if 'nodename' in inp: + hostname = inp.pop('nodename') + inp['hostname'] = hostname + return cls(**inp) + + def __hash__(self) -> int: + return hash(self.osd_id) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, OSD): + return NotImplemented + return self.osd_id == other.osd_id + + def __repr__(self) -> str: + return f"osd.{self.osd_id}{' (draining)' if self.draining else ''}" + + +class OSDRemovalQueue(object): + + def __init__(self, mgr: "CephadmOrchestrator") -> None: + self.mgr: "CephadmOrchestrator" = mgr + self.osds: Set[OSD] = set() + self.rm_util = RemoveUtil(mgr) + + # locks multithreaded access to self.osds. Please avoid locking + # network calls, like mon commands. + self.lock = Lock() + + def process_removal_queue(self) -> None: + """ + Performs actions in the _serve() loop to remove an OSD + when criteria is met. + + we can't hold self.lock, as we're calling _remove_daemon in the loop + """ + + # make sure that we don't run on OSDs that are not in the cluster anymore. + self.cleanup() + + # find osds that are ok-to-stop and not yet draining + ready_to_drain_osds = self._ready_to_drain_osds() + if ready_to_drain_osds: + # start draining those + _ = [osd.start_draining() for osd in ready_to_drain_osds] + + all_osds = self.all_osds() + + logger.debug( + f"{self.queue_size()} OSDs are scheduled " + f"for removal: {all_osds}") + + # Check all osds for their state and take action (remove, purge etc) + new_queue: Set[OSD] = set() + for osd in all_osds: # type: OSD + if not osd.force: + # skip criteria + if not osd.is_empty: + logger.debug(f"{osd} is not empty yet. Waiting a bit more") + new_queue.add(osd) + continue + + if not osd.safe_to_destroy(): + logger.debug( + f"{osd} is not safe-to-destroy yet. Waiting a bit more") + new_queue.add(osd) + continue + + # abort criteria + if not osd.down(): + # also remove it from the remove_osd list and set a health_check warning? + raise orchestrator.OrchestratorError( + f"Could not mark {osd} down") + + # stop and remove daemon + assert osd.hostname is not None + + if self.mgr.cache.has_daemon(f'osd.{osd.osd_id}'): + CephadmServe(self.mgr)._remove_daemon(f'osd.{osd.osd_id}', osd.hostname) + logger.info(f"Successfully removed {osd} on {osd.hostname}") + else: + logger.info(f"Daemon {osd} on {osd.hostname} was already removed") + + if osd.replace: + # mark destroyed in osdmap + if not osd.destroy(): + raise orchestrator.OrchestratorError( + f"Could not destroy {osd}") + logger.info( + f"Successfully destroyed old {osd} on {osd.hostname}; ready for replacement") + else: + # purge from osdmap + if not osd.purge(): + raise orchestrator.OrchestratorError(f"Could not purge {osd}") + logger.info(f"Successfully purged {osd} on {osd.hostname}") + + if osd.zap: + # throws an exception if the zap fails + logger.info(f"Zapping devices for {osd} on {osd.hostname}") + osd.do_zap() + logger.info(f"Successfully zapped devices for {osd} on {osd.hostname}") + + logger.debug(f"Removing {osd} from the queue.") + + # self could change while this is processing (osds get added from the CLI) + # The new set is: 'an intersection of all osds that are still not empty/removed (new_queue) and + # osds that were added while this method was executed' + with self.lock: + self.osds.intersection_update(new_queue) + self._save_to_store() + + def cleanup(self) -> None: + # OSDs can always be cleaned up manually. This ensures that we run on existing OSDs + with self.lock: + for osd in self._not_in_cluster(): + self.osds.remove(osd) + + def _ready_to_drain_osds(self) -> List["OSD"]: + """ + Returns OSDs that are ok to stop and not yet draining. Only returns as many OSDs as can + be accomodated by the 'max_osd_draining_count' config value, considering the number of OSDs + that are already draining. + """ + draining_limit = max(1, self.mgr.max_osd_draining_count) + num_already_draining = len(self.draining_osds()) + num_to_start_draining = max(0, draining_limit - num_already_draining) + stoppable_osds = self.rm_util.find_osd_stop_threshold(self.idling_osds()) + return [] if stoppable_osds is None else stoppable_osds[:num_to_start_draining] + + def _save_to_store(self) -> None: + osd_queue = [osd.to_json() for osd in self.osds] + logger.debug(f"Saving {osd_queue} to store") + self.mgr.set_store('osd_remove_queue', json.dumps(osd_queue)) + + def load_from_store(self) -> None: + with self.lock: + for k, v in self.mgr.get_store_prefix('osd_remove_queue').items(): + for osd in json.loads(v): + logger.debug(f"Loading osd ->{osd} from store") + osd_obj = OSD.from_json(osd, rm_util=self.rm_util) + if osd_obj is not None: + self.osds.add(osd_obj) + + def as_osd_ids(self) -> List[int]: + with self.lock: + return [osd.osd_id for osd in self.osds] + + def queue_size(self) -> int: + with self.lock: + return len(self.osds) + + def draining_osds(self) -> List["OSD"]: + with self.lock: + return [osd for osd in self.osds if osd.is_draining] + + def idling_osds(self) -> List["OSD"]: + with self.lock: + return [osd for osd in self.osds if not osd.is_draining and not osd.is_empty] + + def empty_osds(self) -> List["OSD"]: + with self.lock: + return [osd for osd in self.osds if osd.is_empty] + + def all_osds(self) -> List["OSD"]: + with self.lock: + return [osd for osd in self.osds] + + def _not_in_cluster(self) -> List["OSD"]: + return [osd for osd in self.osds if not osd.exists] + + def enqueue(self, osd: "OSD") -> None: + if not osd.exists: + raise NotFoundError() + with self.lock: + self.osds.add(osd) + osd.start() + + def rm(self, osd: "OSD") -> None: + if not osd.exists: + raise NotFoundError() + osd.stop() + with self.lock: + try: + logger.debug(f'Removing {osd} from the queue.') + self.osds.remove(osd) + except KeyError: + logger.debug(f"Could not find {osd} in queue.") + raise KeyError + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, OSDRemovalQueue): + return False + with self.lock: + return self.osds == other.osds diff --git a/src/pybind/mgr/cephadm/template.py b/src/pybind/mgr/cephadm/template.py new file mode 100644 index 000000000..0d62e587c --- /dev/null +++ b/src/pybind/mgr/cephadm/template.py @@ -0,0 +1,109 @@ +import copy +from typing import Optional, TYPE_CHECKING + +from jinja2 import Environment, PackageLoader, select_autoescape, StrictUndefined +from jinja2 import exceptions as j2_exceptions + +if TYPE_CHECKING: + from cephadm.module import CephadmOrchestrator + + +class TemplateError(Exception): + pass + + +class UndefinedError(TemplateError): + pass + + +class TemplateNotFoundError(TemplateError): + pass + + +class TemplateEngine: + def render(self, name: str, context: Optional[dict] = None) -> str: + raise NotImplementedError() + + +class Jinja2Engine(TemplateEngine): + def __init__(self) -> None: + self.env = Environment( + loader=PackageLoader('cephadm', 'templates'), + autoescape=select_autoescape(['html', 'xml'], default_for_string=False), + trim_blocks=True, + lstrip_blocks=True, + undefined=StrictUndefined + ) + + def render(self, name: str, context: Optional[dict] = None) -> str: + try: + template = self.env.get_template(name) + if context is None: + return template.render() + return template.render(context) + except j2_exceptions.UndefinedError as e: + raise UndefinedError(e.message) + except j2_exceptions.TemplateNotFound as e: + raise TemplateNotFoundError(e.message) + + def render_plain(self, source: str, context: Optional[dict]) -> str: + try: + template = self.env.from_string(source) + if context is None: + return template.render() + return template.render(context) + except j2_exceptions.UndefinedError as e: + raise UndefinedError(e.message) + except j2_exceptions.TemplateNotFound as e: + raise TemplateNotFoundError(e.message) + + +class TemplateMgr: + def __init__(self, mgr: "CephadmOrchestrator"): + self.engine = Jinja2Engine() + self.base_context = { + 'cephadm_managed': 'This file is generated by cephadm.' + } + self.mgr = mgr + + def render(self, name: str, + context: Optional[dict] = None, + managed_context: bool = True, + host: Optional[str] = None) -> str: + """Render a string from a template with context. + + :param name: template name. e.g. services/nfs/ganesha.conf.j2 + :type name: str + :param context: a dictionary that contains values to be used in the template, defaults + to None + :type context: Optional[dict], optional + :param managed_context: to inject default context like managed header or not, defaults + to True + :type managed_context: bool, optional + :param host: The host name used to build the key to access + the module's persistent key-value store. + :type host: Optional[str], optional + :return: the templated string + :rtype: str + """ + ctx = {} + if managed_context: + ctx = copy.deepcopy(self.base_context) + if context is not None: + ctx = {**ctx, **context} + + # Check if the given name exists in the module's persistent + # key-value store, e.g. + # - blink_device_light_cmd + # - /blink_device_light_cmd + # - services/nfs/ganesha.conf + store_name = name.rstrip('.j2') + custom_template = self.mgr.get_store(store_name, None) + if host and custom_template is None: + store_name = '{}/{}'.format(host, store_name) + custom_template = self.mgr.get_store(store_name, None) + + if custom_template: + return self.engine.render_plain(custom_template, ctx) + else: + return self.engine.render(name, ctx) diff --git a/src/pybind/mgr/cephadm/templates/blink_device_light_cmd.j2 b/src/pybind/mgr/cephadm/templates/blink_device_light_cmd.j2 new file mode 100644 index 000000000..dab115833 --- /dev/null +++ b/src/pybind/mgr/cephadm/templates/blink_device_light_cmd.j2 @@ -0,0 +1 @@ +lsmcli local-disk-{{ ident_fault }}-led-{{'on' if on else 'off'}} --path '{{ path or dev }}' diff --git a/src/pybind/mgr/cephadm/templates/services/alertmanager/alertmanager.yml.j2 b/src/pybind/mgr/cephadm/templates/services/alertmanager/alertmanager.yml.j2 new file mode 100644 index 000000000..4e394106f --- /dev/null +++ b/src/pybind/mgr/cephadm/templates/services/alertmanager/alertmanager.yml.j2 @@ -0,0 +1,47 @@ +# {{ cephadm_managed }} +# See https://prometheus.io/docs/alerting/configuration/ for documentation. + +global: + resolve_timeout: 5m +{% if not secure %} + http_config: + tls_config: + insecure_skip_verify: true +{% endif %} + +route: + receiver: 'default' + routes: + - group_by: ['alertname'] + group_wait: 10s + group_interval: 10s + repeat_interval: 1h + receiver: 'ceph-dashboard' +{% if snmp_gateway_urls %} + continue: true + - receiver: 'snmp-gateway' + repeat_interval: 1h + group_interval: 10s + group_by: ['alertname'] + match_re: + oid: "(1.3.6.1.4.1.50495.).*" +{% endif %} + +receivers: +- name: 'default' + webhook_configs: +{% for url in default_webhook_urls %} + - url: '{{ url }}' +{% endfor %} +- name: 'ceph-dashboard' + webhook_configs: +{% for url in dashboard_urls %} + - url: '{{ url }}/api/prometheus_receiver' +{% endfor %} +{% if snmp_gateway_urls %} +- name: 'snmp-gateway' + webhook_configs: +{% for url in snmp_gateway_urls %} + - url: '{{ url }}' +{% endfor %} +{% endif %} diff --git a/src/pybind/mgr/cephadm/templates/services/grafana/ceph-dashboard.yml.j2 b/src/pybind/mgr/cephadm/templates/services/grafana/ceph-dashboard.yml.j2 new file mode 100644 index 000000000..170e6f246 --- /dev/null +++ b/src/pybind/mgr/cephadm/templates/services/grafana/ceph-dashboard.yml.j2 @@ -0,0 +1,18 @@ +# {{ cephadm_managed }} +deleteDatasources: +{% for host in hosts %} + - name: 'Dashboard{{ loop.index }}' + orgId: 1 +{% endfor %} + +datasources: +{% for host in hosts %} + - name: 'Dashboard{{ loop.index }}' + type: 'prometheus' + access: 'proxy' + orgId: 1 + url: '{{ host }}' + basicAuth: false + isDefault: {{ 'true' if loop.first else 'false' }} + editable: false +{% endfor %} diff --git a/src/pybind/mgr/cephadm/templates/services/grafana/grafana.ini.j2 b/src/pybind/mgr/cephadm/templates/services/grafana/grafana.ini.j2 new file mode 100644 index 000000000..cf23802d7 --- /dev/null +++ b/src/pybind/mgr/cephadm/templates/services/grafana/grafana.ini.j2 @@ -0,0 +1,24 @@ +# {{ cephadm_managed }} +[users] + default_theme = light +[auth.anonymous] + enabled = true + org_name = 'Main Org.' + org_role = 'Viewer' +[server] + domain = 'bootstrap.storage.lab' + protocol = https + cert_file = /etc/grafana/certs/cert_file + cert_key = /etc/grafana/certs/cert_key + http_port = {{ http_port }} + http_addr = {{ http_addr }} +[security] +{% if not initial_admin_password %} + disable_initial_admin_creation = true +{% else %} + admin_user = admin + admin_password = {{ initial_admin_password }} +{% endif %} + cookie_secure = true + cookie_samesite = none + allow_embedding = true diff --git a/src/pybind/mgr/cephadm/templates/services/ingress/haproxy.cfg.j2 b/src/pybind/mgr/cephadm/templates/services/ingress/haproxy.cfg.j2 new file mode 100644 index 000000000..cb84f1d07 --- /dev/null +++ b/src/pybind/mgr/cephadm/templates/services/ingress/haproxy.cfg.j2 @@ -0,0 +1,83 @@ +# {{ cephadm_managed }} +global + log 127.0.0.1 local2 + chroot /var/lib/haproxy + pidfile /var/lib/haproxy/haproxy.pid + maxconn 8000 + daemon + stats socket /var/lib/haproxy/stats +{% if spec.ssl_cert %} + {% if spec.ssl_dh_param %} + tune.ssl.default-dh-param {{ spec.ssl_dh_param }} + {% endif %} + {% if spec.ssl_ciphers %} + ssl-default-bind-ciphers {{ spec.ssl_ciphers | join(':') }} + {% endif %} + {% if spec.ssl_options %} + ssl-default-bind-options {{ spec.ssl_options | join(' ') }} + {% endif %} +{% endif %} + +defaults + mode {{ mode }} + log global +{% if mode == 'http' %} + option httplog + option dontlognull + option http-server-close + option forwardfor except 127.0.0.0/8 + option redispatch + retries 3 + timeout queue 20s + timeout connect 5s + timeout http-request 1s + timeout http-keep-alive 5s + timeout client 1s + timeout server 1s + timeout check 5s +{% endif %} +{% if mode == 'tcp' %} + timeout queue 1m + timeout connect 10s + timeout client 1m + timeout server 1m + timeout check 10s +{% endif %} + maxconn 8000 + +frontend stats + mode http + bind {{ ip }}:{{ monitor_port }} + bind localhost:{{ monitor_port }} + stats enable + stats uri /stats + stats refresh 10s + stats auth {{ user }}:{{ password }} + http-request use-service prometheus-exporter if { path /metrics } + monitor-uri /health + +frontend frontend +{% if spec.ssl_cert %} + bind {{ ip }}:{{ frontend_port }} ssl crt /var/lib/haproxy/haproxy.pem +{% else %} + bind {{ ip }}:{{ frontend_port }} +{% endif %} + default_backend backend + +backend backend +{% if mode == 'http' %} + option forwardfor + balance static-rr + option httpchk HEAD / HTTP/1.0 + {% for server in servers %} + server {{ server.name }} {{ server.ip }}:{{ server.port }} check weight 100 + {% endfor %} +{% endif %} +{% if mode == 'tcp' %} + mode tcp + balance source + hash-type consistent + {% for server in servers %} + server {{ server.name }} {{ server.ip }}:{{ server.port }} + {% endfor %} +{% endif %} diff --git a/src/pybind/mgr/cephadm/templates/services/ingress/keepalived.conf.j2 b/src/pybind/mgr/cephadm/templates/services/ingress/keepalived.conf.j2 new file mode 100644 index 000000000..f560c9756 --- /dev/null +++ b/src/pybind/mgr/cephadm/templates/services/ingress/keepalived.conf.j2 @@ -0,0 +1,34 @@ +# {{ cephadm_managed }} +vrrp_script check_backend { + script "{{ script }}" + weight -20 + interval 2 + rise 2 + fall 2 +} + +{% for x in range(virtual_ips|length) %} +vrrp_instance VI_{{ x }} { + state {{ states[x] }} + priority {{ priorities[x] }} + interface {{ interface }} + virtual_router_id {{ 50 + x }} + advert_int 1 + authentication { + auth_type PASS + auth_pass {{ password }} + } + unicast_src_ip {{ host_ip }} + unicast_peer { + {% for ip in other_ips %} + {{ ip }} + {% endfor %} + } + virtual_ipaddress { + {{ virtual_ips[x] }} dev {{ interface }} + } + track_script { + check_backend + } +} +{% endfor %} diff --git a/src/pybind/mgr/cephadm/templates/services/iscsi/iscsi-gateway.cfg.j2 b/src/pybind/mgr/cephadm/templates/services/iscsi/iscsi-gateway.cfg.j2 new file mode 100644 index 000000000..c2582ace7 --- /dev/null +++ b/src/pybind/mgr/cephadm/templates/services/iscsi/iscsi-gateway.cfg.j2 @@ -0,0 +1,13 @@ +# {{ cephadm_managed }} +[config] +cluster_client_name = {{ client_name }} +pool = {{ spec.pool }} +trusted_ip_list = {{ trusted_ip_list|default("''", true) }} +minimum_gateways = 1 +api_port = {{ spec.api_port|default("''", true) }} +api_user = {{ spec.api_user|default("''", true) }} +api_password = {{ spec.api_password|default("''", true) }} +api_secure = {{ spec.api_secure|default('False', true) }} +log_to_stderr = True +log_to_stderr_prefix = debug +log_to_file = False diff --git a/src/pybind/mgr/cephadm/templates/services/nfs/ganesha.conf.j2 b/src/pybind/mgr/cephadm/templates/services/nfs/ganesha.conf.j2 new file mode 100644 index 000000000..9d6e15f1c --- /dev/null +++ b/src/pybind/mgr/cephadm/templates/services/nfs/ganesha.conf.j2 @@ -0,0 +1,35 @@ +# {{ cephadm_managed }} +NFS_CORE_PARAM { + Enable_NLM = false; + Enable_RQUOTA = false; + Protocols = 4; + NFS_Port = {{ port }}; +{% if bind_addr %} + Bind_addr = {{ bind_addr }}; +{% endif %} +} + +NFSv4 { + Delegations = false; + RecoveryBackend = 'rados_cluster'; + Minor_Versions = 1, 2; +} + +RADOS_KV { + UserId = "{{ user }}"; + nodeid = "{{ nodeid }}"; + pool = "{{ pool }}"; + namespace = "{{ namespace }}"; +} + +RADOS_URLS { + UserId = "{{ user }}"; + watch_url = "{{ url }}"; +} + +RGW { + cluster = "ceph"; + name = "client.{{ rgw_user }}"; +} + +%url {{ url }} diff --git a/src/pybind/mgr/cephadm/templates/services/prometheus/prometheus.yml.j2 b/src/pybind/mgr/cephadm/templates/services/prometheus/prometheus.yml.j2 new file mode 100644 index 000000000..bb0a8fcae --- /dev/null +++ b/src/pybind/mgr/cephadm/templates/services/prometheus/prometheus.yml.j2 @@ -0,0 +1,41 @@ +# {{ cephadm_managed }} +global: + scrape_interval: 10s + evaluation_interval: 10s +rule_files: + - /etc/prometheus/alerting/* +{% if alertmgr_targets %} +alerting: + alertmanagers: + - scheme: http + static_configs: + - targets: [{{ alertmgr_targets|join(', ') }}] +{% endif %} +scrape_configs: + - job_name: 'ceph' + honor_labels: true + static_configs: + - targets: +{% for mgr in mgr_scrape_list %} + - '{{ mgr }}' +{% endfor %} + +{% if nodes %} + - job_name: 'node' + static_configs: +{% for node in nodes %} + - targets: ['{{ node.url }}'] + labels: + instance: '{{ node.hostname }}' +{% endfor %} +{% endif %} + +{% if haproxy_targets %} + - job_name: 'haproxy' + static_configs: +{% for haproxy in haproxy_targets %} + - targets: [{{ haproxy.url }}] + labels: + instance: '{{ haproxy.service }}' +{% endfor %} +{% endif %} diff --git a/src/pybind/mgr/cephadm/tests/__init__.py b/src/pybind/mgr/cephadm/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/pybind/mgr/cephadm/tests/conftest.py b/src/pybind/mgr/cephadm/tests/conftest.py new file mode 100644 index 000000000..e8add2c7b --- /dev/null +++ b/src/pybind/mgr/cephadm/tests/conftest.py @@ -0,0 +1,27 @@ +import pytest + +from cephadm.services.osd import RemoveUtil, OSD +from tests import mock + +from .fixtures import with_cephadm_module + + +@pytest.fixture() +def cephadm_module(): + with with_cephadm_module({}) as m: + yield m + + +@pytest.fixture() +def rm_util(): + with with_cephadm_module({}) as m: + r = RemoveUtil.__new__(RemoveUtil) + r.__init__(m) + yield r + + +@pytest.fixture() +def osd_obj(): + with mock.patch("cephadm.services.osd.RemoveUtil"): + o = OSD(0, mock.MagicMock()) + yield o diff --git a/src/pybind/mgr/cephadm/tests/fixtures.py b/src/pybind/mgr/cephadm/tests/fixtures.py new file mode 100644 index 000000000..8c2e1cfbf --- /dev/null +++ b/src/pybind/mgr/cephadm/tests/fixtures.py @@ -0,0 +1,151 @@ +import fnmatch +from contextlib import contextmanager + +from ceph.deployment.service_spec import PlacementSpec, ServiceSpec +from ceph.utils import datetime_to_str, datetime_now +from cephadm.serve import CephadmServe + +try: + from typing import Any, Iterator, List +except ImportError: + pass + +from cephadm import CephadmOrchestrator +from orchestrator import raise_if_exception, OrchResult, HostSpec, DaemonDescriptionStatus +from tests import mock + + +def get_ceph_option(_, key): + return __file__ + + +def get_module_option_ex(_, module, key, default=None): + if module == 'prometheus': + if key == 'server_port': + return 9283 + return None + + +def _run_cephadm(ret): + def foo(s, host, entity, cmd, e, **kwargs): + if cmd == 'gather-facts': + return '{}', '', 0 + return [ret], '', 0 + return foo + + +def match_glob(val, pat): + ok = fnmatch.fnmatchcase(val, pat) + if not ok: + assert pat in val + + +@contextmanager +def with_cephadm_module(module_options=None, store=None): + """ + :param module_options: Set opts as if they were set before module.__init__ is called + :param store: Set the store before module.__init__ is called + """ + with mock.patch("cephadm.module.CephadmOrchestrator.get_ceph_option", get_ceph_option),\ + mock.patch("cephadm.services.osd.RemoveUtil._run_mon_cmd"), \ + mock.patch('cephadm.module.CephadmOrchestrator.get_module_option_ex', get_module_option_ex),\ + mock.patch("cephadm.module.CephadmOrchestrator.get_osdmap"), \ + mock.patch("cephadm.module.CephadmOrchestrator.remote"), \ + mock.patch('cephadm.offline_watcher.OfflineHostWatcher.run'): + + m = CephadmOrchestrator.__new__(CephadmOrchestrator) + if module_options is not None: + for k, v in module_options.items(): + m._ceph_set_module_option('cephadm', k, v) + if store is None: + store = {} + if '_ceph_get/mon_map' not in store: + m.mock_store_set('_ceph_get', 'mon_map', { + 'modified': datetime_to_str(datetime_now()), + 'fsid': 'foobar', + }) + if '_ceph_get/mgr_map' not in store: + m.mock_store_set('_ceph_get', 'mgr_map', { + 'services': { + 'dashboard': 'http://[::1]:8080', + 'prometheus': 'http://[::1]:8081' + }, + 'modules': ['dashboard', 'prometheus'], + }) + for k, v in store.items(): + m._ceph_set_store(k, v) + + m.__init__('cephadm', 0, 0) + m._cluster_fsid = "fsid" + yield m + + +def wait(m: CephadmOrchestrator, c: OrchResult) -> Any: + return raise_if_exception(c) + + +@contextmanager +def with_host(m: CephadmOrchestrator, name, addr='1::4', refresh_hosts=True, rm_with_force=True): + with mock.patch("cephadm.utils.resolve_ip", return_value=addr): + wait(m, m.add_host(HostSpec(hostname=name))) + if refresh_hosts: + CephadmServe(m)._refresh_hosts_and_daemons() + yield + wait(m, m.remove_host(name, force=rm_with_force)) + + +def assert_rm_service(cephadm: CephadmOrchestrator, srv_name): + mon_or_mgr = cephadm.spec_store[srv_name].spec.service_type in ('mon', 'mgr') + if mon_or_mgr: + assert 'Unable' in wait(cephadm, cephadm.remove_service(srv_name)) + return + assert wait(cephadm, cephadm.remove_service(srv_name)) == f'Removed service {srv_name}' + assert cephadm.spec_store[srv_name].deleted is not None + CephadmServe(cephadm)._check_daemons() + CephadmServe(cephadm)._apply_all_services() + assert cephadm.spec_store[srv_name].deleted + unmanaged = cephadm.spec_store[srv_name].spec.unmanaged + CephadmServe(cephadm)._purge_deleted_services() + if not unmanaged: # cause then we're not deleting daemons + assert srv_name not in cephadm.spec_store, f'{cephadm.spec_store[srv_name]!r}' + + +@contextmanager +def with_service(cephadm_module: CephadmOrchestrator, spec: ServiceSpec, meth=None, host: str = '', status_running=False) -> Iterator[List[str]]: + if spec.placement.is_empty() and host: + spec.placement = PlacementSpec(hosts=[host], count=1) + if meth is not None: + c = meth(cephadm_module, spec) + assert wait(cephadm_module, c) == f'Scheduled {spec.service_name()} update...' + else: + c = cephadm_module.apply([spec]) + assert wait(cephadm_module, c) == [f'Scheduled {spec.service_name()} update...'] + + specs = [d.spec for d in wait(cephadm_module, cephadm_module.describe_service())] + assert spec in specs + + CephadmServe(cephadm_module)._apply_all_services() + + if status_running: + make_daemons_running(cephadm_module, spec.service_name()) + + dds = wait(cephadm_module, cephadm_module.list_daemons()) + own_dds = [dd for dd in dds if dd.service_name() == spec.service_name()] + if host and spec.service_type != 'osd': + assert own_dds + + yield [dd.name() for dd in own_dds] + + assert_rm_service(cephadm_module, spec.service_name()) + + +def make_daemons_running(cephadm_module, service_name): + own_dds = cephadm_module.cache.get_daemons_by_service(service_name) + for dd in own_dds: + dd.status = DaemonDescriptionStatus.running # We're changing the reference + + +def _deploy_cephadm_binary(host): + def foo(*args, **kwargs): + return True + return foo diff --git a/src/pybind/mgr/cephadm/tests/test_autotune.py b/src/pybind/mgr/cephadm/tests/test_autotune.py new file mode 100644 index 000000000..524da9c00 --- /dev/null +++ b/src/pybind/mgr/cephadm/tests/test_autotune.py @@ -0,0 +1,69 @@ +# Disable autopep8 for this file: + +# fmt: off + +import pytest + +from cephadm.autotune import MemoryAutotuner +from orchestrator import DaemonDescription + + +@pytest.mark.parametrize("total,daemons,config,result", + [ # noqa: E128 + ( + 128 * 1024 * 1024 * 1024, + [], + {}, + None, + ), + ( + 128 * 1024 * 1024 * 1024, + [ + DaemonDescription('osd', '1', 'host1'), + DaemonDescription('osd', '2', 'host1'), + ], + {}, + 64 * 1024 * 1024 * 1024, + ), + ( + 128 * 1024 * 1024 * 1024, + [ + DaemonDescription('osd', '1', 'host1'), + DaemonDescription('osd', '2', 'host1'), + DaemonDescription('osd', '3', 'host1'), + ], + { + 'osd.3': 16 * 1024 * 1024 * 1024, + }, + 56 * 1024 * 1024 * 1024, + ), + ( + 128 * 1024 * 1024 * 1024, + [ + DaemonDescription('mgr', 'a', 'host1'), + DaemonDescription('osd', '1', 'host1'), + DaemonDescription('osd', '2', 'host1'), + ], + {}, + 62 * 1024 * 1024 * 1024, + ) + ]) +def test_autotune(total, daemons, config, result): + def fake_getter(who, opt): + if opt == 'osd_memory_target_autotune': + if who in config: + return False + else: + return True + if opt == 'osd_memory_target': + return config.get(who, 4 * 1024 * 1024 * 1024) + if opt == 'mds_cache_memory_limit': + return 16 * 1024 * 1024 * 1024 + + a = MemoryAutotuner( + total_mem=total, + daemons=daemons, + config_get=fake_getter, + ) + val, osds = a.tune() + assert val == result diff --git a/src/pybind/mgr/cephadm/tests/test_cephadm.py b/src/pybind/mgr/cephadm/tests/test_cephadm.py new file mode 100644 index 000000000..a6850f6cb --- /dev/null +++ b/src/pybind/mgr/cephadm/tests/test_cephadm.py @@ -0,0 +1,1805 @@ +import json +import logging +from contextlib import contextmanager + +import pytest + +from ceph.deployment.drive_group import DriveGroupSpec, DeviceSelection +from cephadm.serve import CephadmServe +from cephadm.services.osd import OSD, OSDRemovalQueue, OsdIdClaims + +try: + from typing import List +except ImportError: + pass + +from execnet.gateway_bootstrap import HostNotFound + +from ceph.deployment.service_spec import ServiceSpec, PlacementSpec, RGWSpec, \ + NFSServiceSpec, IscsiServiceSpec, HostPlacementSpec, CustomContainerSpec, MDSSpec +from ceph.deployment.drive_selection.selector import DriveSelection +from ceph.deployment.inventory import Devices, Device +from ceph.utils import datetime_to_str, datetime_now +from orchestrator import DaemonDescription, InventoryHost, \ + HostSpec, OrchestratorError, DaemonDescriptionStatus, OrchestratorEvent +from tests import mock +from .fixtures import wait, _run_cephadm, match_glob, with_host, \ + with_cephadm_module, with_service, _deploy_cephadm_binary, make_daemons_running +from cephadm.module import CephadmOrchestrator + +""" +TODOs: + There is really room for improvement here. I just quickly assembled theses tests. + I general, everything should be testes in Teuthology as well. Reasons for + also testing this here is the development roundtrip time. +""" + + +def assert_rm_daemon(cephadm: CephadmOrchestrator, prefix, host): + dds: List[DaemonDescription] = wait(cephadm, cephadm.list_daemons(host=host)) + d_names = [dd.name() for dd in dds if dd.name().startswith(prefix)] + assert d_names + # there should only be one daemon (if not match_glob will throw mismatch) + assert len(d_names) == 1 + + c = cephadm.remove_daemons(d_names) + [out] = wait(cephadm, c) + # picking the 1st element is needed, rather than passing the list when the daemon + # name contains '-' char. If not, the '-' is treated as a range i.e. cephadm-exporter + # is treated like a m-e range which is invalid. rbd-mirror (d-m) and node-exporter (e-e) + # are valid, so pass without incident! Also, match_gob acts on strings anyway! + match_glob(out, f"Removed {d_names[0]}* from host '{host}'") + + +@contextmanager +def with_daemon(cephadm_module: CephadmOrchestrator, spec: ServiceSpec, host: str): + spec.placement = PlacementSpec(hosts=[host], count=1) + + c = cephadm_module.add_daemon(spec) + [out] = wait(cephadm_module, c) + match_glob(out, f"Deployed {spec.service_name()}.* on host '{host}'") + + dds = cephadm_module.cache.get_daemons_by_service(spec.service_name()) + for dd in dds: + if dd.hostname == host: + yield dd.daemon_id + assert_rm_daemon(cephadm_module, spec.service_name(), host) + return + + assert False, 'Daemon not found' + + +@contextmanager +def with_osd_daemon(cephadm_module: CephadmOrchestrator, _run_cephadm, host: str, osd_id: int, ceph_volume_lvm_list=None): + cephadm_module.mock_store_set('_ceph_get', 'osd_map', { + 'osds': [ + { + 'osd': 1, + 'up_from': 0, + 'up': True, + 'uuid': 'uuid' + } + ] + }) + + _run_cephadm.reset_mock(return_value=True, side_effect=True) + if ceph_volume_lvm_list: + _run_cephadm.side_effect = ceph_volume_lvm_list + else: + def _ceph_volume_list(s, host, entity, cmd, **kwargs): + logging.info(f'ceph-volume cmd: {cmd}') + if 'raw' in cmd: + return json.dumps({ + "21a4209b-f51b-4225-81dc-d2dca5b8b2f5": { + "ceph_fsid": cephadm_module._cluster_fsid, + "device": "/dev/loop0", + "osd_id": 21, + "osd_uuid": "21a4209b-f51b-4225-81dc-d2dca5b8b2f5", + "type": "bluestore" + }, + }), '', 0 + if 'lvm' in cmd: + return json.dumps({ + str(osd_id): [{ + 'tags': { + 'ceph.cluster_fsid': cephadm_module._cluster_fsid, + 'ceph.osd_fsid': 'uuid' + }, + 'type': 'data' + }] + }), '', 0 + return '{}', '', 0 + + _run_cephadm.side_effect = _ceph_volume_list + + assert cephadm_module._osd_activate( + [host]).stdout == f"Created osd(s) 1 on host '{host}'" + assert _run_cephadm.mock_calls == [ + mock.call(host, 'osd', 'ceph-volume', + ['--', 'lvm', 'list', '--format', 'json'], no_fsid=False, image=''), + mock.call(host, f'osd.{osd_id}', 'deploy', + ['--name', f'osd.{osd_id}', '--meta-json', mock.ANY, + '--config-json', '-', '--osd-fsid', 'uuid'], + stdin=mock.ANY, image=''), + mock.call(host, 'osd', 'ceph-volume', + ['--', 'raw', 'list', '--format', 'json'], no_fsid=False, image=''), + ] + dd = cephadm_module.cache.get_daemon(f'osd.{osd_id}', host=host) + assert dd.name() == f'osd.{osd_id}' + yield dd + cephadm_module._remove_daemons([(f'osd.{osd_id}', host)]) + + +class TestCephadm(object): + + def test_get_unique_name(self, cephadm_module): + # type: (CephadmOrchestrator) -> None + existing = [ + DaemonDescription(daemon_type='mon', daemon_id='a') + ] + new_mon = cephadm_module.get_unique_name('mon', 'myhost', existing) + match_glob(new_mon, 'myhost') + new_mgr = cephadm_module.get_unique_name('mgr', 'myhost', existing) + match_glob(new_mgr, 'myhost.*') + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('[]')) + def test_host(self, cephadm_module): + assert wait(cephadm_module, cephadm_module.get_hosts()) == [] + with with_host(cephadm_module, 'test'): + assert wait(cephadm_module, cephadm_module.get_hosts()) == [HostSpec('test', '1::4')] + + # Be careful with backward compatibility when changing things here: + assert json.loads(cephadm_module.get_store('inventory')) == \ + {"test": {"hostname": "test", "addr": "1::4", "labels": [], "status": ""}} + + with with_host(cephadm_module, 'second', '1.2.3.5'): + assert wait(cephadm_module, cephadm_module.get_hosts()) == [ + HostSpec('test', '1::4'), + HostSpec('second', '1.2.3.5') + ] + + assert wait(cephadm_module, cephadm_module.get_hosts()) == [HostSpec('test', '1::4')] + assert wait(cephadm_module, cephadm_module.get_hosts()) == [] + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('[]')) + @mock.patch("cephadm.utils.resolve_ip") + def test_re_add_host_receive_loopback(self, resolve_ip, cephadm_module): + resolve_ip.side_effect = ['192.168.122.1', '127.0.0.1', '127.0.0.1'] + assert wait(cephadm_module, cephadm_module.get_hosts()) == [] + cephadm_module._add_host(HostSpec('test', '192.168.122.1')) + assert wait(cephadm_module, cephadm_module.get_hosts()) == [HostSpec('test', '192.168.122.1')] + cephadm_module._add_host(HostSpec('test')) + assert wait(cephadm_module, cephadm_module.get_hosts()) == [HostSpec('test', '192.168.122.1')] + with pytest.raises(OrchestratorError): + cephadm_module._add_host(HostSpec('test2')) + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('[]')) + def test_service_ls(self, cephadm_module): + with with_host(cephadm_module, 'test'): + c = cephadm_module.list_daemons(refresh=True) + assert wait(cephadm_module, c) == [] + with with_service(cephadm_module, MDSSpec('mds', 'name', unmanaged=True)) as _, \ + with_daemon(cephadm_module, MDSSpec('mds', 'name'), 'test') as _: + + c = cephadm_module.list_daemons() + + def remove_id_events(dd): + out = dd.to_json() + del out['daemon_id'] + del out['events'] + del out['daemon_name'] + return out + + assert [remove_id_events(dd) for dd in wait(cephadm_module, c)] == [ + { + 'service_name': 'mds.name', + 'daemon_type': 'mds', + 'hostname': 'test', + 'status': 2, + 'status_desc': 'starting', + 'is_active': False, + 'ports': [], + } + ] + + with with_service(cephadm_module, ServiceSpec('rgw', 'r.z'), + CephadmOrchestrator.apply_rgw, 'test', status_running=True): + make_daemons_running(cephadm_module, 'mds.name') + + c = cephadm_module.describe_service() + out = [dict(o.to_json()) for o in wait(cephadm_module, c)] + expected = [ + { + 'placement': {'count': 2}, + 'service_id': 'name', + 'service_name': 'mds.name', + 'service_type': 'mds', + 'status': {'created': mock.ANY, 'running': 1, 'size': 2}, + 'unmanaged': True + }, + { + 'placement': { + 'count': 1, + 'hosts': ["test"] + }, + 'service_id': 'r.z', + 'service_name': 'rgw.r.z', + 'service_type': 'rgw', + 'status': {'created': mock.ANY, 'running': 1, 'size': 1, + 'ports': [80]}, + } + ] + for o in out: + if 'events' in o: + del o['events'] # delete it, as it contains a timestamp + assert out == expected + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('[]')) + def test_service_ls_service_type_flag(self, cephadm_module): + with with_host(cephadm_module, 'host1'): + with with_host(cephadm_module, 'host2'): + with with_service(cephadm_module, ServiceSpec('mgr', placement=PlacementSpec(count=2)), + CephadmOrchestrator.apply_mgr, '', status_running=True): + with with_service(cephadm_module, MDSSpec('mds', 'test-id', placement=PlacementSpec(count=2)), + CephadmOrchestrator.apply_mds, '', status_running=True): + + # with no service-type. Should provide info fot both services + c = cephadm_module.describe_service() + out = [dict(o.to_json()) for o in wait(cephadm_module, c)] + expected = [ + { + 'placement': {'count': 2}, + 'service_name': 'mgr', + 'service_type': 'mgr', + 'status': {'created': mock.ANY, + 'running': 2, + 'size': 2} + }, + { + 'placement': {'count': 2}, + 'service_id': 'test-id', + 'service_name': 'mds.test-id', + 'service_type': 'mds', + 'status': {'created': mock.ANY, + 'running': 2, + 'size': 2} + }, + ] + + for o in out: + if 'events' in o: + del o['events'] # delete it, as it contains a timestamp + assert out == expected + + # with service-type. Should provide info fot only mds + c = cephadm_module.describe_service(service_type='mds') + out = [dict(o.to_json()) for o in wait(cephadm_module, c)] + expected = [ + { + 'placement': {'count': 2}, + 'service_id': 'test-id', + 'service_name': 'mds.test-id', + 'service_type': 'mds', + 'status': {'created': mock.ANY, + 'running': 2, + 'size': 2} + }, + ] + + for o in out: + if 'events' in o: + del o['events'] # delete it, as it contains a timestamp + assert out == expected + + # service-type should not match with service names + c = cephadm_module.describe_service(service_type='mds.test-id') + out = [dict(o.to_json()) for o in wait(cephadm_module, c)] + assert out == [] + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('[]')) + def test_device_ls(self, cephadm_module): + with with_host(cephadm_module, 'test'): + c = cephadm_module.get_inventory() + assert wait(cephadm_module, c) == [InventoryHost('test')] + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm( + json.dumps([ + dict( + name='rgw.myrgw.foobar', + style='cephadm', + fsid='fsid', + container_id='container_id', + version='version', + state='running', + ), + dict( + name='something.foo.bar', + style='cephadm', + fsid='fsid', + ), + dict( + name='haproxy.test.bar', + style='cephadm', + fsid='fsid', + ), + + ]) + )) + def test_list_daemons(self, cephadm_module: CephadmOrchestrator): + cephadm_module.service_cache_timeout = 10 + with with_host(cephadm_module, 'test'): + CephadmServe(cephadm_module)._refresh_host_daemons('test') + dds = wait(cephadm_module, cephadm_module.list_daemons()) + assert {d.name() for d in dds} == {'rgw.myrgw.foobar', 'haproxy.test.bar'} + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('[]')) + def test_daemon_action(self, cephadm_module: CephadmOrchestrator): + cephadm_module.service_cache_timeout = 10 + with with_host(cephadm_module, 'test'): + with with_service(cephadm_module, RGWSpec(service_id='myrgw.foobar', unmanaged=True)) as _, \ + with_daemon(cephadm_module, RGWSpec(service_id='myrgw.foobar'), 'test') as daemon_id: + + d_name = 'rgw.' + daemon_id + + c = cephadm_module.daemon_action('redeploy', d_name) + assert wait(cephadm_module, + c) == f"Scheduled to redeploy rgw.{daemon_id} on host 'test'" + + for what in ('start', 'stop', 'restart'): + c = cephadm_module.daemon_action(what, d_name) + assert wait(cephadm_module, + c) == F"Scheduled to {what} {d_name} on host 'test'" + + # Make sure, _check_daemons does a redeploy due to monmap change: + cephadm_module._store['_ceph_get/mon_map'] = { + 'modified': datetime_to_str(datetime_now()), + 'fsid': 'foobar', + } + cephadm_module.notify('mon_map', None) + + CephadmServe(cephadm_module)._check_daemons() + + assert cephadm_module.events.get_for_daemon(d_name) == [ + OrchestratorEvent(mock.ANY, 'daemon', d_name, 'INFO', + f"Deployed {d_name} on host \'test\'"), + OrchestratorEvent(mock.ANY, 'daemon', d_name, 'INFO', + f"stop {d_name} from host \'test\'"), + ] + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('[]')) + def test_daemon_action_fail(self, cephadm_module: CephadmOrchestrator): + cephadm_module.service_cache_timeout = 10 + with with_host(cephadm_module, 'test'): + with with_service(cephadm_module, RGWSpec(service_id='myrgw.foobar', unmanaged=True)) as _, \ + with_daemon(cephadm_module, RGWSpec(service_id='myrgw.foobar'), 'test') as daemon_id: + with mock.patch('ceph_module.BaseMgrModule._ceph_send_command') as _ceph_send_command: + + _ceph_send_command.side_effect = Exception("myerror") + + # Make sure, _check_daemons does a redeploy due to monmap change: + cephadm_module.mock_store_set('_ceph_get', 'mon_map', { + 'modified': datetime_to_str(datetime_now()), + 'fsid': 'foobar', + }) + cephadm_module.notify('mon_map', None) + + CephadmServe(cephadm_module)._check_daemons() + + evs = [e.message for e in cephadm_module.events.get_for_daemon( + f'rgw.{daemon_id}')] + + assert 'myerror' in ''.join(evs) + + @pytest.mark.parametrize( + "action", + [ + 'start', + 'stop', + 'restart', + 'reconfig', + 'redeploy' + ] + ) + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) + def test_daemon_check(self, cephadm_module: CephadmOrchestrator, action): + with with_host(cephadm_module, 'test'): + with with_service(cephadm_module, ServiceSpec(service_type='grafana'), CephadmOrchestrator.apply_grafana, 'test') as d_names: + [daemon_name] = d_names + + cephadm_module._schedule_daemon_action(daemon_name, action) + + assert cephadm_module.cache.get_scheduled_daemon_action( + 'test', daemon_name) == action + + CephadmServe(cephadm_module)._check_daemons() + + assert cephadm_module.cache.get_scheduled_daemon_action('test', daemon_name) is None + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm") + def test_daemon_check_extra_config(self, _run_cephadm, cephadm_module: CephadmOrchestrator): + _run_cephadm.return_value = ('{}', '', 0) + + with with_host(cephadm_module, 'test'): + + # Also testing deploying mons without explicit network placement + cephadm_module.check_mon_command({ + 'prefix': 'config set', + 'who': 'mon', + 'name': 'public_network', + 'value': '127.0.0.0/8' + }) + + cephadm_module.cache.update_host_devices_networks( + 'test', + [], + { + "127.0.0.0/8": [ + "127.0.0.1" + ], + } + ) + + with with_service(cephadm_module, ServiceSpec(service_type='mon'), CephadmOrchestrator.apply_mon, 'test') as d_names: + [daemon_name] = d_names + + cephadm_module._set_extra_ceph_conf('[mon]\nk=v') + + CephadmServe(cephadm_module)._check_daemons() + + _run_cephadm.assert_called_with( + 'test', 'mon.test', 'deploy', [ + '--name', 'mon.test', + '--meta-json', '{"service_name": "mon", "ports": [], "ip": null, "deployed_by": [], "rank": null, "rank_generation": null, "extra_container_args": null}', + '--config-json', '-', + '--reconfig', + ], + stdin='{"config": "\\n\\n[mon]\\nk=v\\n[mon.test]\\npublic network = 127.0.0.0/8\\n", ' + + '"keyring": "", "files": {"config": "[mon.test]\\npublic network = 127.0.0.0/8\\n"}}', + image='') + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm") + def test_extra_container_args(self, _run_cephadm, cephadm_module: CephadmOrchestrator): + _run_cephadm.return_value = ('{}', '', 0) + with with_host(cephadm_module, 'test'): + with with_service(cephadm_module, ServiceSpec(service_type='crash', extra_container_args=['--cpus=2', '--quiet']), CephadmOrchestrator.apply_crash): + _run_cephadm.assert_called_with( + 'test', 'crash.test', 'deploy', [ + '--name', 'crash.test', + '--meta-json', '{"service_name": "crash", "ports": [], "ip": null, "deployed_by": [], "rank": null, "rank_generation": null, "extra_container_args": ["--cpus=2", "--quiet"]}', + '--config-json', '-', + '--extra-container-args=--cpus=2', + '--extra-container-args=--quiet' + ], + stdin='{"config": "", "keyring": ""}', + image='', + ) + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) + def test_daemon_check_post(self, cephadm_module: CephadmOrchestrator): + with with_host(cephadm_module, 'test'): + with with_service(cephadm_module, ServiceSpec(service_type='grafana'), CephadmOrchestrator.apply_grafana, 'test'): + + # Make sure, _check_daemons does a redeploy due to monmap change: + cephadm_module.mock_store_set('_ceph_get', 'mon_map', { + 'modified': datetime_to_str(datetime_now()), + 'fsid': 'foobar', + }) + cephadm_module.notify('mon_map', None) + cephadm_module.mock_store_set('_ceph_get', 'mgr_map', { + 'modules': ['dashboard'] + }) + + with mock.patch("cephadm.module.CephadmOrchestrator.mon_command") as _mon_cmd: + CephadmServe(cephadm_module)._check_daemons() + _mon_cmd.assert_any_call( + {'prefix': 'dashboard set-grafana-api-url', 'value': 'https://[1::4]:3000'}, + None) + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) + @mock.patch("cephadm.module.CephadmOrchestrator.get_mgr_ip", lambda _: '1.2.3.4') + def test_iscsi_post_actions_with_missing_daemon_in_cache(self, cephadm_module: CephadmOrchestrator): + # https://tracker.ceph.com/issues/52866 + with with_host(cephadm_module, 'test1'): + with with_host(cephadm_module, 'test2'): + with with_service(cephadm_module, IscsiServiceSpec(service_id='foobar', pool='pool', placement=PlacementSpec(host_pattern='*')), CephadmOrchestrator.apply_iscsi, 'test'): + + CephadmServe(cephadm_module)._apply_all_services() + assert len(cephadm_module.cache.get_daemons_by_type('iscsi')) == 2 + + # get a deamons from postaction list (ARRGH sets!!) + tempset = cephadm_module.requires_post_actions.copy() + tempdeamon1 = tempset.pop() + tempdeamon2 = tempset.pop() + + # make sure post actions has 2 daemons in it + assert len(cephadm_module.requires_post_actions) == 2 + + # replicate a host cache that is not in sync when check_daemons is called + tempdd1 = cephadm_module.cache.get_daemon(tempdeamon1) + tempdd2 = cephadm_module.cache.get_daemon(tempdeamon2) + host = 'test1' + if 'test1' not in tempdeamon1: + host = 'test2' + cephadm_module.cache.rm_daemon(host, tempdeamon1) + + # Make sure, _check_daemons does a redeploy due to monmap change: + cephadm_module.mock_store_set('_ceph_get', 'mon_map', { + 'modified': datetime_to_str(datetime_now()), + 'fsid': 'foobar', + }) + cephadm_module.notify('mon_map', None) + cephadm_module.mock_store_set('_ceph_get', 'mgr_map', { + 'modules': ['dashboard'] + }) + + with mock.patch("cephadm.module.IscsiService.config_dashboard") as _cfg_db: + CephadmServe(cephadm_module)._check_daemons() + _cfg_db.assert_called_once_with([tempdd2]) + + # post actions still has the other deamon in it and will run next _check_deamons + assert len(cephadm_module.requires_post_actions) == 1 + + # post actions was missed for a daemon + assert tempdeamon1 in cephadm_module.requires_post_actions + + # put the daemon back in the cache + cephadm_module.cache.add_daemon(host, tempdd1) + + _cfg_db.reset_mock() + # replicate serve loop running again + CephadmServe(cephadm_module)._check_daemons() + + # post actions should have been called again + _cfg_db.asset_called() + + # post actions is now empty + assert len(cephadm_module.requires_post_actions) == 0 + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('[]')) + def test_mon_add(self, cephadm_module): + with with_host(cephadm_module, 'test'): + with with_service(cephadm_module, ServiceSpec(service_type='mon', unmanaged=True)): + ps = PlacementSpec(hosts=['test:0.0.0.0=a'], count=1) + c = cephadm_module.add_daemon(ServiceSpec('mon', placement=ps)) + assert wait(cephadm_module, c) == ["Deployed mon.a on host 'test'"] + + with pytest.raises(OrchestratorError, match="Must set public_network config option or specify a CIDR network,"): + ps = PlacementSpec(hosts=['test'], count=1) + c = cephadm_module.add_daemon(ServiceSpec('mon', placement=ps)) + wait(cephadm_module, c) + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('[]')) + def test_mgr_update(self, cephadm_module): + with with_host(cephadm_module, 'test'): + ps = PlacementSpec(hosts=['test:0.0.0.0=a'], count=1) + r = CephadmServe(cephadm_module)._apply_service(ServiceSpec('mgr', placement=ps)) + assert r + + assert_rm_daemon(cephadm_module, 'mgr.a', 'test') + + @mock.patch("cephadm.module.CephadmOrchestrator.mon_command") + def test_find_destroyed_osds(self, _mon_cmd, cephadm_module): + dict_out = { + "nodes": [ + { + "id": -1, + "name": "default", + "type": "root", + "type_id": 11, + "children": [ + -3 + ] + }, + { + "id": -3, + "name": "host1", + "type": "host", + "type_id": 1, + "pool_weights": {}, + "children": [ + 0 + ] + }, + { + "id": 0, + "device_class": "hdd", + "name": "osd.0", + "type": "osd", + "type_id": 0, + "crush_weight": 0.0243988037109375, + "depth": 2, + "pool_weights": {}, + "exists": 1, + "status": "destroyed", + "reweight": 1, + "primary_affinity": 1 + } + ], + "stray": [] + } + json_out = json.dumps(dict_out) + _mon_cmd.return_value = (0, json_out, '') + osd_claims = OsdIdClaims(cephadm_module) + assert osd_claims.get() == {'host1': ['0']} + assert osd_claims.filtered_by_host('host1') == ['0'] + assert osd_claims.filtered_by_host('host1.domain.com') == ['0'] + + @ pytest.mark.parametrize( + "ceph_services, cephadm_daemons, strays_expected, metadata", + # [ ([(daemon_type, daemon_id), ... ], [...], [...]), ... ] + [ + ( + [('mds', 'a'), ('osd', '0'), ('mgr', 'x')], + [], + [('mds', 'a'), ('osd', '0'), ('mgr', 'x')], + {}, + ), + ( + [('mds', 'a'), ('osd', '0'), ('mgr', 'x')], + [('mds', 'a'), ('osd', '0'), ('mgr', 'x')], + [], + {}, + ), + ( + [('mds', 'a'), ('osd', '0'), ('mgr', 'x')], + [('mds', 'a'), ('osd', '0')], + [('mgr', 'x')], + {}, + ), + # https://tracker.ceph.com/issues/49573 + ( + [('rgw-nfs', '14649')], + [], + [('nfs', 'foo-rgw.host1')], + {'14649': {'id': 'nfs.foo-rgw.host1-rgw'}}, + ), + ( + [('rgw-nfs', '14649'), ('rgw-nfs', '14650')], + [('nfs', 'foo-rgw.host1'), ('nfs', 'foo2.host2')], + [], + {'14649': {'id': 'nfs.foo-rgw.host1-rgw'}, '14650': {'id': 'nfs.foo2.host2-rgw'}}, + ), + ( + [('rgw-nfs', '14649'), ('rgw-nfs', '14650')], + [('nfs', 'foo-rgw.host1')], + [('nfs', 'foo2.host2')], + {'14649': {'id': 'nfs.foo-rgw.host1-rgw'}, '14650': {'id': 'nfs.foo2.host2-rgw'}}, + ), + ] + ) + def test_check_for_stray_daemons( + self, + cephadm_module, + ceph_services, + cephadm_daemons, + strays_expected, + metadata + ): + # mock ceph service-map + services = [] + for service in ceph_services: + s = {'type': service[0], 'id': service[1]} + services.append(s) + ls = [{'hostname': 'host1', 'services': services}] + + with mock.patch.object(cephadm_module, 'list_servers', mock.MagicMock()) as list_servers: + list_servers.return_value = ls + list_servers.__iter__.side_effect = ls.__iter__ + + # populate cephadm daemon cache + dm = {} + for daemon_type, daemon_id in cephadm_daemons: + dd = DaemonDescription(daemon_type=daemon_type, daemon_id=daemon_id) + dm[dd.name()] = dd + cephadm_module.cache.update_host_daemons('host1', dm) + + def get_metadata_mock(svc_type, svc_id, default): + return metadata[svc_id] + + with mock.patch.object(cephadm_module, 'get_metadata', new_callable=lambda: get_metadata_mock): + + # test + CephadmServe(cephadm_module)._check_for_strays() + + # verify + strays = cephadm_module.health_checks.get('CEPHADM_STRAY_DAEMON') + if not strays: + assert len(strays_expected) == 0 + else: + for dt, di in strays_expected: + name = '%s.%s' % (dt, di) + for detail in strays['detail']: + if name in detail: + strays['detail'].remove(detail) + break + assert name in detail + assert len(strays['detail']) == 0 + assert strays['count'] == len(strays_expected) + + @mock.patch("cephadm.module.CephadmOrchestrator.mon_command") + def test_find_destroyed_osds_cmd_failure(self, _mon_cmd, cephadm_module): + _mon_cmd.return_value = (1, "", "fail_msg") + with pytest.raises(OrchestratorError): + OsdIdClaims(cephadm_module) + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm") + def test_apply_osd_save(self, _run_cephadm, cephadm_module: CephadmOrchestrator): + _run_cephadm.return_value = ('{}', '', 0) + with with_host(cephadm_module, 'test'): + + spec = DriveGroupSpec( + service_id='foo', + placement=PlacementSpec( + host_pattern='*', + ), + data_devices=DeviceSelection( + all=True + ) + ) + + c = cephadm_module.apply([spec]) + assert wait(cephadm_module, c) == ['Scheduled osd.foo update...'] + + inventory = Devices([ + Device( + '/dev/sdb', + available=True + ), + ]) + + cephadm_module.cache.update_host_devices_networks('test', inventory.devices, {}) + + _run_cephadm.return_value = (['{}'], '', 0) + + assert CephadmServe(cephadm_module)._apply_all_services() is False + + _run_cephadm.assert_any_call( + 'test', 'osd', 'ceph-volume', + ['--config-json', '-', '--', 'lvm', 'batch', + '--no-auto', '/dev/sdb', '--yes', '--no-systemd'], + env_vars=['CEPH_VOLUME_OSDSPEC_AFFINITY=foo'], error_ok=True, stdin='{"config": "", "keyring": ""}') + _run_cephadm.assert_any_call( + 'test', 'osd', 'ceph-volume', ['--', 'lvm', 'list', '--format', 'json'], image='', no_fsid=False) + _run_cephadm.assert_any_call( + 'test', 'osd', 'ceph-volume', ['--', 'raw', 'list', '--format', 'json'], image='', no_fsid=False) + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm") + def test_apply_osd_save_non_collocated(self, _run_cephadm, cephadm_module: CephadmOrchestrator): + _run_cephadm.return_value = ('{}', '', 0) + with with_host(cephadm_module, 'test'): + + spec = DriveGroupSpec( + service_id='noncollocated', + placement=PlacementSpec( + hosts=['test'] + ), + data_devices=DeviceSelection(paths=['/dev/sdb']), + db_devices=DeviceSelection(paths=['/dev/sdc']), + wal_devices=DeviceSelection(paths=['/dev/sdd']) + ) + + c = cephadm_module.apply([spec]) + assert wait(cephadm_module, c) == ['Scheduled osd.noncollocated update...'] + + inventory = Devices([ + Device('/dev/sdb', available=True), + Device('/dev/sdc', available=True), + Device('/dev/sdd', available=True) + ]) + + cephadm_module.cache.update_host_devices_networks('test', inventory.devices, {}) + + _run_cephadm.return_value = (['{}'], '', 0) + + assert CephadmServe(cephadm_module)._apply_all_services() is False + + _run_cephadm.assert_any_call( + 'test', 'osd', 'ceph-volume', + ['--config-json', '-', '--', 'lvm', 'batch', + '--no-auto', '/dev/sdb', '--db-devices', '/dev/sdc', + '--wal-devices', '/dev/sdd', '--yes', '--no-systemd'], + env_vars=['CEPH_VOLUME_OSDSPEC_AFFINITY=noncollocated'], + error_ok=True, stdin='{"config": "", "keyring": ""}') + _run_cephadm.assert_any_call( + 'test', 'osd', 'ceph-volume', ['--', 'lvm', 'list', '--format', 'json'], image='', no_fsid=False) + _run_cephadm.assert_any_call( + 'test', 'osd', 'ceph-volume', ['--', 'raw', 'list', '--format', 'json'], image='', no_fsid=False) + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) + @mock.patch("cephadm.module.SpecStore.save") + def test_apply_osd_save_placement(self, _save_spec, cephadm_module): + with with_host(cephadm_module, 'test'): + json_spec = {'service_type': 'osd', 'placement': {'host_pattern': 'test'}, + 'service_id': 'foo', 'data_devices': {'all': True}} + spec = ServiceSpec.from_json(json_spec) + assert isinstance(spec, DriveGroupSpec) + c = cephadm_module.apply([spec]) + assert wait(cephadm_module, c) == ['Scheduled osd.foo update...'] + _save_spec.assert_called_with(spec) + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) + def test_create_osds(self, cephadm_module): + with with_host(cephadm_module, 'test'): + dg = DriveGroupSpec(placement=PlacementSpec(host_pattern='test'), + data_devices=DeviceSelection(paths=[''])) + c = cephadm_module.create_osds(dg) + out = wait(cephadm_module, c) + assert out == "Created no osd(s) on host test; already created?" + bad_dg = DriveGroupSpec(placement=PlacementSpec(host_pattern='invalid_hsot'), + data_devices=DeviceSelection(paths=[''])) + c = cephadm_module.create_osds(bad_dg) + out = wait(cephadm_module, c) + assert "Invalid 'host:device' spec: host not found in cluster" in out + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) + def test_create_noncollocated_osd(self, cephadm_module): + with with_host(cephadm_module, 'test'): + dg = DriveGroupSpec(placement=PlacementSpec(host_pattern='test'), + data_devices=DeviceSelection(paths=[''])) + c = cephadm_module.create_osds(dg) + out = wait(cephadm_module, c) + assert out == "Created no osd(s) on host test; already created?" + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) + @mock.patch('cephadm.services.osd.OSDService._run_ceph_volume_command') + @mock.patch('cephadm.services.osd.OSDService.driveselection_to_ceph_volume') + @mock.patch('cephadm.services.osd.OsdIdClaims.refresh', lambda _: None) + @mock.patch('cephadm.services.osd.OsdIdClaims.get', lambda _: {}) + def test_limit_not_reached(self, d_to_cv, _run_cv_cmd, cephadm_module): + with with_host(cephadm_module, 'test'): + dg = DriveGroupSpec(placement=PlacementSpec(host_pattern='test'), + data_devices=DeviceSelection(limit=5, rotational=1), + service_id='not_enough') + + disks_found = [ + '[{"data": "/dev/vdb", "data_size": "50.00 GB", "encryption": "None"}, {"data": "/dev/vdc", "data_size": "50.00 GB", "encryption": "None"}]'] + d_to_cv.return_value = 'foo' + _run_cv_cmd.return_value = (disks_found, '', 0) + preview = cephadm_module.osd_service.generate_previews([dg], 'test') + + for osd in preview: + assert 'notes' in osd + assert osd['notes'] == [ + 'NOTE: Did not find enough disks matching filter on host test to reach data device limit (Found: 2 | Limit: 5)'] + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) + def test_prepare_drivegroup(self, cephadm_module): + with with_host(cephadm_module, 'test'): + dg = DriveGroupSpec(placement=PlacementSpec(host_pattern='test'), + data_devices=DeviceSelection(paths=[''])) + out = cephadm_module.osd_service.prepare_drivegroup(dg) + assert len(out) == 1 + f1 = out[0] + assert f1[0] == 'test' + assert isinstance(f1[1], DriveSelection) + + @pytest.mark.parametrize( + "devices, preview, exp_commands", + [ + # no preview and only one disk, prepare is used due the hack that is in place. + (['/dev/sda'], False, ["lvm batch --no-auto /dev/sda --yes --no-systemd"]), + # no preview and multiple disks, uses batch + (['/dev/sda', '/dev/sdb'], False, + ["CEPH_VOLUME_OSDSPEC_AFFINITY=test.spec lvm batch --no-auto /dev/sda /dev/sdb --yes --no-systemd"]), + # preview and only one disk needs to use batch again to generate the preview + (['/dev/sda'], True, ["lvm batch --no-auto /dev/sda --yes --no-systemd --report --format json"]), + # preview and multiple disks work the same + (['/dev/sda', '/dev/sdb'], True, + ["CEPH_VOLUME_OSDSPEC_AFFINITY=test.spec lvm batch --no-auto /dev/sda /dev/sdb --yes --no-systemd --report --format json"]), + ] + ) + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) + def test_driveselection_to_ceph_volume(self, cephadm_module, devices, preview, exp_commands): + with with_host(cephadm_module, 'test'): + dg = DriveGroupSpec(service_id='test.spec', placement=PlacementSpec( + host_pattern='test'), data_devices=DeviceSelection(paths=devices)) + ds = DriveSelection(dg, Devices([Device(path) for path in devices])) + preview = preview + out = cephadm_module.osd_service.driveselection_to_ceph_volume(ds, [], preview) + assert all(any(cmd in exp_cmd for exp_cmd in exp_commands) for cmd in out), f'Expected cmds from f{out} in {exp_commands}' + + @pytest.mark.parametrize( + "devices, preview, exp_commands", + [ + # one data device, no preview + (['/dev/sda'], False, ["raw prepare --bluestore --data /dev/sda"]), + # multiple data devices, no preview + (['/dev/sda', '/dev/sdb'], False, + ["raw prepare --bluestore --data /dev/sda", "raw prepare --bluestore --data /dev/sdb"]), + # one data device, preview + (['/dev/sda'], True, ["raw prepare --bluestore --data /dev/sda --report --format json"]), + # multiple data devices, preview + (['/dev/sda', '/dev/sdb'], True, + ["raw prepare --bluestore --data /dev/sda --report --format json", "raw prepare --bluestore --data /dev/sdb --report --format json"]), + ] + ) + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) + def test_raw_driveselection_to_ceph_volume(self, cephadm_module, devices, preview, exp_commands): + with with_host(cephadm_module, 'test'): + dg = DriveGroupSpec(service_id='test.spec', method='raw', placement=PlacementSpec( + host_pattern='test'), data_devices=DeviceSelection(paths=devices)) + ds = DriveSelection(dg, Devices([Device(path) for path in devices])) + preview = preview + out = cephadm_module.osd_service.driveselection_to_ceph_volume(ds, [], preview) + assert all(any(cmd in exp_cmd for exp_cmd in exp_commands) for cmd in out), f'Expected cmds from f{out} in {exp_commands}' + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm( + json.dumps([ + dict( + name='osd.0', + style='cephadm', + fsid='fsid', + container_id='container_id', + version='version', + state='running', + ) + ]) + )) + @mock.patch("cephadm.services.osd.OSD.exists", True) + @mock.patch("cephadm.services.osd.RemoveUtil.get_pg_count", lambda _, __: 0) + def test_remove_osds(self, cephadm_module): + with with_host(cephadm_module, 'test'): + CephadmServe(cephadm_module)._refresh_host_daemons('test') + c = cephadm_module.list_daemons() + wait(cephadm_module, c) + + c = cephadm_module.remove_daemons(['osd.0']) + out = wait(cephadm_module, c) + assert out == ["Removed osd.0 from host 'test'"] + + cephadm_module.to_remove_osds.enqueue(OSD(osd_id=0, + replace=False, + force=False, + hostname='test', + process_started_at=datetime_now(), + remove_util=cephadm_module.to_remove_osds.rm_util + )) + cephadm_module.to_remove_osds.process_removal_queue() + assert cephadm_module.to_remove_osds == OSDRemovalQueue(cephadm_module) + + c = cephadm_module.remove_osds_status() + out = wait(cephadm_module, c) + assert out == [] + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) + def test_rgw_update(self, cephadm_module): + with with_host(cephadm_module, 'host1'): + with with_host(cephadm_module, 'host2'): + with with_service(cephadm_module, RGWSpec(service_id="foo", unmanaged=True)): + ps = PlacementSpec(hosts=['host1'], count=1) + c = cephadm_module.add_daemon( + RGWSpec(service_id="foo", placement=ps)) + [out] = wait(cephadm_module, c) + match_glob(out, "Deployed rgw.foo.* on host 'host1'") + + ps = PlacementSpec(hosts=['host1', 'host2'], count=2) + r = CephadmServe(cephadm_module)._apply_service( + RGWSpec(service_id="foo", placement=ps)) + assert r + + assert_rm_daemon(cephadm_module, 'rgw.foo', 'host1') + assert_rm_daemon(cephadm_module, 'rgw.foo', 'host2') + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm( + json.dumps([ + dict( + name='rgw.myrgw.myhost.myid', + style='cephadm', + fsid='fsid', + container_id='container_id', + version='version', + state='running', + ) + ]) + )) + def test_remove_daemon(self, cephadm_module): + with with_host(cephadm_module, 'test'): + CephadmServe(cephadm_module)._refresh_host_daemons('test') + c = cephadm_module.list_daemons() + wait(cephadm_module, c) + c = cephadm_module.remove_daemons(['rgw.myrgw.myhost.myid']) + out = wait(cephadm_module, c) + assert out == ["Removed rgw.myrgw.myhost.myid from host 'test'"] + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm") + def test_remove_duplicate_osds(self, _run_cephadm, cephadm_module: CephadmOrchestrator): + _run_cephadm.return_value = ('{}', '', 0) + with with_host(cephadm_module, 'host1'): + with with_host(cephadm_module, 'host2'): + with with_osd_daemon(cephadm_module, _run_cephadm, 'host1', 1) as dd1: # type: DaemonDescription + with with_osd_daemon(cephadm_module, _run_cephadm, 'host2', 1) as dd2: # type: DaemonDescription + CephadmServe(cephadm_module)._check_for_moved_osds() + # both are in status "starting" + assert len(cephadm_module.cache.get_daemons()) == 2 + + dd1.status = DaemonDescriptionStatus.running + dd2.status = DaemonDescriptionStatus.error + cephadm_module.cache.update_host_daemons(dd1.hostname, {dd1.name(): dd1}) + cephadm_module.cache.update_host_daemons(dd2.hostname, {dd2.name(): dd2}) + CephadmServe(cephadm_module)._check_for_moved_osds() + assert len(cephadm_module.cache.get_daemons()) == 1 + + assert cephadm_module.events.get_for_daemon('osd.1') == [ + OrchestratorEvent(mock.ANY, 'daemon', 'osd.1', 'INFO', + "Deployed osd.1 on host 'host1'"), + OrchestratorEvent(mock.ANY, 'daemon', 'osd.1', 'INFO', + "Deployed osd.1 on host 'host2'"), + OrchestratorEvent(mock.ANY, 'daemon', 'osd.1', 'INFO', + "Removed duplicated daemon on host 'host2'"), + ] + + with pytest.raises(AssertionError): + cephadm_module.assert_issued_mon_command({ + 'prefix': 'auth rm', + 'entity': 'osd.1', + }) + + cephadm_module.assert_issued_mon_command({ + 'prefix': 'auth rm', + 'entity': 'osd.1', + }) + + @pytest.mark.parametrize( + "spec", + [ + ServiceSpec('crash'), + ServiceSpec('prometheus'), + ServiceSpec('grafana'), + ServiceSpec('node-exporter'), + ServiceSpec('alertmanager'), + ServiceSpec('rbd-mirror'), + ServiceSpec('cephfs-mirror'), + ServiceSpec('mds', service_id='fsname'), + RGWSpec(rgw_realm='realm', rgw_zone='zone'), + RGWSpec(service_id="foo"), + ServiceSpec('cephadm-exporter'), + ] + ) + @mock.patch("cephadm.serve.CephadmServe._deploy_cephadm_binary", _deploy_cephadm_binary('test')) + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) + def test_daemon_add(self, spec: ServiceSpec, cephadm_module): + unmanaged_spec = ServiceSpec.from_json(spec.to_json()) + unmanaged_spec.unmanaged = True + with with_host(cephadm_module, 'test'): + with with_service(cephadm_module, unmanaged_spec): + with with_daemon(cephadm_module, spec, 'test'): + pass + + @pytest.mark.parametrize( + "entity,success,spec", + [ + ('mgr.x', True, ServiceSpec( + service_type='mgr', + placement=PlacementSpec(hosts=[HostPlacementSpec('test', '', 'x')], count=1), + unmanaged=True) + ), # noqa: E124 + ('client.rgw.x', True, ServiceSpec( + service_type='rgw', + service_id='id', + placement=PlacementSpec(hosts=[HostPlacementSpec('test', '', 'x')], count=1), + unmanaged=True) + ), # noqa: E124 + ('client.nfs.x', True, ServiceSpec( + service_type='nfs', + service_id='id', + placement=PlacementSpec(hosts=[HostPlacementSpec('test', '', 'x')], count=1), + unmanaged=True) + ), # noqa: E124 + ('mon.', False, ServiceSpec( + service_type='mon', + placement=PlacementSpec( + hosts=[HostPlacementSpec('test', '127.0.0.0/24', 'x')], count=1), + unmanaged=True) + ), # noqa: E124 + ] + ) + @mock.patch("cephadm.serve.CephadmServe._run_cephadm") + @mock.patch("cephadm.services.nfs.NFSService.run_grace_tool", mock.MagicMock()) + @mock.patch("cephadm.services.nfs.NFSService.purge", mock.MagicMock()) + @mock.patch("cephadm.services.nfs.NFSService.create_rados_config_obj", mock.MagicMock()) + def test_daemon_add_fail(self, _run_cephadm, entity, success, spec, cephadm_module): + _run_cephadm.return_value = '{}', '', 0 + with with_host(cephadm_module, 'test'): + with with_service(cephadm_module, spec): + _run_cephadm.side_effect = OrchestratorError('fail') + with pytest.raises(OrchestratorError): + wait(cephadm_module, cephadm_module.add_daemon(spec)) + if success: + cephadm_module.assert_issued_mon_command({ + 'prefix': 'auth rm', + 'entity': entity, + }) + else: + with pytest.raises(AssertionError): + cephadm_module.assert_issued_mon_command({ + 'prefix': 'auth rm', + 'entity': entity, + }) + assert cephadm_module.events.get_for_service(spec.service_name()) == [ + OrchestratorEvent(mock.ANY, 'service', spec.service_name(), 'INFO', + "service was created"), + OrchestratorEvent(mock.ANY, 'service', spec.service_name(), 'ERROR', + "fail"), + ] + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm") + def test_daemon_place_fail_health_warning(self, _run_cephadm, cephadm_module): + _run_cephadm.return_value = ('{}', '', 0) + with with_host(cephadm_module, 'test'): + _run_cephadm.side_effect = OrchestratorError('fail') + ps = PlacementSpec(hosts=['test:0.0.0.0=a'], count=1) + r = CephadmServe(cephadm_module)._apply_service(ServiceSpec('mgr', placement=ps)) + assert not r + assert cephadm_module.health_checks.get('CEPHADM_DAEMON_PLACE_FAIL') is not None + assert cephadm_module.health_checks['CEPHADM_DAEMON_PLACE_FAIL']['count'] == 1 + assert 'Failed to place 1 daemon(s)' in cephadm_module.health_checks['CEPHADM_DAEMON_PLACE_FAIL']['summary'] + assert 'Failed while placing mgr.a on test: fail' in cephadm_module.health_checks['CEPHADM_DAEMON_PLACE_FAIL']['detail'] + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm") + def test_apply_spec_fail_health_warning(self, _run_cephadm, cephadm_module: CephadmOrchestrator): + _run_cephadm.return_value = ('{}', '', 0) + with with_host(cephadm_module, 'test'): + CephadmServe(cephadm_module)._apply_all_services() + ps = PlacementSpec(hosts=['fail'], count=1) + r = CephadmServe(cephadm_module)._apply_service(ServiceSpec('mgr', placement=ps)) + assert not r + assert cephadm_module.apply_spec_fails + assert cephadm_module.health_checks.get('CEPHADM_APPLY_SPEC_FAIL') is not None + assert cephadm_module.health_checks['CEPHADM_APPLY_SPEC_FAIL']['count'] == 1 + assert 'Failed to apply 1 service(s)' in cephadm_module.health_checks['CEPHADM_APPLY_SPEC_FAIL']['summary'] + + @mock.patch("cephadm.module.CephadmOrchestrator.get_foreign_ceph_option") + @mock.patch("cephadm.serve.CephadmServe._run_cephadm") + def test_invalid_config_option_health_warning(self, _run_cephadm, get_foreign_ceph_option, cephadm_module: CephadmOrchestrator): + _run_cephadm.return_value = ('{}', '', 0) + with with_host(cephadm_module, 'test'): + ps = PlacementSpec(hosts=['test:0.0.0.0=a'], count=1) + get_foreign_ceph_option.side_effect = KeyError + CephadmServe(cephadm_module)._apply_service_config( + ServiceSpec('mgr', placement=ps, config={'test': 'foo'})) + assert cephadm_module.health_checks.get('CEPHADM_INVALID_CONFIG_OPTION') is not None + assert cephadm_module.health_checks['CEPHADM_INVALID_CONFIG_OPTION']['count'] == 1 + assert 'Ignoring 1 invalid config option(s)' in cephadm_module.health_checks[ + 'CEPHADM_INVALID_CONFIG_OPTION']['summary'] + assert 'Ignoring invalid mgr config option test' in cephadm_module.health_checks[ + 'CEPHADM_INVALID_CONFIG_OPTION']['detail'] + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) + @mock.patch("cephadm.services.nfs.NFSService.run_grace_tool", mock.MagicMock()) + @mock.patch("cephadm.services.nfs.NFSService.purge", mock.MagicMock()) + @mock.patch("cephadm.services.nfs.NFSService.create_rados_config_obj", mock.MagicMock()) + def test_nfs(self, cephadm_module): + with with_host(cephadm_module, 'test'): + ps = PlacementSpec(hosts=['test'], count=1) + spec = NFSServiceSpec( + service_id='name', + placement=ps) + unmanaged_spec = ServiceSpec.from_json(spec.to_json()) + unmanaged_spec.unmanaged = True + with with_service(cephadm_module, unmanaged_spec): + c = cephadm_module.add_daemon(spec) + [out] = wait(cephadm_module, c) + match_glob(out, "Deployed nfs.name.* on host 'test'") + + assert_rm_daemon(cephadm_module, 'nfs.name.test', 'test') + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) + @mock.patch("subprocess.run", None) + @mock.patch("cephadm.module.CephadmOrchestrator.rados", mock.MagicMock()) + @mock.patch("cephadm.module.CephadmOrchestrator.get_mgr_ip", lambda _: '1::4') + def test_iscsi(self, cephadm_module): + with with_host(cephadm_module, 'test'): + ps = PlacementSpec(hosts=['test'], count=1) + spec = IscsiServiceSpec( + service_id='name', + pool='pool', + api_user='user', + api_password='password', + placement=ps) + unmanaged_spec = ServiceSpec.from_json(spec.to_json()) + unmanaged_spec.unmanaged = True + with with_service(cephadm_module, unmanaged_spec): + + c = cephadm_module.add_daemon(spec) + [out] = wait(cephadm_module, c) + match_glob(out, "Deployed iscsi.name.* on host 'test'") + + assert_rm_daemon(cephadm_module, 'iscsi.name.test', 'test') + + @pytest.mark.parametrize( + "on_bool", + [ + True, + False + ] + ) + @pytest.mark.parametrize( + "fault_ident", + [ + 'fault', + 'ident' + ] + ) + @mock.patch("cephadm.serve.CephadmServe._run_cephadm") + def test_blink_device_light(self, _run_cephadm, on_bool, fault_ident, cephadm_module): + _run_cephadm.return_value = '{}', '', 0 + with with_host(cephadm_module, 'test'): + c = cephadm_module.blink_device_light(fault_ident, on_bool, [('test', '', 'dev')]) + on_off = 'on' if on_bool else 'off' + assert wait(cephadm_module, c) == [f'Set {fault_ident} light for test: {on_off}'] + _run_cephadm.assert_called_with('test', 'osd', 'shell', [ + '--', 'lsmcli', f'local-disk-{fault_ident}-led-{on_off}', '--path', 'dev'], error_ok=True) + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm") + def test_blink_device_light_custom(self, _run_cephadm, cephadm_module): + _run_cephadm.return_value = '{}', '', 0 + with with_host(cephadm_module, 'test'): + cephadm_module.set_store('blink_device_light_cmd', 'echo hello') + c = cephadm_module.blink_device_light('ident', True, [('test', '', '/dev/sda')]) + assert wait(cephadm_module, c) == ['Set ident light for test: on'] + _run_cephadm.assert_called_with('test', 'osd', 'shell', [ + '--', 'echo', 'hello'], error_ok=True) + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm") + def test_blink_device_light_custom_per_host(self, _run_cephadm, cephadm_module): + _run_cephadm.return_value = '{}', '', 0 + with with_host(cephadm_module, 'mgr0'): + cephadm_module.set_store('mgr0/blink_device_light_cmd', + 'xyz --foo --{{ ident_fault }}={{\'on\' if on else \'off\'}} \'{{ path or dev }}\'') + c = cephadm_module.blink_device_light( + 'fault', True, [('mgr0', 'SanDisk_X400_M.2_2280_512GB_162924424784', '')]) + assert wait(cephadm_module, c) == [ + 'Set fault light for mgr0:SanDisk_X400_M.2_2280_512GB_162924424784 on'] + _run_cephadm.assert_called_with('mgr0', 'osd', 'shell', [ + '--', 'xyz', '--foo', '--fault=on', 'SanDisk_X400_M.2_2280_512GB_162924424784' + ], error_ok=True) + + @pytest.mark.parametrize( + "spec, meth", + [ + (ServiceSpec('mgr'), CephadmOrchestrator.apply_mgr), + (ServiceSpec('crash'), CephadmOrchestrator.apply_crash), + (ServiceSpec('prometheus'), CephadmOrchestrator.apply_prometheus), + (ServiceSpec('grafana'), CephadmOrchestrator.apply_grafana), + (ServiceSpec('node-exporter'), CephadmOrchestrator.apply_node_exporter), + (ServiceSpec('alertmanager'), CephadmOrchestrator.apply_alertmanager), + (ServiceSpec('rbd-mirror'), CephadmOrchestrator.apply_rbd_mirror), + (ServiceSpec('cephfs-mirror'), CephadmOrchestrator.apply_rbd_mirror), + (ServiceSpec('mds', service_id='fsname'), CephadmOrchestrator.apply_mds), + (ServiceSpec( + 'mds', service_id='fsname', + placement=PlacementSpec( + hosts=[HostPlacementSpec( + hostname='test', + name='fsname', + network='' + )] + ) + ), CephadmOrchestrator.apply_mds), + (RGWSpec(service_id='foo'), CephadmOrchestrator.apply_rgw), + (RGWSpec( + service_id='bar', + rgw_realm='realm', rgw_zone='zone', + placement=PlacementSpec( + hosts=[HostPlacementSpec( + hostname='test', + name='bar', + network='' + )] + ) + ), CephadmOrchestrator.apply_rgw), + (NFSServiceSpec( + service_id='name', + ), CephadmOrchestrator.apply_nfs), + (IscsiServiceSpec( + service_id='name', + pool='pool', + api_user='user', + api_password='password' + ), CephadmOrchestrator.apply_iscsi), + (CustomContainerSpec( + service_id='hello-world', + image='docker.io/library/hello-world:latest', + uid=65534, + gid=65534, + dirs=['foo/bar'], + files={ + 'foo/bar/xyz.conf': 'aaa\nbbb' + }, + bind_mounts=[[ + 'type=bind', + 'source=lib/modules', + 'destination=/lib/modules', + 'ro=true' + ]], + volume_mounts={ + 'foo/bar': '/foo/bar:Z' + }, + args=['--no-healthcheck'], + envs=['SECRET=password'], + ports=[8080, 8443] + ), CephadmOrchestrator.apply_container), + (ServiceSpec('cephadm-exporter'), CephadmOrchestrator.apply_cephadm_exporter), + ] + ) + @mock.patch("cephadm.serve.CephadmServe._deploy_cephadm_binary", _deploy_cephadm_binary('test')) + @mock.patch("subprocess.run", None) + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) + @mock.patch("cephadm.services.nfs.NFSService.run_grace_tool", mock.MagicMock()) + @mock.patch("cephadm.services.nfs.NFSService.create_rados_config_obj", mock.MagicMock()) + @mock.patch("cephadm.services.nfs.NFSService.purge", mock.MagicMock()) + @mock.patch("subprocess.run", mock.MagicMock()) + def test_apply_save(self, spec: ServiceSpec, meth, cephadm_module: CephadmOrchestrator): + with with_host(cephadm_module, 'test'): + with with_service(cephadm_module, spec, meth, 'test'): + pass + + @mock.patch("cephadm.serve.CephadmServe._deploy_cephadm_binary", _deploy_cephadm_binary('test')) + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) + def test_mds_config_purge(self, cephadm_module: CephadmOrchestrator): + spec = MDSSpec('mds', service_id='fsname', config={'test': 'foo'}) + with with_host(cephadm_module, 'test'): + with with_service(cephadm_module, spec, host='test'): + ret, out, err = cephadm_module.check_mon_command({ + 'prefix': 'config get', + 'who': spec.service_name(), + 'key': 'mds_join_fs', + }) + assert out == 'fsname' + ret, out, err = cephadm_module.check_mon_command({ + 'prefix': 'config get', + 'who': spec.service_name(), + 'key': 'mds_join_fs', + }) + assert not out + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) + @mock.patch("cephadm.services.cephadmservice.CephadmService.ok_to_stop") + def test_daemon_ok_to_stop(self, ok_to_stop, cephadm_module: CephadmOrchestrator): + spec = MDSSpec( + 'mds', + service_id='fsname', + placement=PlacementSpec(hosts=['host1', 'host2']), + config={'test': 'foo'} + ) + with with_host(cephadm_module, 'host1'), with_host(cephadm_module, 'host2'): + c = cephadm_module.apply_mds(spec) + out = wait(cephadm_module, c) + match_glob(out, "Scheduled mds.fsname update...") + CephadmServe(cephadm_module)._apply_all_services() + + [daemon] = cephadm_module.cache.daemons['host1'].keys() + + spec.placement.set_hosts(['host2']) + + ok_to_stop.side_effect = False + + c = cephadm_module.apply_mds(spec) + out = wait(cephadm_module, c) + match_glob(out, "Scheduled mds.fsname update...") + CephadmServe(cephadm_module)._apply_all_services() + + ok_to_stop.assert_called_with([daemon[4:]], force=True) + + assert_rm_daemon(cephadm_module, spec.service_name(), 'host1') # verifies ok-to-stop + assert_rm_daemon(cephadm_module, spec.service_name(), 'host2') + + @mock.patch("cephadm.module.CephadmOrchestrator._get_connection") + @mock.patch("remoto.process.check") + def test_offline(self, _check, _get_connection, cephadm_module): + _check.return_value = '{}', '', 0 + _get_connection.return_value = mock.Mock(), mock.Mock() + with with_host(cephadm_module, 'test'): + _get_connection.side_effect = HostNotFound + code, out, err = cephadm_module.check_host('test') + assert out == '' + assert "Host 'test' not found" in err + + out = wait(cephadm_module, cephadm_module.get_hosts())[0].to_json() + assert out == HostSpec('test', '1::4', status='Offline').to_json() + + _get_connection.side_effect = None + assert CephadmServe(cephadm_module)._check_host('test') is None + out = wait(cephadm_module, cephadm_module.get_hosts())[0].to_json() + assert out == HostSpec('test', '1::4').to_json() + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) + def test_dont_touch_offline_or_maintenance_host_daemons(self, cephadm_module): + # test daemons on offline/maint hosts not removed when applying specs + # test daemons not added to hosts in maint/offline state + with with_host(cephadm_module, 'test1'): + with with_host(cephadm_module, 'test2'): + with with_host(cephadm_module, 'test3'): + with with_service(cephadm_module, ServiceSpec('mgr', placement=PlacementSpec(host_pattern='*'))): + # should get a mgr on all 3 hosts + # CephadmServe(cephadm_module)._apply_all_services() + assert len(cephadm_module.cache.get_daemons_by_type('mgr')) == 3 + + # put one host in offline state and one host in maintenance state + cephadm_module.offline_hosts = {'test2'} + cephadm_module.inventory._inventory['test3']['status'] = 'maintenance' + cephadm_module.inventory.save() + + # being in offline/maint mode should disqualify hosts from being + # candidates for scheduling + candidates = [ + h.hostname for h in cephadm_module._schedulable_hosts()] + assert 'test2' in candidates + assert 'test3' in candidates + + unreachable = [h.hostname for h in cephadm_module._unreachable_hosts()] + assert 'test2' in unreachable + assert 'test3' in unreachable + + with with_service(cephadm_module, ServiceSpec('crash', placement=PlacementSpec(host_pattern='*'))): + # re-apply services. No mgr should be removed from maint/offline hosts + # crash daemon should only be on host not in maint/offline mode + CephadmServe(cephadm_module)._apply_all_services() + assert len(cephadm_module.cache.get_daemons_by_type('mgr')) == 3 + assert len(cephadm_module.cache.get_daemons_by_type('crash')) == 1 + + cephadm_module.offline_hosts = {} + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm") + @mock.patch("cephadm.CephadmOrchestrator._host_ok_to_stop") + @mock.patch("cephadm.module.HostCache.get_daemon_types") + @mock.patch("cephadm.module.HostCache.get_hosts") + def test_maintenance_enter_success(self, _hosts, _get_daemon_types, _host_ok, _run_cephadm, cephadm_module: CephadmOrchestrator): + hostname = 'host1' + _run_cephadm.return_value = [''], ['something\nsuccess - systemd target xxx disabled'], 0 + _host_ok.return_value = 0, 'it is okay' + _get_daemon_types.return_value = ['crash'] + _hosts.return_value = [hostname, 'other_host'] + cephadm_module.inventory.add_host(HostSpec(hostname)) + # should not raise an error + retval = cephadm_module.enter_host_maintenance(hostname) + assert retval.result_str().startswith('Daemons for Ceph cluster') + assert not retval.exception_str + assert cephadm_module.inventory._inventory[hostname]['status'] == 'maintenance' + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm") + @mock.patch("cephadm.CephadmOrchestrator._host_ok_to_stop") + @mock.patch("cephadm.module.HostCache.get_daemon_types") + @mock.patch("cephadm.module.HostCache.get_hosts") + def test_maintenance_enter_failure(self, _hosts, _get_daemon_types, _host_ok, _run_cephadm, cephadm_module: CephadmOrchestrator): + hostname = 'host1' + _run_cephadm.return_value = [''], ['something\nfailed - disable the target'], 0 + _host_ok.return_value = 0, 'it is okay' + _get_daemon_types.return_value = ['crash'] + _hosts.return_value = [hostname, 'other_host'] + cephadm_module.inventory.add_host(HostSpec(hostname)) + + with pytest.raises(OrchestratorError, match='Failed to place host1 into maintenance for cluster fsid'): + cephadm_module.enter_host_maintenance(hostname) + + assert not cephadm_module.inventory._inventory[hostname]['status'] + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm") + @mock.patch("cephadm.module.HostCache.get_daemon_types") + @mock.patch("cephadm.module.HostCache.get_hosts") + def test_maintenance_exit_success(self, _hosts, _get_daemon_types, _run_cephadm, cephadm_module: CephadmOrchestrator): + hostname = 'host1' + _run_cephadm.return_value = [''], [ + 'something\nsuccess - systemd target xxx enabled and started'], 0 + _get_daemon_types.return_value = ['crash'] + _hosts.return_value = [hostname, 'other_host'] + cephadm_module.inventory.add_host(HostSpec(hostname, status='maintenance')) + # should not raise an error + retval = cephadm_module.exit_host_maintenance(hostname) + assert retval.result_str().startswith('Ceph cluster') + assert not retval.exception_str + assert not cephadm_module.inventory._inventory[hostname]['status'] + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm") + @mock.patch("cephadm.module.HostCache.get_daemon_types") + @mock.patch("cephadm.module.HostCache.get_hosts") + def test_maintenance_exit_failure(self, _hosts, _get_daemon_types, _run_cephadm, cephadm_module: CephadmOrchestrator): + hostname = 'host1' + _run_cephadm.return_value = [''], ['something\nfailed - unable to enable the target'], 0 + _get_daemon_types.return_value = ['crash'] + _hosts.return_value = [hostname, 'other_host'] + cephadm_module.inventory.add_host(HostSpec(hostname, status='maintenance')) + + with pytest.raises(OrchestratorError, match='Failed to exit maintenance state for host host1, cluster fsid'): + cephadm_module.exit_host_maintenance(hostname) + + assert cephadm_module.inventory._inventory[hostname]['status'] == 'maintenance' + + def test_stale_connections(self, cephadm_module): + class Connection(object): + """ + A mocked connection class that only allows the use of the connection + once. If you attempt to use it again via a _check, it'll explode (go + boom!). + + The old code triggers the boom. The new code checks the has_connection + and will recreate the connection. + """ + fuse = False + + @ staticmethod + def has_connection(): + return False + + def import_module(self, *args, **kargs): + return mock.Mock() + + @ staticmethod + def exit(): + pass + + def _check(conn, *args, **kargs): + if conn.fuse: + raise Exception("boom: connection is dead") + else: + conn.fuse = True + return '{}', [], 0 + with mock.patch("remoto.Connection", side_effect=[Connection(), Connection(), Connection()]): + with mock.patch("remoto.process.check", _check): + with with_host(cephadm_module, 'test', refresh_hosts=False): + code, out, err = cephadm_module.check_host('test') + # First should succeed. + assert err == '' + + # On second it should attempt to reuse the connection, where the + # connection is "down" so will recreate the connection. The old + # code will blow up here triggering the BOOM! + code, out, err = cephadm_module.check_host('test') + assert err == '' + + @mock.patch("cephadm.module.CephadmOrchestrator._get_connection") + @mock.patch("remoto.process.check") + @mock.patch("cephadm.module.CephadmServe._write_remote_file") + def test_etc_ceph(self, _write_file, _check, _get_connection, cephadm_module): + _get_connection.return_value = mock.Mock(), mock.Mock() + _check.return_value = '{}', '', 0 + _write_file.return_value = None + + assert cephadm_module.manage_etc_ceph_ceph_conf is False + + with with_host(cephadm_module, 'test'): + assert '/etc/ceph/ceph.conf' not in cephadm_module.cache.get_host_client_files('test') + + with with_host(cephadm_module, 'test'): + cephadm_module.set_module_option('manage_etc_ceph_ceph_conf', True) + cephadm_module.config_notify() + assert cephadm_module.manage_etc_ceph_ceph_conf is True + + CephadmServe(cephadm_module)._refresh_hosts_and_daemons() + _write_file.assert_called_with('test', '/etc/ceph/ceph.conf', b'', + 0o644, 0, 0) + + assert '/etc/ceph/ceph.conf' in cephadm_module.cache.get_host_client_files('test') + + # set extra config and expect that we deploy another ceph.conf + cephadm_module._set_extra_ceph_conf('[mon]\nk=v') + CephadmServe(cephadm_module)._refresh_hosts_and_daemons() + _write_file.assert_called_with('test', '/etc/ceph/ceph.conf', + b'\n\n[mon]\nk=v\n', 0o644, 0, 0) + + # reload + cephadm_module.cache.last_client_files = {} + cephadm_module.cache.load() + + assert '/etc/ceph/ceph.conf' in cephadm_module.cache.get_host_client_files('test') + + # Make sure, _check_daemons does a redeploy due to monmap change: + before_digest = cephadm_module.cache.get_host_client_files('test')[ + '/etc/ceph/ceph.conf'][0] + cephadm_module._set_extra_ceph_conf('[mon]\nk2=v2') + CephadmServe(cephadm_module)._refresh_hosts_and_daemons() + after_digest = cephadm_module.cache.get_host_client_files('test')[ + '/etc/ceph/ceph.conf'][0] + assert before_digest != after_digest + + def test_etc_ceph_init(self): + with with_cephadm_module({'manage_etc_ceph_ceph_conf': True}) as m: + assert m.manage_etc_ceph_ceph_conf is True + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm") + def test_registry_login(self, _run_cephadm, cephadm_module: CephadmOrchestrator): + def check_registry_credentials(url, username, password): + assert json.loads(cephadm_module.get_store('registry_credentials')) == { + 'url': url, 'username': username, 'password': password} + + _run_cephadm.return_value = '{}', '', 0 + with with_host(cephadm_module, 'test'): + # test successful login with valid args + code, out, err = cephadm_module.registry_login('test-url', 'test-user', 'test-password') + assert out == 'registry login scheduled' + assert err == '' + check_registry_credentials('test-url', 'test-user', 'test-password') + + # test bad login attempt with invalid args + code, out, err = cephadm_module.registry_login('bad-args') + assert err == ("Invalid arguments. Please provide arguments " + "or -i ") + check_registry_credentials('test-url', 'test-user', 'test-password') + + # test bad login using invalid json file + code, out, err = cephadm_module.registry_login( + None, None, None, '{"bad-json": "bad-json"}') + assert err == ("json provided for custom registry login did not include all necessary fields. " + "Please setup json file as\n" + "{\n" + " \"url\": \"REGISTRY_URL\",\n" + " \"username\": \"REGISTRY_USERNAME\",\n" + " \"password\": \"REGISTRY_PASSWORD\"\n" + "}\n") + check_registry_credentials('test-url', 'test-user', 'test-password') + + # test good login using valid json file + good_json = ("{\"url\": \"" + "json-url" + "\", \"username\": \"" + "json-user" + "\", " + " \"password\": \"" + "json-pass" + "\"}") + code, out, err = cephadm_module.registry_login(None, None, None, good_json) + assert out == 'registry login scheduled' + assert err == '' + check_registry_credentials('json-url', 'json-user', 'json-pass') + + # test bad login where args are valid but login command fails + _run_cephadm.return_value = '{}', 'error', 1 + code, out, err = cephadm_module.registry_login('fail-url', 'fail-user', 'fail-password') + assert err == 'Host test failed to login to fail-url as fail-user with given password' + check_registry_credentials('json-url', 'json-user', 'json-pass') + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm(json.dumps({ + 'image_id': 'image_id', + 'repo_digests': ['image@repo_digest'], + }))) + @pytest.mark.parametrize("use_repo_digest", + [ + False, + True + ]) + def test_upgrade_run(self, use_repo_digest, cephadm_module: CephadmOrchestrator): + cephadm_module.use_repo_digest = use_repo_digest + + with with_host(cephadm_module, 'test', refresh_hosts=False): + cephadm_module.set_container_image('global', 'image') + + if use_repo_digest: + + CephadmServe(cephadm_module).convert_tags_to_repo_digest() + + _, image, _ = cephadm_module.check_mon_command({ + 'prefix': 'config get', + 'who': 'global', + 'key': 'container_image', + }) + if use_repo_digest: + assert image == 'image@repo_digest' + else: + assert image == 'image' + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm") + def test_ceph_volume_no_filter_for_batch(self, _run_cephadm, cephadm_module: CephadmOrchestrator): + _run_cephadm.return_value = ('{}', '', 0) + + error_message = """cephadm exited with an error code: 1, stderr:/usr/bin/podman:stderr usage: ceph-volume inventory [-h] [--format {plain,json,json-pretty}] [path]/usr/bin/podman:stderr ceph-volume inventory: error: unrecognized arguments: --filter-for-batch +Traceback (most recent call last): + File "", line 6112, in + File "", line 1299, in _infer_fsid + File "", line 1382, in _infer_image + File "", line 3612, in command_ceph_volume + File "", line 1061, in call_throws""" + + with with_host(cephadm_module, 'test'): + _run_cephadm.reset_mock() + _run_cephadm.side_effect = OrchestratorError(error_message) + + s = CephadmServe(cephadm_module)._refresh_host_devices('test') + assert s == 'host test `cephadm ceph-volume` failed: ' + error_message + + assert _run_cephadm.mock_calls == [ + mock.call('test', 'osd', 'ceph-volume', + ['--', 'inventory', '--format=json-pretty', '--filter-for-batch'], image='', + no_fsid=False), + mock.call('test', 'osd', 'ceph-volume', + ['--', 'inventory', '--format=json-pretty'], image='', + no_fsid=False), + ] + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm") + def test_osd_activate_datadevice(self, _run_cephadm, cephadm_module: CephadmOrchestrator): + _run_cephadm.return_value = ('{}', '', 0) + with with_host(cephadm_module, 'test', refresh_hosts=False): + with with_osd_daemon(cephadm_module, _run_cephadm, 'test', 1): + pass + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm") + def test_osd_activate_datadevice_fail(self, _run_cephadm, cephadm_module: CephadmOrchestrator): + _run_cephadm.return_value = ('{}', '', 0) + with with_host(cephadm_module, 'test', refresh_hosts=False): + cephadm_module.mock_store_set('_ceph_get', 'osd_map', { + 'osds': [ + { + 'osd': 1, + 'up_from': 0, + 'uuid': 'uuid' + } + ] + }) + + ceph_volume_lvm_list = { + '1': [{ + 'tags': { + 'ceph.cluster_fsid': cephadm_module._cluster_fsid, + 'ceph.osd_fsid': 'uuid' + }, + 'type': 'data' + }] + } + _run_cephadm.reset_mock(return_value=True) + + def _r_c(*args, **kwargs): + if 'ceph-volume' in args: + return (json.dumps(ceph_volume_lvm_list), '', 0) + else: + assert 'deploy' in args + raise OrchestratorError("let's fail somehow") + _run_cephadm.side_effect = _r_c + assert cephadm_module._osd_activate( + ['test']).stderr == "let's fail somehow" + with pytest.raises(AssertionError): + cephadm_module.assert_issued_mon_command({ + 'prefix': 'auth rm', + 'entity': 'osd.1', + }) + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm") + def test_osd_activate_datadevice_dbdevice(self, _run_cephadm, cephadm_module: CephadmOrchestrator): + _run_cephadm.return_value = ('{}', '', 0) + with with_host(cephadm_module, 'test', refresh_hosts=False): + + def _ceph_volume_list(s, host, entity, cmd, **kwargs): + logging.info(f'ceph-volume cmd: {cmd}') + if 'raw' in cmd: + return json.dumps({ + "21a4209b-f51b-4225-81dc-d2dca5b8b2f5": { + "ceph_fsid": "64c84f19-fe1d-452a-a731-ab19dc144aa8", + "device": "/dev/loop0", + "osd_id": 21, + "osd_uuid": "21a4209b-f51b-4225-81dc-d2dca5b8b2f5", + "type": "bluestore" + }, + }), '', 0 + if 'lvm' in cmd: + return json.dumps({ + '1': [{ + 'tags': { + 'ceph.cluster_fsid': cephadm_module._cluster_fsid, + 'ceph.osd_fsid': 'uuid' + }, + 'type': 'data' + }, { + 'tags': { + 'ceph.cluster_fsid': cephadm_module._cluster_fsid, + 'ceph.osd_fsid': 'uuid' + }, + 'type': 'db' + }] + }), '', 0 + return '{}', '', 0 + + with with_osd_daemon(cephadm_module, _run_cephadm, 'test', 1, ceph_volume_lvm_list=_ceph_volume_list): + pass + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm") + def test_osd_count(self, _run_cephadm, cephadm_module: CephadmOrchestrator): + _run_cephadm.return_value = ('{}', '', 0) + dg = DriveGroupSpec(service_id='', data_devices=DeviceSelection(all=True)) + with with_host(cephadm_module, 'test', refresh_hosts=False): + with with_service(cephadm_module, dg, host='test'): + with with_osd_daemon(cephadm_module, _run_cephadm, 'test', 1): + assert wait(cephadm_module, cephadm_module.describe_service())[0].size == 1 + + @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('[]')) + def test_host_rm_last_admin(self, cephadm_module: CephadmOrchestrator): + with pytest.raises(OrchestratorError): + with with_host(cephadm_module, 'test', refresh_hosts=False, rm_with_force=False): + cephadm_module.inventory.add_label('test', '_admin') + pass + assert False + with with_host(cephadm_module, 'test1', refresh_hosts=False, rm_with_force=True): + with with_host(cephadm_module, 'test2', refresh_hosts=False, rm_with_force=False): + cephadm_module.inventory.add_label('test2', '_admin') diff --git a/src/pybind/mgr/cephadm/tests/test_completion.py b/src/pybind/mgr/cephadm/tests/test_completion.py new file mode 100644 index 000000000..327c12d2a --- /dev/null +++ b/src/pybind/mgr/cephadm/tests/test_completion.py @@ -0,0 +1,40 @@ +import pytest + +from ..module import forall_hosts + + +class TestCompletion(object): + + @pytest.mark.parametrize("input,expected", [ + ([], []), + ([1], ["(1,)"]), + (["hallo"], ["('hallo',)"]), + ("hi", ["('h',)", "('i',)"]), + (list(range(5)), [str((x, )) for x in range(5)]), + ([(1, 2), (3, 4)], ["(1, 2)", "(3, 4)"]), + ]) + def test_async_map(self, input, expected, cephadm_module): + @forall_hosts + def run_forall(*args): + return str(args) + assert run_forall(input) == expected + + @pytest.mark.parametrize("input,expected", [ + ([], []), + ([1], ["(1,)"]), + (["hallo"], ["('hallo',)"]), + ("hi", ["('h',)", "('i',)"]), + (list(range(5)), [str((x, )) for x in range(5)]), + ([(1, 2), (3, 4)], ["(1, 2)", "(3, 4)"]), + ]) + def test_async_map_self(self, input, expected, cephadm_module): + class Run(object): + def __init__(self): + self.attr = 1 + + @forall_hosts + def run_forall(self, *args): + assert self.attr == 1 + return str(args) + + assert Run().run_forall(input) == expected diff --git a/src/pybind/mgr/cephadm/tests/test_configchecks.py b/src/pybind/mgr/cephadm/tests/test_configchecks.py new file mode 100644 index 000000000..3cae0a27d --- /dev/null +++ b/src/pybind/mgr/cephadm/tests/test_configchecks.py @@ -0,0 +1,668 @@ +import copy +import json +import logging +import ipaddress +import pytest +import uuid + +from time import time as now + +from ..configchecks import CephadmConfigChecks +from ..inventory import HostCache +from ..upgrade import CephadmUpgrade, UpgradeState +from orchestrator import DaemonDescription + +from typing import List, Dict, Any, Optional + +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + +host_sample = { + "arch": "x86_64", + "bios_date": "04/01/2014", + "bios_version": "F2", + "cpu_cores": 16, + "cpu_count": 2, + "cpu_load": { + "15min": 0.0, + "1min": 0.01, + "5min": 0.01 + }, + "cpu_model": "Intel® Xeon® Processor E5-2698 v3", + "cpu_threads": 64, + "flash_capacity": "4.0TB", + "flash_capacity_bytes": 4000797868032, + "flash_count": 2, + "flash_list": [ + { + "description": "ATA CT2000MX500SSD1 (2.0TB)", + "dev_name": "sda", + "disk_size_bytes": 2000398934016, + "model": "CT2000MX500SSD1", + "rev": "023", + "vendor": "ATA", + "wwid": "t10.ATA CT2000MX500SSD1 193023156DE0" + }, + { + "description": "ATA CT2000MX500SSD1 (2.0TB)", + "dev_name": "sdb", + "disk_size_bytes": 2000398934016, + "model": "CT2000MX500SSD1", + "rev": "023", + "vendor": "ATA", + "wwid": "t10.ATA CT2000MX500SSD1 193023156DE0" + }, + ], + "hdd_capacity": "16.0TB", + "hdd_capacity_bytes": 16003148120064, + "hdd_count": 4, + "hdd_list": [ + { + "description": "ST4000VN008-2DR1 (4.0TB)", + "dev_name": "sdc", + "disk_size_bytes": 4000787030016, + "model": "ST4000VN008-2DR1", + "rev": "SC60", + "vendor": "ATA", + "wwid": "t10.ATA ST4000VN008-2DR1 Z340EPBJ" + }, + { + "description": "ST4000VN008-2DR1 (4.0TB)", + "dev_name": "sdd", + "disk_size_bytes": 4000787030016, + "model": "ST4000VN008-2DR1", + "rev": "SC60", + "vendor": "ATA", + "wwid": "t10.ATA ST4000VN008-2DR1 Z340EPBJ" + }, + { + "description": "ST4000VN008-2DR1 (4.0TB)", + "dev_name": "sde", + "disk_size_bytes": 4000787030016, + "model": "ST4000VN008-2DR1", + "rev": "SC60", + "vendor": "ATA", + "wwid": "t10.ATA ST4000VN008-2DR1 Z340EPBJ" + }, + { + "description": "ST4000VN008-2DR1 (4.0TB)", + "dev_name": "sdf", + "disk_size_bytes": 4000787030016, + "model": "ST4000VN008-2DR1", + "rev": "SC60", + "vendor": "ATA", + "wwid": "t10.ATA ST4000VN008-2DR1 Z340EPBJ" + }, + ], + "hostname": "dummy", + "interfaces": { + "eth0": { + "driver": "e1000e", + "iftype": "physical", + "ipv4_address": "10.7.17.1/24", + "ipv6_address": "fe80::215:17ff:feab:50e2/64", + "lower_devs_list": [], + "mtu": 9000, + "nic_type": "ethernet", + "operstate": "up", + "speed": 1000, + "upper_devs_list": [], + }, + "eth1": { + "driver": "e1000e", + "iftype": "physical", + "ipv4_address": "10.7.18.1/24", + "ipv6_address": "fe80::215:17ff:feab:50e2/64", + "lower_devs_list": [], + "mtu": 9000, + "nic_type": "ethernet", + "operstate": "up", + "speed": 1000, + "upper_devs_list": [], + }, + "eth2": { + "driver": "r8169", + "iftype": "physical", + "ipv4_address": "10.7.19.1/24", + "ipv6_address": "fe80::76d4:35ff:fe58:9a79/64", + "lower_devs_list": [], + "mtu": 1500, + "nic_type": "ethernet", + "operstate": "up", + "speed": 1000, + "upper_devs_list": [] + }, + }, + "kernel": "4.18.0-240.10.1.el8_3.x86_64", + "kernel_parameters": { + "net.ipv4.ip_nonlocal_bind": "0", + }, + "kernel_security": { + "SELINUX": "enforcing", + "SELINUXTYPE": "targeted", + "description": "SELinux: Enabled(enforcing, targeted)", + "type": "SELinux" + }, + "memory_available_kb": 19489212, + "memory_free_kb": 245164, + "memory_total_kb": 32900916, + "model": "StorageHeavy", + "nic_count": 3, + "operating_system": "Red Hat Enterprise Linux 8.3 (Ootpa)", + "subscribed": "Yes", + "system_uptime": 777600.0, + "timestamp": now(), + "vendor": "Ceph Servers Inc", +} + + +def role_list(n: int) -> List[str]: + if n == 1: + return ['mon', 'mgr', 'osd'] + if n in [2, 3]: + return ['mon', 'mds', 'osd'] + + return ['osd'] + + +def generate_testdata(count: int = 10, public_network: str = '10.7.17.0/24', cluster_network: str = '10.7.18.0/24'): + # public network = eth0, cluster_network = eth1 + assert count > 3 + assert public_network + num_disks = host_sample['hdd_count'] + hosts = {} + daemons = {} + daemon_to_host = {} + osd_num = 0 + public_netmask = public_network.split('/')[1] + cluster_ip_list = [] + cluster_netmask = '' + + public_ip_list = [str(i) for i in list(ipaddress.ip_network(public_network).hosts())] + if cluster_network: + cluster_ip_list = [str(i) for i in list(ipaddress.ip_network(cluster_network).hosts())] + cluster_netmask = cluster_network.split('/')[1] + + for n in range(1, count + 1, 1): + + new_host = copy.deepcopy(host_sample) + hostname = f"node-{n}.ceph.com" + + new_host['hostname'] = hostname + new_host['interfaces']['eth0']['ipv4_address'] = f"{public_ip_list.pop(0)}/{public_netmask}" + if cluster_ip_list: + new_host['interfaces']['eth1']['ipv4_address'] = f"{cluster_ip_list.pop(0)}/{cluster_netmask}" + else: + new_host['interfaces']['eth1']['ipv4_address'] = '' + + hosts[hostname] = new_host + daemons[hostname] = {} + for r in role_list(n): + name = '' + if r == 'osd': + for n in range(num_disks): + osd = DaemonDescription( + hostname=hostname, daemon_type='osd', daemon_id=osd_num) + name = f"osd.{osd_num}" + daemons[hostname][name] = osd + daemon_to_host[name] = hostname + osd_num += 1 + else: + name = f"{r}.{hostname}" + daemons[hostname][name] = DaemonDescription( + hostname=hostname, daemon_type=r, daemon_id=hostname) + daemon_to_host[name] = hostname + + logger.debug(f"daemon to host lookup - {json.dumps(daemon_to_host)}") + return hosts, daemons, daemon_to_host + + +@pytest.fixture() +def mgr(): + """Provide a fake ceph mgr object preloaded with a configuration""" + mgr = FakeMgr() + mgr.cache.facts, mgr.cache.daemons, mgr.daemon_to_host = \ + generate_testdata(public_network='10.9.64.0/24', cluster_network='') + mgr.module_option.update({ + "config_checks_enabled": True, + }) + yield mgr + + +class FakeMgr: + + def __init__(self): + self.datastore = {} + self.module_option = {} + self.health_checks = {} + self.default_version = 'quincy' + self.version_overrides = {} + self.daemon_to_host = {} + + self.cache = HostCache(self) + self.upgrade = CephadmUpgrade(self) + + def set_health_checks(self, checks: dict): + return + + def get_module_option(self, keyname: str) -> Optional[str]: + return self.module_option.get(keyname, None) + + def set_module_option(self, keyname: str, value: str) -> None: + return None + + def get_store(self, keyname: str, default=None) -> Optional[str]: + return self.datastore.get(keyname, None) + + def set_store(self, keyname: str, value: str) -> None: + self.datastore[keyname] = value + return None + + def _ceph_get_server(self) -> None: + pass + + def get_metadata(self, daemon_type: str, daemon_id: str) -> Dict[str, Any]: + key = f"{daemon_type}.{daemon_id}" + if key in self.version_overrides: + logger.debug(f"override applied for {key}") + version_str = self.version_overrides[key] + else: + version_str = self.default_version + + return {"ceph_release": version_str, "hostname": self.daemon_to_host[key]} + + def list_servers(self) -> List[Dict[str, List[Dict[str, str]]]]: + num_disks = host_sample['hdd_count'] + osd_num = 0 + service_map = [] + + for hostname in self.cache.facts: + + host_num = int(hostname.split('.')[0].split('-')[1]) + svc_list = [] + for r in role_list(host_num): + if r == 'osd': + for _n in range(num_disks): + svc_list.append({ + "type": "osd", + "id": osd_num, + }) + osd_num += 1 + else: + svc_list.append({ + "type": r, + "id": hostname, + }) + + service_map.append({"services": svc_list}) + logger.debug(f"services map - {json.dumps(service_map)}") + return service_map + + def use_repo_digest(self) -> None: + return None + + +class TestConfigCheck: + + def test_to_json(self, mgr): + checker = CephadmConfigChecks(mgr) + out = checker.to_json() + assert out + assert len(out) == len(checker.health_checks) + + def test_lookup_check(self, mgr): + checker = CephadmConfigChecks(mgr) + check = checker.lookup_check('osd_mtu_size') + logger.debug(json.dumps(check.to_json())) + assert check + assert check.healthcheck_name == "CEPHADM_CHECK_MTU" + + def test_old_checks_removed(self, mgr): + mgr.datastore.update({ + "config_checks": '{"bogus_one": "enabled", "bogus_two": "enabled", ' + '"kernel_security": "enabled", "public_network": "enabled", ' + '"kernel_version": "enabled", "network_missing": "enabled", ' + '"osd_mtu_size": "enabled", "osd_linkspeed": "enabled", ' + '"os_subscription": "enabled", "ceph_release": "enabled"}' + }) + checker = CephadmConfigChecks(mgr) + raw = mgr.get_store('config_checks') + checks = json.loads(raw) + assert "bogus_one" not in checks + assert "bogus_two" not in checks + assert len(checks) == len(checker.health_checks) + + def test_new_checks(self, mgr): + mgr.datastore.update({ + "config_checks": '{"kernel_security": "enabled", "public_network": "enabled", ' + '"osd_mtu_size": "enabled", "osd_linkspeed": "enabled"}' + }) + checker = CephadmConfigChecks(mgr) + raw = mgr.get_store('config_checks') + checks = json.loads(raw) + assert len(checks) == len(checker.health_checks) + + def test_no_issues(self, mgr): + checker = CephadmConfigChecks(mgr) + checker.cluster_network_list = [] + checker.public_network_list = ['10.9.64.0/24'] + checker.run_checks() + + assert not mgr.health_checks + + def test_no_public_network(self, mgr): + bad_node = mgr.cache.facts['node-1.ceph.com'] + bad_node['interfaces']['eth0']['ipv4_address'] = "192.168.1.20/24" + checker = CephadmConfigChecks(mgr) + checker.cluster_network_list = [] + checker.public_network_list = ['10.9.64.0/24'] + checker.run_checks() + logger.debug(mgr.health_checks) + assert len(mgr.health_checks) == 1 + assert 'CEPHADM_CHECK_PUBLIC_MEMBERSHIP' in mgr.health_checks + assert mgr.health_checks['CEPHADM_CHECK_PUBLIC_MEMBERSHIP']['detail'][0] == \ + 'node-1.ceph.com does not have an interface on any public network' + + def test_missing_networks(self, mgr): + + checker = CephadmConfigChecks(mgr) + checker.cluster_network_list = [] + checker.public_network_list = ['10.9.66.0/24'] + checker.run_checks() + + logger.info(json.dumps(mgr.health_checks)) + logger.info(checker.subnet_lookup) + assert len(mgr.health_checks) == 1 + assert 'CEPHADM_CHECK_NETWORK_MISSING' in mgr.health_checks + assert mgr.health_checks['CEPHADM_CHECK_NETWORK_MISSING']['detail'][0] == \ + "10.9.66.0/24 not found on any host in the cluster" + + def test_bad_mtu_single(self, mgr): + + bad_node = mgr.cache.facts['node-1.ceph.com'] + bad_node['interfaces']['eth0']['mtu'] = 1500 + + checker = CephadmConfigChecks(mgr) + checker.cluster_network_list = [] + checker.public_network_list = ['10.9.64.0/24'] + + checker.run_checks() + logger.info(json.dumps(mgr.health_checks)) + logger.info(checker.subnet_lookup) + assert "CEPHADM_CHECK_MTU" in mgr.health_checks and len(mgr.health_checks) == 1 + assert mgr.health_checks['CEPHADM_CHECK_MTU']['detail'][0] == \ + 'host node-1.ceph.com(eth0) is using MTU 1500 on 10.9.64.0/24, NICs on other hosts use 9000' + + def test_bad_mtu_multiple(self, mgr): + + for n in [1, 5]: + bad_node = mgr.cache.facts[f'node-{n}.ceph.com'] + bad_node['interfaces']['eth0']['mtu'] = 1500 + + checker = CephadmConfigChecks(mgr) + checker.cluster_network_list = [] + checker.public_network_list = ['10.9.64.0/24'] + + checker.run_checks() + logger.info(json.dumps(mgr.health_checks)) + logger.info(checker.subnet_lookup) + assert "CEPHADM_CHECK_MTU" in mgr.health_checks and len(mgr.health_checks) == 1 + assert mgr.health_checks['CEPHADM_CHECK_MTU']['count'] == 2 + + def test_bad_linkspeed_single(self, mgr): + + bad_node = mgr.cache.facts['node-1.ceph.com'] + bad_node['interfaces']['eth0']['speed'] = 100 + + checker = CephadmConfigChecks(mgr) + checker.cluster_network_list = [] + checker.public_network_list = ['10.9.64.0/24'] + + checker.run_checks() + logger.info(json.dumps(mgr.health_checks)) + logger.info(checker.subnet_lookup) + assert mgr.health_checks + assert "CEPHADM_CHECK_LINKSPEED" in mgr.health_checks and len(mgr.health_checks) == 1 + assert mgr.health_checks['CEPHADM_CHECK_LINKSPEED']['detail'][0] == \ + 'host node-1.ceph.com(eth0) has linkspeed of 100 on 10.9.64.0/24, NICs on other hosts use 1000' + + def test_super_linkspeed_single(self, mgr): + + bad_node = mgr.cache.facts['node-1.ceph.com'] + bad_node['interfaces']['eth0']['speed'] = 10000 + + checker = CephadmConfigChecks(mgr) + checker.cluster_network_list = [] + checker.public_network_list = ['10.9.64.0/24'] + + checker.run_checks() + logger.info(json.dumps(mgr.health_checks)) + logger.info(checker.subnet_lookup) + assert not mgr.health_checks + + def test_release_mismatch_single(self, mgr): + + mgr.version_overrides = { + "osd.1": "pacific", + } + + checker = CephadmConfigChecks(mgr) + checker.cluster_network_list = [] + checker.public_network_list = ['10.9.64.0/24'] + + checker.run_checks() + logger.info(json.dumps(mgr.health_checks)) + assert mgr.health_checks + assert "CEPHADM_CHECK_CEPH_RELEASE" in mgr.health_checks and len(mgr.health_checks) == 1 + assert mgr.health_checks['CEPHADM_CHECK_CEPH_RELEASE']['detail'][0] == \ + 'osd.1 is running pacific (majority of cluster is using quincy)' + + def test_release_mismatch_multi(self, mgr): + + mgr.version_overrides = { + "osd.1": "pacific", + "osd.5": "octopus", + } + + checker = CephadmConfigChecks(mgr) + checker.cluster_network_list = [] + checker.public_network_list = ['10.9.64.0/24'] + + checker.run_checks() + logger.info(json.dumps(mgr.health_checks)) + assert mgr.health_checks + assert "CEPHADM_CHECK_CEPH_RELEASE" in mgr.health_checks and len(mgr.health_checks) == 1 + assert len(mgr.health_checks['CEPHADM_CHECK_CEPH_RELEASE']['detail']) == 2 + + def test_kernel_mismatch(self, mgr): + + bad_host = mgr.cache.facts['node-1.ceph.com'] + bad_host['kernel'] = "5.10.18.0-241.10.1.el8.x86_64" + + checker = CephadmConfigChecks(mgr) + checker.cluster_network_list = [] + checker.public_network_list = ['10.9.64.0/24'] + + checker.run_checks() + logger.info(json.dumps(mgr.health_checks)) + assert len(mgr.health_checks) == 1 + assert 'CEPHADM_CHECK_KERNEL_VERSION' in mgr.health_checks + assert mgr.health_checks['CEPHADM_CHECK_KERNEL_VERSION']['detail'][0] == \ + "host node-1.ceph.com running kernel 5.10, majority of hosts(9) running 4.18" + assert mgr.health_checks['CEPHADM_CHECK_KERNEL_VERSION']['count'] == 1 + + def test_inconsistent_subscription(self, mgr): + + bad_host = mgr.cache.facts['node-5.ceph.com'] + bad_host['subscribed'] = "no" + + checker = CephadmConfigChecks(mgr) + checker.cluster_network_list = [] + checker.public_network_list = ['10.9.64.0/24'] + + checker.run_checks() + logger.info(json.dumps(mgr.health_checks)) + assert len(mgr.health_checks) == 1 + assert "CEPHADM_CHECK_SUBSCRIPTION" in mgr.health_checks + assert mgr.health_checks['CEPHADM_CHECK_SUBSCRIPTION']['detail'][0] == \ + "node-5.ceph.com does not have an active subscription" + + def test_kernel_security_inconsistent(self, mgr): + + bad_node = mgr.cache.facts['node-3.ceph.com'] + bad_node['kernel_security'] = { + "SELINUX": "permissive", + "SELINUXTYPE": "targeted", + "description": "SELinux: Enabled(permissive, targeted)", + "type": "SELinux" + } + checker = CephadmConfigChecks(mgr) + checker.cluster_network_list = [] + checker.public_network_list = ['10.9.64.0/24'] + + checker.run_checks() + logger.info(json.dumps(mgr.health_checks)) + assert len(mgr.health_checks) == 1 + assert 'CEPHADM_CHECK_KERNEL_LSM' in mgr.health_checks + assert mgr.health_checks['CEPHADM_CHECK_KERNEL_LSM']['detail'][0] == \ + "node-3.ceph.com has inconsistent KSM settings compared to the majority of hosts(9) in the cluster" + + def test_release_and_bad_mtu(self, mgr): + + mgr.version_overrides = { + "osd.1": "pacific", + } + bad_node = mgr.cache.facts['node-1.ceph.com'] + bad_node['interfaces']['eth0']['mtu'] = 1500 + + checker = CephadmConfigChecks(mgr) + checker.cluster_network_list = [] + checker.public_network_list = ['10.9.64.0/24'] + + checker.run_checks() + logger.info(json.dumps(mgr.health_checks)) + logger.info(checker.subnet_lookup) + assert mgr.health_checks + assert len(mgr.health_checks) == 2 + assert "CEPHADM_CHECK_CEPH_RELEASE" in mgr.health_checks and \ + "CEPHADM_CHECK_MTU" in mgr.health_checks + + def test_release_mtu_LSM(self, mgr): + + mgr.version_overrides = { + "osd.1": "pacific", + } + bad_node1 = mgr.cache.facts['node-1.ceph.com'] + bad_node1['interfaces']['eth0']['mtu'] = 1500 + bad_node2 = mgr.cache.facts['node-3.ceph.com'] + bad_node2['kernel_security'] = { + "SELINUX": "permissive", + "SELINUXTYPE": "targeted", + "description": "SELinux: Enabled(permissive, targeted)", + "type": "SELinux" + } + checker = CephadmConfigChecks(mgr) + checker.cluster_network_list = [] + checker.public_network_list = ['10.9.64.0/24'] + + checker.run_checks() + logger.info(json.dumps(mgr.health_checks)) + logger.info(checker.subnet_lookup) + assert mgr.health_checks + assert len(mgr.health_checks) == 3 + assert \ + "CEPHADM_CHECK_CEPH_RELEASE" in mgr.health_checks and \ + "CEPHADM_CHECK_MTU" in mgr.health_checks and \ + "CEPHADM_CHECK_KERNEL_LSM" in mgr.health_checks + + def test_release_mtu_LSM_subscription(self, mgr): + + mgr.version_overrides = { + "osd.1": "pacific", + } + bad_node1 = mgr.cache.facts['node-1.ceph.com'] + bad_node1['interfaces']['eth0']['mtu'] = 1500 + bad_node1['subscribed'] = "no" + bad_node2 = mgr.cache.facts['node-3.ceph.com'] + bad_node2['kernel_security'] = { + "SELINUX": "permissive", + "SELINUXTYPE": "targeted", + "description": "SELinux: Enabled(permissive, targeted)", + "type": "SELinux" + } + checker = CephadmConfigChecks(mgr) + checker.cluster_network_list = [] + checker.public_network_list = ['10.9.64.0/24'] + + checker.run_checks() + logger.info(json.dumps(mgr.health_checks)) + logger.info(checker.subnet_lookup) + assert mgr.health_checks + assert len(mgr.health_checks) == 4 + assert \ + "CEPHADM_CHECK_CEPH_RELEASE" in mgr.health_checks and \ + "CEPHADM_CHECK_MTU" in mgr.health_checks and \ + "CEPHADM_CHECK_KERNEL_LSM" in mgr.health_checks and \ + "CEPHADM_CHECK_SUBSCRIPTION" in mgr.health_checks + + def test_skip_release_during_upgrade(self, mgr): + mgr.upgrade.upgrade_state = UpgradeState.from_json({ + 'target_name': 'wah', + 'progress_id': str(uuid.uuid4()), + 'target_id': 'wah', + 'error': '', + 'paused': False, + }) + checker = CephadmConfigChecks(mgr) + checker.cluster_network_list = [] + checker.public_network_list = ['10.9.64.0/24'] + + checker.run_checks() + logger.info(f"{checker.skipped_checks_count} skipped check(s): {checker.skipped_checks}") + assert checker.skipped_checks_count == 1 + assert 'ceph_release' in checker.skipped_checks + + def test_skip_when_disabled(self, mgr): + mgr.module_option.update({ + "config_checks_enabled": "false" + }) + checker = CephadmConfigChecks(mgr) + checker.cluster_network_list = [] + checker.public_network_list = ['10.9.64.0/24'] + + checker.run_checks() + logger.info(checker.active_checks) + logger.info(checker.defined_checks) + assert checker.active_checks_count == 0 + + def test_skip_mtu_checks(self, mgr): + mgr.datastore.update({ + 'config_checks': '{"osd_mtu_size": "disabled"}' + }) + + checker = CephadmConfigChecks(mgr) + checker.cluster_network_list = [] + checker.public_network_list = ['10.9.64.0/24'] + + checker.run_checks() + logger.info(checker.active_checks) + logger.info(checker.defined_checks) + assert 'osd_mtu_size' not in checker.active_checks + assert checker.defined_checks == 8 and checker.active_checks_count == 7 + + def test_skip_mtu_lsm_checks(self, mgr): + mgr.datastore.update({ + 'config_checks': '{"osd_mtu_size": "disabled", "kernel_security": "disabled"}' + }) + + checker = CephadmConfigChecks(mgr) + checker.cluster_network_list = [] + checker.public_network_list = ['10.9.64.0/24'] + + checker.run_checks() + logger.info(checker.active_checks) + logger.info(checker.defined_checks) + assert 'osd_mtu_size' not in checker.active_checks and \ + 'kernel_security' not in checker.active_checks + assert checker.defined_checks == 8 and checker.active_checks_count == 6 + assert not mgr.health_checks diff --git a/src/pybind/mgr/cephadm/tests/test_facts.py b/src/pybind/mgr/cephadm/tests/test_facts.py new file mode 100644 index 000000000..6c33f5368 --- /dev/null +++ b/src/pybind/mgr/cephadm/tests/test_facts.py @@ -0,0 +1,10 @@ +from ..import CephadmOrchestrator + +from .fixtures import wait + + +def test_facts(cephadm_module: CephadmOrchestrator): + facts = {'node-1.ceph.com': {'bios_version': 'F2', 'cpu_cores': 16}} + cephadm_module.cache.facts = facts + ret_facts = cephadm_module.get_facts('node-1.ceph.com') + assert wait(cephadm_module, ret_facts) == [{'bios_version': 'F2', 'cpu_cores': 16}] diff --git a/src/pybind/mgr/cephadm/tests/test_migration.py b/src/pybind/mgr/cephadm/tests/test_migration.py new file mode 100644 index 000000000..b95f54f7c --- /dev/null +++ b/src/pybind/mgr/cephadm/tests/test_migration.py @@ -0,0 +1,229 @@ +import json + +from ceph.deployment.service_spec import PlacementSpec, ServiceSpec, HostPlacementSpec +from ceph.utils import datetime_to_str, datetime_now +from cephadm import CephadmOrchestrator +from cephadm.inventory import SPEC_STORE_PREFIX +from cephadm.migrations import LAST_MIGRATION +from cephadm.tests.fixtures import _run_cephadm, wait, with_host +from cephadm.serve import CephadmServe +from tests import mock + + +@mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('[]')) +def test_migrate_scheduler(cephadm_module: CephadmOrchestrator): + with with_host(cephadm_module, 'host1', refresh_hosts=False): + with with_host(cephadm_module, 'host2', refresh_hosts=False): + + # emulate the old scheduler: + c = cephadm_module.apply_rgw( + ServiceSpec('rgw', 'r.z', placement=PlacementSpec(host_pattern='*', count=2)) + ) + assert wait(cephadm_module, c) == 'Scheduled rgw.r.z update...' + + # with pytest.raises(OrchestratorError, match="cephadm migration still ongoing. Please wait, until the migration is complete."): + CephadmServe(cephadm_module)._apply_all_services() + + cephadm_module.migration_current = 0 + cephadm_module.migration.migrate() + # assert we need all daemons. + assert cephadm_module.migration_current == 0 + + CephadmServe(cephadm_module)._refresh_hosts_and_daemons() + cephadm_module.migration.migrate() + + CephadmServe(cephadm_module)._apply_all_services() + + out = {o.hostname for o in wait(cephadm_module, cephadm_module.list_daemons())} + assert out == {'host1', 'host2'} + + c = cephadm_module.apply_rgw( + ServiceSpec('rgw', 'r.z', placement=PlacementSpec(host_pattern='host1', count=2)) + ) + assert wait(cephadm_module, c) == 'Scheduled rgw.r.z update...' + + # Sorry, for this hack, but I need to make sure, Migration thinks, + # we have updated all daemons already. + cephadm_module.cache.last_daemon_update['host1'] = datetime_now() + cephadm_module.cache.last_daemon_update['host2'] = datetime_now() + + cephadm_module.migration_current = 0 + cephadm_module.migration.migrate() + assert cephadm_module.migration_current >= 2 + + out = [o.spec.placement for o in wait( + cephadm_module, cephadm_module.describe_service())] + assert out == [PlacementSpec(count=2, hosts=[HostPlacementSpec( + hostname='host1', network='', name=''), HostPlacementSpec(hostname='host2', network='', name='')])] + + +@mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('[]')) +def test_migrate_service_id_mon_one(cephadm_module: CephadmOrchestrator): + with with_host(cephadm_module, 'host1'): + cephadm_module.set_store(SPEC_STORE_PREFIX + 'mon.wrong', json.dumps({ + 'spec': { + 'service_type': 'mon', + 'service_id': 'wrong', + 'placement': { + 'hosts': ['host1'] + } + }, + 'created': datetime_to_str(datetime_now()), + }, sort_keys=True), + ) + + cephadm_module.spec_store.load() + + assert len(cephadm_module.spec_store.all_specs) == 1 + assert cephadm_module.spec_store.all_specs['mon.wrong'].service_name() == 'mon' + + cephadm_module.migration_current = 1 + cephadm_module.migration.migrate() + assert cephadm_module.migration_current >= 2 + + assert len(cephadm_module.spec_store.all_specs) == 1 + assert cephadm_module.spec_store.all_specs['mon'] == ServiceSpec( + service_type='mon', + unmanaged=True, + placement=PlacementSpec(hosts=['host1']) + ) + + +@mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('[]')) +def test_migrate_service_id_mon_two(cephadm_module: CephadmOrchestrator): + with with_host(cephadm_module, 'host1'): + cephadm_module.set_store(SPEC_STORE_PREFIX + 'mon', json.dumps({ + 'spec': { + 'service_type': 'mon', + 'placement': { + 'count': 5, + } + }, + 'created': datetime_to_str(datetime_now()), + }, sort_keys=True), + ) + cephadm_module.set_store(SPEC_STORE_PREFIX + 'mon.wrong', json.dumps({ + 'spec': { + 'service_type': 'mon', + 'service_id': 'wrong', + 'placement': { + 'hosts': ['host1'] + } + }, + 'created': datetime_to_str(datetime_now()), + }, sort_keys=True), + ) + + cephadm_module.spec_store.load() + + assert len(cephadm_module.spec_store.all_specs) == 2 + assert cephadm_module.spec_store.all_specs['mon.wrong'].service_name() == 'mon' + assert cephadm_module.spec_store.all_specs['mon'].service_name() == 'mon' + + cephadm_module.migration_current = 1 + cephadm_module.migration.migrate() + assert cephadm_module.migration_current >= 2 + + assert len(cephadm_module.spec_store.all_specs) == 1 + assert cephadm_module.spec_store.all_specs['mon'] == ServiceSpec( + service_type='mon', + unmanaged=True, + placement=PlacementSpec(count=5) + ) + + +@mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('[]')) +def test_migrate_service_id_mds_one(cephadm_module: CephadmOrchestrator): + with with_host(cephadm_module, 'host1'): + cephadm_module.set_store(SPEC_STORE_PREFIX + 'mds', json.dumps({ + 'spec': { + 'service_type': 'mds', + 'placement': { + 'hosts': ['host1'] + } + }, + 'created': datetime_to_str(datetime_now()), + }, sort_keys=True), + ) + + cephadm_module.spec_store.load() + + # there is nothing to migrate, as the spec is gone now. + assert len(cephadm_module.spec_store.all_specs) == 0 + + +@mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('[]')) +def test_migrate_nfs_initial(cephadm_module: CephadmOrchestrator): + with with_host(cephadm_module, 'host1'): + cephadm_module.set_store( + SPEC_STORE_PREFIX + 'mds', + json.dumps({ + 'spec': { + 'service_type': 'nfs', + 'service_id': 'foo', + 'placement': { + 'hosts': ['host1'] + }, + 'spec': { + 'pool': 'mypool', + 'namespace': 'foons', + }, + }, + 'created': datetime_to_str(datetime_now()), + }, sort_keys=True), + ) + cephadm_module.migration_current = 1 + cephadm_module.spec_store.load() + + ls = json.loads(cephadm_module.get_store('nfs_migration_queue')) + assert ls == [['foo', 'mypool', 'foons']] + + cephadm_module.migration.migrate(True) + assert cephadm_module.migration_current == 2 + + cephadm_module.migration.migrate() + assert cephadm_module.migration_current == LAST_MIGRATION + + +@mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('[]')) +def test_migrate_nfs_initial_octopus(cephadm_module: CephadmOrchestrator): + with with_host(cephadm_module, 'host1'): + cephadm_module.set_store( + SPEC_STORE_PREFIX + 'mds', + json.dumps({ + 'spec': { + 'service_type': 'nfs', + 'service_id': 'ganesha-foo', + 'placement': { + 'hosts': ['host1'] + }, + 'spec': { + 'pool': 'mypool', + 'namespace': 'foons', + }, + }, + 'created': datetime_to_str(datetime_now()), + }, sort_keys=True), + ) + cephadm_module.migration_current = 1 + cephadm_module.spec_store.load() + + ls = json.loads(cephadm_module.get_store('nfs_migration_queue')) + assert ls == [['ganesha-foo', 'mypool', 'foons']] + + cephadm_module.migration.migrate(True) + assert cephadm_module.migration_current == 2 + + cephadm_module.migration.migrate() + assert cephadm_module.migration_current == LAST_MIGRATION + + +@mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('[]')) +def test_migrate_admin_client_keyring(cephadm_module: CephadmOrchestrator): + assert 'client.admin' not in cephadm_module.keys.keys + + cephadm_module.migration_current = 3 + cephadm_module.migration.migrate() + assert cephadm_module.migration_current == LAST_MIGRATION + + assert cephadm_module.keys.keys['client.admin'].placement.label == '_admin' diff --git a/src/pybind/mgr/cephadm/tests/test_osd_removal.py b/src/pybind/mgr/cephadm/tests/test_osd_removal.py new file mode 100644 index 000000000..6685fcb2a --- /dev/null +++ b/src/pybind/mgr/cephadm/tests/test_osd_removal.py @@ -0,0 +1,298 @@ +import json + +from cephadm.services.osd import OSDRemovalQueue, OSD +import pytest +from tests import mock +from .fixtures import with_cephadm_module +from datetime import datetime + + +class MockOSD: + + def __init__(self, osd_id): + self.osd_id = osd_id + + +class TestOSDRemoval: + + @pytest.mark.parametrize( + "osd_id, osd_df, expected", + [ + # missing 'nodes' key + (1, dict(nodes=[]), -1), + # missing 'pgs' key + (1, dict(nodes=[dict(id=1)]), -1), + # id != osd_id + (1, dict(nodes=[dict(id=999, pgs=1)]), -1), + # valid + (1, dict(nodes=[dict(id=1, pgs=1)]), 1), + ] + ) + def test_get_pg_count(self, rm_util, osd_id, osd_df, expected): + with mock.patch("cephadm.services.osd.RemoveUtil.osd_df", return_value=osd_df): + assert rm_util.get_pg_count(osd_id) == expected + + @pytest.mark.parametrize( + "osds, ok_to_stop, expected", + [ + # no osd_ids provided + ([], [False], []), + # all osds are ok_to_stop + ([1, 2], [True], [1, 2]), + # osds are ok_to_stop after the second iteration + ([1, 2], [False, True], [2]), + # osds are never ok_to_stop, (taking the sample size `(len(osd_ids))` into account), + # expected to get False + ([1, 2], [False, False], []), + ] + ) + def test_find_stop_threshold(self, rm_util, osds, ok_to_stop, expected): + with mock.patch("cephadm.services.osd.RemoveUtil.ok_to_stop", side_effect=ok_to_stop): + assert rm_util.find_osd_stop_threshold(osds) == expected + + def test_process_removal_queue(self, rm_util): + # TODO: ! + # rm_util.process_removal_queue() + pass + + @pytest.mark.parametrize( + "max_osd_draining_count, draining_osds, idling_osds, ok_to_stop, expected", + [ + # drain one at a time, one already draining + (1, [1], [1], [True], 0), + # drain one at a time, none draining yet + (1, [], [1, 2, 3], [True, True, True], 1), + # drain one at a time, one already draining, none ok-to-stop + (1, [1], [1], [False], 0), + # drain one at a time, none draining, one ok-to-stop + (1, [], [1, 2, 3], [False, False, True], 1), + # drain three at a time, one already draining, all ok-to-stop + (3, [1], [1, 2, 3], [True, True, True], 2), + # drain two at a time, none already draining, none ok-to-stop + (2, [], [1, 2, 3], [False, False, False], 0), + # drain two at a time, none already draining, none idling + (2, [], [], [], 0), + ] + ) + def test_ready_to_drain_osds(self, max_osd_draining_count, draining_osds, idling_osds, ok_to_stop, expected): + with with_cephadm_module({'max_osd_draining_count': max_osd_draining_count}) as m: + with mock.patch("cephadm.services.osd.OSDRemovalQueue.draining_osds", return_value=draining_osds): + with mock.patch("cephadm.services.osd.OSDRemovalQueue.idling_osds", return_value=idling_osds): + with mock.patch("cephadm.services.osd.RemoveUtil.ok_to_stop", side_effect=ok_to_stop): + removal_queue = OSDRemovalQueue(m) + assert len(removal_queue._ready_to_drain_osds()) == expected + + def test_ok_to_stop(self, rm_util): + rm_util.ok_to_stop([MockOSD(1)]) + rm_util._run_mon_cmd.assert_called_with({'prefix': 'osd ok-to-stop', 'ids': ['1']}, + error_ok=True) + + def test_safe_to_destroy(self, rm_util): + rm_util.safe_to_destroy([1]) + rm_util._run_mon_cmd.assert_called_with({'prefix': 'osd safe-to-destroy', + 'ids': ['1']}, error_ok=True) + + def test_destroy_osd(self, rm_util): + rm_util.destroy_osd(1) + rm_util._run_mon_cmd.assert_called_with( + {'prefix': 'osd destroy-actual', 'id': 1, 'yes_i_really_mean_it': True}) + + def test_purge_osd(self, rm_util): + rm_util.purge_osd(1) + rm_util._run_mon_cmd.assert_called_with( + {'prefix': 'osd purge-actual', 'id': 1, 'yes_i_really_mean_it': True}) + + def test_load(self, cephadm_module, rm_util): + data = json.dumps([ + { + "osd_id": 35, + "started": True, + "draining": True, + "stopped": False, + "replace": False, + "force": False, + "zap": False, + "nodename": "node2", + "drain_started_at": "2020-09-14T11:41:53.960463", + "drain_stopped_at": None, + "drain_done_at": None, + "process_started_at": "2020-09-14T11:41:52.245832" + } + ]) + cephadm_module.set_store('osd_remove_queue', data) + cephadm_module.to_remove_osds.load_from_store() + + expected = OSDRemovalQueue(cephadm_module) + expected.osds.add(OSD(osd_id=35, remove_util=rm_util, draining=True)) + assert cephadm_module.to_remove_osds == expected + + +class TestOSD: + + def test_start(self, osd_obj): + assert osd_obj.started is False + osd_obj.start() + assert osd_obj.started is True + assert osd_obj.stopped is False + + def test_start_draining_purge(self, osd_obj): + assert osd_obj.draining is False + assert osd_obj.drain_started_at is None + ret = osd_obj.start_draining() + osd_obj.rm_util.reweight_osd.assert_called_with(osd_obj, 0.0) + assert isinstance(osd_obj.drain_started_at, datetime) + assert osd_obj.draining is True + assert osd_obj.replace is False + assert ret is True + + def test_start_draining_replace(self, osd_obj): + assert osd_obj.draining is False + assert osd_obj.drain_started_at is None + osd_obj.replace = True + ret = osd_obj.start_draining() + osd_obj.rm_util.set_osd_flag.assert_called_with([osd_obj], 'out') + assert isinstance(osd_obj.drain_started_at, datetime) + assert osd_obj.draining is True + assert osd_obj.replace is True + assert ret is True + + def test_start_draining_stopped(self, osd_obj): + osd_obj.stopped = True + ret = osd_obj.start_draining() + assert osd_obj.drain_started_at is None + assert ret is False + assert osd_obj.draining is False + + def test_stop_draining_replace(self, osd_obj): + osd_obj.replace = True + ret = osd_obj.stop_draining() + osd_obj.rm_util.set_osd_flag.assert_called_with([osd_obj], 'in') + assert isinstance(osd_obj.drain_stopped_at, datetime) + assert osd_obj.draining is False + assert ret is True + + def test_stop_draining_purge(self, osd_obj): + osd_obj.original_weight = 1.0 + ret = osd_obj.stop_draining() + osd_obj.rm_util.reweight_osd.assert_called_with(osd_obj, 1.0) + assert isinstance(osd_obj.drain_stopped_at, datetime) + assert osd_obj.draining is False + assert ret is True + + @mock.patch('cephadm.services.osd.OSD.stop_draining') + def test_stop(self, stop_draining_mock, osd_obj): + osd_obj.stop() + assert osd_obj.started is False + assert osd_obj.stopped is True + stop_draining_mock.assert_called_once() + + @pytest.mark.parametrize( + "draining, empty, expected", + [ + # must be !draining! and !not empty! to yield True + (True, not True, True), + # not draining and not empty + (False, not True, False), + # not draining and empty + (False, True, False), + # draining and empty + (True, True, False), + ] + ) + def test_is_draining(self, osd_obj, draining, empty, expected): + with mock.patch("cephadm.services.osd.OSD.is_empty", new_callable=mock.PropertyMock(return_value=empty)): + osd_obj.draining = draining + assert osd_obj.is_draining is expected + + @mock.patch("cephadm.services.osd.RemoveUtil.ok_to_stop") + def test_is_ok_to_stop(self, _, osd_obj): + osd_obj.is_ok_to_stop + osd_obj.rm_util.ok_to_stop.assert_called_once() + + @pytest.mark.parametrize( + "pg_count, expected", + [ + (0, True), + (1, False), + (9999, False), + (-1, False), + ] + ) + def test_is_empty(self, osd_obj, pg_count, expected): + with mock.patch("cephadm.services.osd.OSD.get_pg_count", return_value=pg_count): + assert osd_obj.is_empty is expected + + @mock.patch("cephadm.services.osd.RemoveUtil.safe_to_destroy") + def test_safe_to_destroy(self, _, osd_obj): + osd_obj.safe_to_destroy() + osd_obj.rm_util.safe_to_destroy.assert_called_once() + + @mock.patch("cephadm.services.osd.RemoveUtil.set_osd_flag") + def test_down(self, _, osd_obj): + osd_obj.down() + osd_obj.rm_util.set_osd_flag.assert_called_with([osd_obj], 'down') + + @mock.patch("cephadm.services.osd.RemoveUtil.destroy_osd") + def test_destroy_osd(self, _, osd_obj): + osd_obj.destroy() + osd_obj.rm_util.destroy_osd.assert_called_once() + + @mock.patch("cephadm.services.osd.RemoveUtil.purge_osd") + def test_purge(self, _, osd_obj): + osd_obj.purge() + osd_obj.rm_util.purge_osd.assert_called_once() + + @mock.patch("cephadm.services.osd.RemoveUtil.get_pg_count") + def test_pg_count(self, _, osd_obj): + osd_obj.get_pg_count() + osd_obj.rm_util.get_pg_count.assert_called_once() + + def test_drain_status_human_not_started(self, osd_obj): + assert osd_obj.drain_status_human() == 'not started' + + def test_drain_status_human_started(self, osd_obj): + osd_obj.started = True + assert osd_obj.drain_status_human() == 'started' + + def test_drain_status_human_draining(self, osd_obj): + osd_obj.started = True + osd_obj.draining = True + assert osd_obj.drain_status_human() == 'draining' + + def test_drain_status_human_done(self, osd_obj): + osd_obj.started = True + osd_obj.draining = False + osd_obj.drain_done_at = datetime.utcnow() + assert osd_obj.drain_status_human() == 'done, waiting for purge' + + +class TestOSDRemovalQueue: + + def test_queue_size(self, osd_obj): + q = OSDRemovalQueue(mock.Mock()) + assert q.queue_size() == 0 + q.osds.add(osd_obj) + assert q.queue_size() == 1 + + @mock.patch("cephadm.services.osd.OSD.start") + @mock.patch("cephadm.services.osd.OSD.exists") + def test_enqueue(self, exist, start, osd_obj): + q = OSDRemovalQueue(mock.Mock()) + q.enqueue(osd_obj) + osd_obj.start.assert_called_once() + + @mock.patch("cephadm.services.osd.OSD.stop") + @mock.patch("cephadm.services.osd.OSD.exists") + def test_rm_raise(self, exist, stop, osd_obj): + q = OSDRemovalQueue(mock.Mock()) + with pytest.raises(KeyError): + q.rm(osd_obj) + osd_obj.stop.assert_called_once() + + @mock.patch("cephadm.services.osd.OSD.stop") + @mock.patch("cephadm.services.osd.OSD.exists") + def test_rm(self, exist, stop, osd_obj): + q = OSDRemovalQueue(mock.Mock()) + q.osds.add(osd_obj) + q.rm(osd_obj) + osd_obj.stop.assert_called_once() diff --git a/src/pybind/mgr/cephadm/tests/test_scheduling.py b/src/pybind/mgr/cephadm/tests/test_scheduling.py new file mode 100644 index 000000000..2454dc0d1 --- /dev/null +++ b/src/pybind/mgr/cephadm/tests/test_scheduling.py @@ -0,0 +1,1591 @@ +# Disable autopep8 for this file: + +# fmt: off + +from typing import NamedTuple, List, Dict, Optional +import pytest + +from ceph.deployment.hostspec import HostSpec +from ceph.deployment.service_spec import ServiceSpec, PlacementSpec, IngressSpec +from ceph.deployment.hostspec import SpecValidationError + +from cephadm.module import HostAssignment +from cephadm.schedule import DaemonPlacement +from orchestrator import DaemonDescription, OrchestratorValidationError, OrchestratorError + + +def wrapper(func): + # some odd thingy to revert the order or arguments + def inner(*args): + def inner2(expected): + func(expected, *args) + return inner2 + return inner + + +@wrapper +def none(expected): + assert expected == [] + + +@wrapper +def one_of(expected, *hosts): + if not isinstance(expected, list): + assert False, str(expected) + assert len(expected) == 1, f'one_of failed len({expected}) != 1' + assert expected[0] in hosts + + +@wrapper +def two_of(expected, *hosts): + if not isinstance(expected, list): + assert False, str(expected) + assert len(expected) == 2, f'one_of failed len({expected}) != 2' + matches = 0 + for h in hosts: + matches += int(h in expected) + if matches != 2: + assert False, f'two of {hosts} not in {expected}' + + +@wrapper +def exactly(expected, *hosts): + assert expected == list(hosts) + + +@wrapper +def error(expected, kind, match): + assert isinstance(expected, kind), (str(expected), match) + assert str(expected) == match, (str(expected), match) + + +@wrapper +def _or(expected, *inners): + def catch(inner): + try: + inner(expected) + except AssertionError as e: + return e + result = [catch(i) for i in inners] + if None not in result: + assert False, f"_or failed: {expected}" + + +def _always_true(_): + pass + + +def k(s): + return [e for e in s.split(' ') if e] + + +def get_result(key, results): + def match(one): + for o, k in zip(one, key): + if o != k and o != '*': + return False + return True + return [v for k, v in results if match(k)][0] + + +def mk_spec_and_host(spec_section, hosts, explicit_key, explicit, count): + + if spec_section == 'hosts': + mk_spec = lambda: ServiceSpec('mgr', placement=PlacementSpec( # noqa: E731 + hosts=explicit, + count=count, + )) + elif spec_section == 'label': + mk_spec = lambda: ServiceSpec('mgr', placement=PlacementSpec( # noqa: E731 + label='mylabel', + count=count, + )) + elif spec_section == 'host_pattern': + pattern = { + 'e': 'notfound', + '1': '1', + '12': '[1-2]', + '123': '*', + }[explicit_key] + mk_spec = lambda: ServiceSpec('mgr', placement=PlacementSpec( # noqa: E731 + host_pattern=pattern, + count=count, + )) + else: + assert False + + hosts = [ + HostSpec(h, labels=['mylabel']) if h in explicit else HostSpec(h) + for h in hosts + ] + + return mk_spec, hosts + + +def run_scheduler_test(results, mk_spec, hosts, daemons, key_elems): + key = ' '.join('N' if e is None else str(e) for e in key_elems) + try: + assert_res = get_result(k(key), results) + except IndexError: + try: + spec = mk_spec() + host_res, to_add, to_remove = HostAssignment( + spec=spec, + hosts=hosts, + unreachable_hosts=[], + daemons=daemons, + ).place() + if isinstance(host_res, list): + e = ', '.join(repr(h.hostname) for h in host_res) + assert False, f'`(k("{key}"), exactly({e})),` not found' + assert False, f'`(k("{key}"), ...),` not found' + except OrchestratorError as e: + assert False, f'`(k("{key}"), error({type(e).__name__}, {repr(str(e))})),` not found' + + for _ in range(10): # scheduler has a random component + try: + spec = mk_spec() + host_res, to_add, to_remove = HostAssignment( + spec=spec, + hosts=hosts, + unreachable_hosts=[], + daemons=daemons + ).place() + + assert_res(sorted([h.hostname for h in host_res])) + except Exception as e: + assert_res(e) + + +@pytest.mark.parametrize("dp,n,result", + [ # noqa: E128 + ( + DaemonPlacement(daemon_type='mgr', hostname='host1', ports=[80]), + 0, + DaemonPlacement(daemon_type='mgr', hostname='host1', ports=[80]), + ), + ( + DaemonPlacement(daemon_type='mgr', hostname='host1', ports=[80]), + 2, + DaemonPlacement(daemon_type='mgr', hostname='host1', ports=[82]), + ), + ( + DaemonPlacement(daemon_type='mgr', hostname='host1', ports=[80, 90]), + 2, + DaemonPlacement(daemon_type='mgr', hostname='host1', ports=[82, 92]), + ), + ]) +def test_daemon_placement_renumber(dp, n, result): + assert dp.renumber_ports(n) == result + + +@pytest.mark.parametrize( + 'dp,dd,result', + [ + ( + DaemonPlacement(daemon_type='mgr', hostname='host1'), + DaemonDescription('mgr', 'a', 'host1'), + True + ), + ( + DaemonPlacement(daemon_type='mgr', hostname='host1', name='a'), + DaemonDescription('mgr', 'a', 'host1'), + True + ), + ( + DaemonPlacement(daemon_type='mon', hostname='host1', name='a'), + DaemonDescription('mgr', 'a', 'host1'), + False + ), + ( + DaemonPlacement(daemon_type='mgr', hostname='host1', name='a'), + DaemonDescription('mgr', 'b', 'host1'), + False + ), + ]) +def test_daemon_placement_match(dp, dd, result): + assert dp.matches_daemon(dd) == result + + +# * first match from the top wins +# * where e=[], *=any +# +# + list of known hosts available for scheduling (host_key) +# | + hosts used for explict placement (explicit_key) +# | | + count +# | | | + section (host, label, pattern) +# | | | | + expected result +# | | | | | +test_explicit_scheduler_results = [ + (k("* * 0 *"), error(SpecValidationError, 'num/count must be >= 1')), + (k("* e N l"), error(OrchestratorValidationError, 'Cannot place : No matching hosts for label mylabel')), + (k("* e N p"), error(OrchestratorValidationError, 'Cannot place : No matching hosts')), + (k("* e N h"), error(OrchestratorValidationError, 'placement spec is empty: no hosts, no label, no pattern, no count')), + (k("* e * *"), none), + (k("1 12 * h"), error(OrchestratorValidationError, "Cannot place on 2: Unknown hosts")), + (k("1 123 * h"), error(OrchestratorValidationError, "Cannot place on 2, 3: Unknown hosts")), + (k("1 * * *"), exactly('1')), + (k("12 1 * *"), exactly('1')), + (k("12 12 1 *"), one_of('1', '2')), + (k("12 12 * *"), exactly('1', '2')), + (k("12 123 * h"), error(OrchestratorValidationError, "Cannot place on 3: Unknown hosts")), + (k("12 123 1 *"), one_of('1', '2', '3')), + (k("12 123 * *"), two_of('1', '2', '3')), + (k("123 1 * *"), exactly('1')), + (k("123 12 1 *"), one_of('1', '2')), + (k("123 12 * *"), exactly('1', '2')), + (k("123 123 1 *"), one_of('1', '2', '3')), + (k("123 123 2 *"), two_of('1', '2', '3')), + (k("123 123 * *"), exactly('1', '2', '3')), +] + + +@pytest.mark.parametrize("spec_section_key,spec_section", + [ # noqa: E128 + ('h', 'hosts'), + ('l', 'label'), + ('p', 'host_pattern'), + ]) +@pytest.mark.parametrize("count", + [ # noqa: E128 + None, + 0, + 1, + 2, + 3, + ]) +@pytest.mark.parametrize("explicit_key, explicit", + [ # noqa: E128 + ('e', []), + ('1', ['1']), + ('12', ['1', '2']), + ('123', ['1', '2', '3']), + ]) +@pytest.mark.parametrize("host_key, hosts", + [ # noqa: E128 + ('1', ['1']), + ('12', ['1', '2']), + ('123', ['1', '2', '3']), + ]) +def test_explicit_scheduler(host_key, hosts, + explicit_key, explicit, + count, + spec_section_key, spec_section): + + mk_spec, hosts = mk_spec_and_host(spec_section, hosts, explicit_key, explicit, count) + run_scheduler_test( + results=test_explicit_scheduler_results, + mk_spec=mk_spec, + hosts=hosts, + daemons=[], + key_elems=(host_key, explicit_key, count, spec_section_key) + ) + + +# * first match from the top wins +# * where e=[], *=any +# +# + list of known hosts available for scheduling (host_key) +# | + hosts used for explict placement (explicit_key) +# | | + count +# | | | + existing daemons +# | | | | + section (host, label, pattern) +# | | | | | + expected result +# | | | | | | +test_scheduler_daemons_results = [ + (k("* 1 * * *"), exactly('1')), + (k("1 123 * * h"), error(OrchestratorValidationError, 'Cannot place on 2, 3: Unknown hosts')), + (k("1 123 * * *"), exactly('1')), + (k("12 123 * * h"), error(OrchestratorValidationError, 'Cannot place on 3: Unknown hosts')), + (k("12 123 N * *"), exactly('1', '2')), + (k("12 123 1 * *"), one_of('1', '2')), + (k("12 123 2 * *"), exactly('1', '2')), + (k("12 123 3 * *"), exactly('1', '2')), + (k("123 123 N * *"), exactly('1', '2', '3')), + (k("123 123 1 e *"), one_of('1', '2', '3')), + (k("123 123 1 1 *"), exactly('1')), + (k("123 123 1 3 *"), exactly('3')), + (k("123 123 1 12 *"), one_of('1', '2')), + (k("123 123 1 112 *"), one_of('1', '2')), + (k("123 123 1 23 *"), one_of('2', '3')), + (k("123 123 1 123 *"), one_of('1', '2', '3')), + (k("123 123 2 e *"), two_of('1', '2', '3')), + (k("123 123 2 1 *"), _or(exactly('1', '2'), exactly('1', '3'))), + (k("123 123 2 3 *"), _or(exactly('1', '3'), exactly('2', '3'))), + (k("123 123 2 12 *"), exactly('1', '2')), + (k("123 123 2 112 *"), exactly('1', '2')), + (k("123 123 2 23 *"), exactly('2', '3')), + (k("123 123 2 123 *"), two_of('1', '2', '3')), + (k("123 123 3 * *"), exactly('1', '2', '3')), +] + + +@pytest.mark.parametrize("spec_section_key,spec_section", + [ # noqa: E128 + ('h', 'hosts'), + ('l', 'label'), + ('p', 'host_pattern'), + ]) +@pytest.mark.parametrize("daemons_key, daemons", + [ # noqa: E128 + ('e', []), + ('1', ['1']), + ('3', ['3']), + ('12', ['1', '2']), + ('112', ['1', '1', '2']), # deal with existing co-located daemons + ('23', ['2', '3']), + ('123', ['1', '2', '3']), + ]) +@pytest.mark.parametrize("count", + [ # noqa: E128 + None, + 1, + 2, + 3, + ]) +@pytest.mark.parametrize("explicit_key, explicit", + [ # noqa: E128 + ('1', ['1']), + ('123', ['1', '2', '3']), + ]) +@pytest.mark.parametrize("host_key, hosts", + [ # noqa: E128 + ('1', ['1']), + ('12', ['1', '2']), + ('123', ['1', '2', '3']), + ]) +def test_scheduler_daemons(host_key, hosts, + explicit_key, explicit, + count, + daemons_key, daemons, + spec_section_key, spec_section): + mk_spec, hosts = mk_spec_and_host(spec_section, hosts, explicit_key, explicit, count) + dds = [ + DaemonDescription('mgr', d, d) + for d in daemons + ] + run_scheduler_test( + results=test_scheduler_daemons_results, + mk_spec=mk_spec, + hosts=hosts, + daemons=dds, + key_elems=(host_key, explicit_key, count, daemons_key, spec_section_key) + ) + + +# ========================= + + +class NodeAssignmentTest(NamedTuple): + service_type: str + placement: PlacementSpec + hosts: List[str] + daemons: List[DaemonDescription] + rank_map: Optional[Dict[int, Dict[int, Optional[str]]]] + post_rank_map: Optional[Dict[int, Dict[int, Optional[str]]]] + expected: List[str] + expected_add: List[str] + expected_remove: List[DaemonDescription] + + +@pytest.mark.parametrize("service_type,placement,hosts,daemons,rank_map,post_rank_map,expected,expected_add,expected_remove", + [ # noqa: E128 + # just hosts + NodeAssignmentTest( + 'mgr', + PlacementSpec(hosts=['smithi060']), + ['smithi060'], + [], + None, None, + ['mgr:smithi060'], ['mgr:smithi060'], [] + ), + # all_hosts + NodeAssignmentTest( + 'mgr', + PlacementSpec(host_pattern='*'), + 'host1 host2 host3'.split(), + [ + DaemonDescription('mgr', 'a', 'host1'), + DaemonDescription('mgr', 'b', 'host2'), + ], + None, None, + ['mgr:host1', 'mgr:host2', 'mgr:host3'], + ['mgr:host3'], + [] + ), + # all_hosts + count_per_host + NodeAssignmentTest( + 'mds', + PlacementSpec(host_pattern='*', count_per_host=2), + 'host1 host2 host3'.split(), + [ + DaemonDescription('mds', 'a', 'host1'), + DaemonDescription('mds', 'b', 'host2'), + ], + None, None, + ['mds:host1', 'mds:host2', 'mds:host3', 'mds:host1', 'mds:host2', 'mds:host3'], + ['mds:host3', 'mds:host1', 'mds:host2', 'mds:host3'], + [] + ), + # count that is bigger than the amount of hosts. Truncate to len(hosts) + # mgr should not be co-located to each other. + NodeAssignmentTest( + 'mgr', + PlacementSpec(count=4), + 'host1 host2 host3'.split(), + [], + None, None, + ['mgr:host1', 'mgr:host2', 'mgr:host3'], + ['mgr:host1', 'mgr:host2', 'mgr:host3'], + [] + ), + # count that is bigger than the amount of hosts; wrap around. + NodeAssignmentTest( + 'mds', + PlacementSpec(count=6), + 'host1 host2 host3'.split(), + [], + None, None, + ['mds:host1', 'mds:host2', 'mds:host3', 'mds:host1', 'mds:host2', 'mds:host3'], + ['mds:host1', 'mds:host2', 'mds:host3', 'mds:host1', 'mds:host2', 'mds:host3'], + [] + ), + # count + partial host list + NodeAssignmentTest( + 'mgr', + PlacementSpec(count=3, hosts=['host3']), + 'host1 host2 host3'.split(), + [ + DaemonDescription('mgr', 'a', 'host1'), + DaemonDescription('mgr', 'b', 'host2'), + ], + None, None, + ['mgr:host3'], + ['mgr:host3'], + ['mgr.a', 'mgr.b'] + ), + # count + partial host list (with colo) + NodeAssignmentTest( + 'mds', + PlacementSpec(count=3, hosts=['host3']), + 'host1 host2 host3'.split(), + [ + DaemonDescription('mds', 'a', 'host1'), + DaemonDescription('mds', 'b', 'host2'), + ], + None, None, + ['mds:host3', 'mds:host3', 'mds:host3'], + ['mds:host3', 'mds:host3', 'mds:host3'], + ['mds.a', 'mds.b'] + ), + # count 1 + partial host list + NodeAssignmentTest( + 'mgr', + PlacementSpec(count=1, hosts=['host3']), + 'host1 host2 host3'.split(), + [ + DaemonDescription('mgr', 'a', 'host1'), + DaemonDescription('mgr', 'b', 'host2'), + ], + None, None, + ['mgr:host3'], + ['mgr:host3'], + ['mgr.a', 'mgr.b'] + ), + # count + partial host list + existing + NodeAssignmentTest( + 'mgr', + PlacementSpec(count=2, hosts=['host3']), + 'host1 host2 host3'.split(), + [ + DaemonDescription('mgr', 'a', 'host1'), + ], + None, None, + ['mgr:host3'], + ['mgr:host3'], + ['mgr.a'] + ), + # count + partial host list + existing (deterministic) + NodeAssignmentTest( + 'mgr', + PlacementSpec(count=2, hosts=['host1']), + 'host1 host2'.split(), + [ + DaemonDescription('mgr', 'a', 'host1'), + ], + None, None, + ['mgr:host1'], + [], + [] + ), + # count + partial host list + existing (deterministic) + NodeAssignmentTest( + 'mgr', + PlacementSpec(count=2, hosts=['host1']), + 'host1 host2'.split(), + [ + DaemonDescription('mgr', 'a', 'host2'), + ], + None, None, + ['mgr:host1'], + ['mgr:host1'], + ['mgr.a'] + ), + # label only + NodeAssignmentTest( + 'mgr', + PlacementSpec(label='foo'), + 'host1 host2 host3'.split(), + [], + None, None, + ['mgr:host1', 'mgr:host2', 'mgr:host3'], + ['mgr:host1', 'mgr:host2', 'mgr:host3'], + [] + ), + # label + count (truncate to host list) + NodeAssignmentTest( + 'mgr', + PlacementSpec(count=4, label='foo'), + 'host1 host2 host3'.split(), + [], + None, None, + ['mgr:host1', 'mgr:host2', 'mgr:host3'], + ['mgr:host1', 'mgr:host2', 'mgr:host3'], + [] + ), + # label + count (with colo) + NodeAssignmentTest( + 'mds', + PlacementSpec(count=6, label='foo'), + 'host1 host2 host3'.split(), + [], + None, None, + ['mds:host1', 'mds:host2', 'mds:host3', 'mds:host1', 'mds:host2', 'mds:host3'], + ['mds:host1', 'mds:host2', 'mds:host3', 'mds:host1', 'mds:host2', 'mds:host3'], + [] + ), + # label only + count_per_hst + NodeAssignmentTest( + 'mds', + PlacementSpec(label='foo', count_per_host=3), + 'host1 host2 host3'.split(), + [], + None, None, + ['mds:host1', 'mds:host2', 'mds:host3', 'mds:host1', 'mds:host2', 'mds:host3', + 'mds:host1', 'mds:host2', 'mds:host3'], + ['mds:host1', 'mds:host2', 'mds:host3', 'mds:host1', 'mds:host2', 'mds:host3', + 'mds:host1', 'mds:host2', 'mds:host3'], + [] + ), + # host_pattern + NodeAssignmentTest( + 'mgr', + PlacementSpec(host_pattern='mgr*'), + 'mgrhost1 mgrhost2 datahost'.split(), + [], + None, None, + ['mgr:mgrhost1', 'mgr:mgrhost2'], + ['mgr:mgrhost1', 'mgr:mgrhost2'], + [] + ), + # host_pattern + count_per_host + NodeAssignmentTest( + 'mds', + PlacementSpec(host_pattern='mds*', count_per_host=3), + 'mdshost1 mdshost2 datahost'.split(), + [], + None, None, + ['mds:mdshost1', 'mds:mdshost2', 'mds:mdshost1', 'mds:mdshost2', 'mds:mdshost1', 'mds:mdshost2'], + ['mds:mdshost1', 'mds:mdshost2', 'mds:mdshost1', 'mds:mdshost2', 'mds:mdshost1', 'mds:mdshost2'], + [] + ), + # label + count_per_host + ports + NodeAssignmentTest( + 'rgw', + PlacementSpec(count=6, label='foo'), + 'host1 host2 host3'.split(), + [], + None, None, + ['rgw:host1(*:80)', 'rgw:host2(*:80)', 'rgw:host3(*:80)', + 'rgw:host1(*:81)', 'rgw:host2(*:81)', 'rgw:host3(*:81)'], + ['rgw:host1(*:80)', 'rgw:host2(*:80)', 'rgw:host3(*:80)', + 'rgw:host1(*:81)', 'rgw:host2(*:81)', 'rgw:host3(*:81)'], + [] + ), + # label + count_per_host + ports (+ xisting) + NodeAssignmentTest( + 'rgw', + PlacementSpec(count=6, label='foo'), + 'host1 host2 host3'.split(), + [ + DaemonDescription('rgw', 'a', 'host1', ports=[81]), + DaemonDescription('rgw', 'b', 'host2', ports=[80]), + DaemonDescription('rgw', 'c', 'host1', ports=[82]), + ], + None, None, + ['rgw:host1(*:80)', 'rgw:host2(*:80)', 'rgw:host3(*:80)', + 'rgw:host1(*:81)', 'rgw:host2(*:81)', 'rgw:host3(*:81)'], + ['rgw:host1(*:80)', 'rgw:host3(*:80)', + 'rgw:host2(*:81)', 'rgw:host3(*:81)'], + ['rgw.c'] + ), + # cephadm.py teuth case + NodeAssignmentTest( + 'mgr', + PlacementSpec(count=3, hosts=['host1=y', 'host2=x']), + 'host1 host2'.split(), + [ + DaemonDescription('mgr', 'y', 'host1'), + DaemonDescription('mgr', 'x', 'host2'), + ], + None, None, + ['mgr:host1(name=y)', 'mgr:host2(name=x)'], + [], [] + ), + + # note: host -> rank mapping is psuedo-random based on svc name, so these + # host/rank pairs may seem random but they match the nfs.mynfs seed used by + # the test. + + # ranked, fresh + NodeAssignmentTest( + 'nfs', + PlacementSpec(count=3), + 'host1 host2 host3'.split(), + [], + {}, + {0: {0: None}, 1: {0: None}, 2: {0: None}}, + ['nfs:host3(rank=0.0)', 'nfs:host2(rank=1.0)', 'nfs:host1(rank=2.0)'], + ['nfs:host3(rank=0.0)', 'nfs:host2(rank=1.0)', 'nfs:host1(rank=2.0)'], + [] + ), + # 21: ranked, exist + NodeAssignmentTest( + 'nfs', + PlacementSpec(count=3), + 'host1 host2 host3'.split(), + [ + DaemonDescription('nfs', '0.1', 'host1', rank=0, rank_generation=1), + ], + {0: {1: '0.1'}}, + {0: {1: '0.1'}, 1: {0: None}, 2: {0: None}}, + ['nfs:host1(rank=0.1)', 'nfs:host3(rank=1.0)', 'nfs:host2(rank=2.0)'], + ['nfs:host3(rank=1.0)', 'nfs:host2(rank=2.0)'], + [] + ), + # ranked, exist, different ranks + NodeAssignmentTest( + 'nfs', + PlacementSpec(count=3), + 'host1 host2 host3'.split(), + [ + DaemonDescription('nfs', '0.1', 'host1', rank=0, rank_generation=1), + DaemonDescription('nfs', '1.1', 'host2', rank=1, rank_generation=1), + ], + {0: {1: '0.1'}, 1: {1: '1.1'}}, + {0: {1: '0.1'}, 1: {1: '1.1'}, 2: {0: None}}, + ['nfs:host1(rank=0.1)', 'nfs:host2(rank=1.1)', 'nfs:host3(rank=2.0)'], + ['nfs:host3(rank=2.0)'], + [] + ), + # ranked, exist, different ranks (2) + NodeAssignmentTest( + 'nfs', + PlacementSpec(count=3), + 'host1 host2 host3'.split(), + [ + DaemonDescription('nfs', '0.1', 'host1', rank=0, rank_generation=1), + DaemonDescription('nfs', '1.1', 'host3', rank=1, rank_generation=1), + ], + {0: {1: '0.1'}, 1: {1: '1.1'}}, + {0: {1: '0.1'}, 1: {1: '1.1'}, 2: {0: None}}, + ['nfs:host1(rank=0.1)', 'nfs:host3(rank=1.1)', 'nfs:host2(rank=2.0)'], + ['nfs:host2(rank=2.0)'], + [] + ), + # ranked, exist, extra ranks + NodeAssignmentTest( + 'nfs', + PlacementSpec(count=3), + 'host1 host2 host3'.split(), + [ + DaemonDescription('nfs', '0.5', 'host1', rank=0, rank_generation=5), + DaemonDescription('nfs', '1.5', 'host2', rank=1, rank_generation=5), + DaemonDescription('nfs', '4.5', 'host2', rank=4, rank_generation=5), + ], + {0: {5: '0.5'}, 1: {5: '1.5'}}, + {0: {5: '0.5'}, 1: {5: '1.5'}, 2: {0: None}}, + ['nfs:host1(rank=0.5)', 'nfs:host2(rank=1.5)', 'nfs:host3(rank=2.0)'], + ['nfs:host3(rank=2.0)'], + ['nfs.4.5'] + ), + # 25: ranked, exist, extra ranks (scale down: kill off high rank) + NodeAssignmentTest( + 'nfs', + PlacementSpec(count=2), + 'host3 host2 host1'.split(), + [ + DaemonDescription('nfs', '0.5', 'host1', rank=0, rank_generation=5), + DaemonDescription('nfs', '1.5', 'host2', rank=1, rank_generation=5), + DaemonDescription('nfs', '2.5', 'host3', rank=2, rank_generation=5), + ], + {0: {5: '0.5'}, 1: {5: '1.5'}, 2: {5: '2.5'}}, + {0: {5: '0.5'}, 1: {5: '1.5'}, 2: {5: '2.5'}}, + ['nfs:host1(rank=0.5)', 'nfs:host2(rank=1.5)'], + [], + ['nfs.2.5'] + ), + # ranked, exist, extra ranks (scale down hosts) + NodeAssignmentTest( + 'nfs', + PlacementSpec(count=2), + 'host1 host3'.split(), + [ + DaemonDescription('nfs', '0.5', 'host1', rank=0, rank_generation=5), + DaemonDescription('nfs', '1.5', 'host2', rank=1, rank_generation=5), + DaemonDescription('nfs', '2.5', 'host3', rank=4, rank_generation=5), + ], + {0: {5: '0.5'}, 1: {5: '1.5'}, 2: {5: '2.5'}}, + {0: {5: '0.5'}, 1: {5: '1.5', 6: None}, 2: {5: '2.5'}}, + ['nfs:host1(rank=0.5)', 'nfs:host3(rank=1.6)'], + ['nfs:host3(rank=1.6)'], + ['nfs.2.5', 'nfs.1.5'] + ), + # ranked, exist, duplicate rank + NodeAssignmentTest( + 'nfs', + PlacementSpec(count=3), + 'host1 host2 host3'.split(), + [ + DaemonDescription('nfs', '0.0', 'host1', rank=0, rank_generation=0), + DaemonDescription('nfs', '1.1', 'host2', rank=1, rank_generation=1), + DaemonDescription('nfs', '1.2', 'host3', rank=1, rank_generation=2), + ], + {0: {0: '0.0'}, 1: {2: '1.2'}}, + {0: {0: '0.0'}, 1: {2: '1.2'}, 2: {0: None}}, + ['nfs:host1(rank=0.0)', 'nfs:host3(rank=1.2)', 'nfs:host2(rank=2.0)'], + ['nfs:host2(rank=2.0)'], + ['nfs.1.1'] + ), + # 28: ranked, all gens stale (failure during update cycle) + NodeAssignmentTest( + 'nfs', + PlacementSpec(count=2), + 'host1 host2 host3'.split(), + [ + DaemonDescription('nfs', '0.2', 'host1', rank=0, rank_generation=2), + DaemonDescription('nfs', '1.2', 'host2', rank=1, rank_generation=2), + ], + {0: {2: '0.2'}, 1: {2: '1.2', 3: '1.3'}}, + {0: {2: '0.2'}, 1: {2: '1.2', 3: '1.3', 4: None}}, + ['nfs:host1(rank=0.2)', 'nfs:host3(rank=1.4)'], + ['nfs:host3(rank=1.4)'], + ['nfs.1.2'] + ), + # ranked, not enough hosts + NodeAssignmentTest( + 'nfs', + PlacementSpec(count=4), + 'host1 host2 host3'.split(), + [ + DaemonDescription('nfs', '0.2', 'host1', rank=0, rank_generation=2), + DaemonDescription('nfs', '1.2', 'host2', rank=1, rank_generation=2), + ], + {0: {2: '0.2'}, 1: {2: '1.2'}}, + {0: {2: '0.2'}, 1: {2: '1.2'}, 2: {0: None}}, + ['nfs:host1(rank=0.2)', 'nfs:host2(rank=1.2)', 'nfs:host3(rank=2.0)'], + ['nfs:host3(rank=2.0)'], + [] + ), + # ranked, scale down + NodeAssignmentTest( + 'nfs', + PlacementSpec(hosts=['host2']), + 'host1 host2'.split(), + [ + DaemonDescription('nfs', '0.2', 'host1', rank=0, rank_generation=2), + DaemonDescription('nfs', '1.2', 'host2', rank=1, rank_generation=2), + DaemonDescription('nfs', '2.2', 'host3', rank=2, rank_generation=2), + ], + {0: {2: '0.2'}, 1: {2: '1.2'}, 2: {2: '2.2'}}, + {0: {2: '0.2', 3: None}, 1: {2: '1.2'}, 2: {2: '2.2'}}, + ['nfs:host2(rank=0.3)'], + ['nfs:host2(rank=0.3)'], + ['nfs.0.2', 'nfs.1.2', 'nfs.2.2'] + ), + + ]) +def test_node_assignment(service_type, placement, hosts, daemons, rank_map, post_rank_map, + expected, expected_add, expected_remove): + spec = None + service_id = None + allow_colo = False + if service_type == 'rgw': + service_id = 'realm.zone' + allow_colo = True + elif service_type == 'mds': + service_id = 'myfs' + allow_colo = True + elif service_type == 'nfs': + service_id = 'mynfs' + spec = ServiceSpec(service_type=service_type, + service_id=service_id, + placement=placement) + + if not spec: + spec = ServiceSpec(service_type=service_type, + service_id=service_id, + placement=placement) + + all_slots, to_add, to_remove = HostAssignment( + spec=spec, + hosts=[HostSpec(h, labels=['foo']) for h in hosts], + unreachable_hosts=[], + daemons=daemons, + allow_colo=allow_colo, + rank_map=rank_map, + ).place() + + assert rank_map == post_rank_map + + got = [str(p) for p in all_slots] + num_wildcard = 0 + for i in expected: + if i == '*': + num_wildcard += 1 + else: + assert i in got + got.remove(i) + assert num_wildcard == len(got) + + got = [str(p) for p in to_add] + num_wildcard = 0 + for i in expected_add: + if i == '*': + num_wildcard += 1 + else: + assert i in got + got.remove(i) + assert num_wildcard == len(got) + + assert sorted([d.name() for d in to_remove]) == sorted(expected_remove) + + +class NodeAssignmentTest5(NamedTuple): + service_type: str + placement: PlacementSpec + available_hosts: List[str] + candidates_hosts: List[str] + + +@pytest.mark.parametrize("service_type, placement, available_hosts, expected_candidates", + [ # noqa: E128 + NodeAssignmentTest5( + 'alertmanager', + PlacementSpec(hosts='host1 host2 host3 host4'.split()), + 'host1 host2 host3 host4'.split(), + 'host3 host1 host4 host2'.split(), + ), + NodeAssignmentTest5( + 'prometheus', + PlacementSpec(hosts='host1 host2 host3 host4'.split()), + 'host1 host2 host3 host4'.split(), + 'host3 host2 host4 host1'.split(), + ), + NodeAssignmentTest5( + 'grafana', + PlacementSpec(hosts='host1 host2 host3 host4'.split()), + 'host1 host2 host3 host4'.split(), + 'host1 host2 host4 host3'.split(), + ), + NodeAssignmentTest5( + 'mgr', + PlacementSpec(hosts='host1 host2 host3 host4'.split()), + 'host1 host2 host3 host4'.split(), + 'host4 host2 host1 host3'.split(), + ), + NodeAssignmentTest5( + 'mon', + PlacementSpec(hosts='host1 host2 host3 host4'.split()), + 'host1 host2 host3 host4'.split(), + 'host1 host3 host4 host2'.split(), + ), + NodeAssignmentTest5( + 'rgw', + PlacementSpec(hosts='host1 host2 host3 host4'.split()), + 'host1 host2 host3 host4'.split(), + 'host1 host3 host2 host4'.split(), + ), + NodeAssignmentTest5( + 'cephfs-mirror', + PlacementSpec(hosts='host1 host2 host3 host4'.split()), + 'host1 host2 host3 host4'.split(), + 'host4 host3 host1 host2'.split(), + ), + ]) +def test_node_assignment_random_shuffle(service_type, placement, available_hosts, expected_candidates): + spec = None + service_id = None + allow_colo = False + spec = ServiceSpec(service_type=service_type, + service_id=service_id, + placement=placement) + + candidates = HostAssignment( + spec=spec, + hosts=[HostSpec(h, labels=['foo']) for h in available_hosts], + unreachable_hosts=[], + daemons=[], + allow_colo=allow_colo, + ).get_candidates() + + candidates_hosts = [h.hostname for h in candidates] + assert candidates_hosts == expected_candidates + + +class NodeAssignmentTest2(NamedTuple): + service_type: str + placement: PlacementSpec + hosts: List[str] + daemons: List[DaemonDescription] + expected_len: int + in_set: List[str] + + +@pytest.mark.parametrize("service_type,placement,hosts,daemons,expected_len,in_set", + [ # noqa: E128 + # just count + NodeAssignmentTest2( + 'mgr', + PlacementSpec(count=1), + 'host1 host2 host3'.split(), + [], + 1, + ['host1', 'host2', 'host3'], + ), + + # hosts + (smaller) count + NodeAssignmentTest2( + 'mgr', + PlacementSpec(count=1, hosts='host1 host2'.split()), + 'host1 host2'.split(), + [], + 1, + ['host1', 'host2'], + ), + # hosts + (smaller) count, existing + NodeAssignmentTest2( + 'mgr', + PlacementSpec(count=1, hosts='host1 host2 host3'.split()), + 'host1 host2 host3'.split(), + [DaemonDescription('mgr', 'mgr.a', 'host1')], + 1, + ['host1', 'host2', 'host3'], + ), + # hosts + (smaller) count, (more) existing + NodeAssignmentTest2( + 'mgr', + PlacementSpec(count=1, hosts='host1 host2 host3'.split()), + 'host1 host2 host3'.split(), + [ + DaemonDescription('mgr', 'a', 'host1'), + DaemonDescription('mgr', 'b', 'host2'), + ], + 1, + ['host1', 'host2'] + ), + # count + partial host list + NodeAssignmentTest2( + 'mgr', + PlacementSpec(count=2, hosts=['host3']), + 'host1 host2 host3'.split(), + [], + 1, + ['host1', 'host2', 'host3'] + ), + # label + count + NodeAssignmentTest2( + 'mgr', + PlacementSpec(count=1, label='foo'), + 'host1 host2 host3'.split(), + [], + 1, + ['host1', 'host2', 'host3'] + ), + ]) +def test_node_assignment2(service_type, placement, hosts, + daemons, expected_len, in_set): + hosts, to_add, to_remove = HostAssignment( + spec=ServiceSpec(service_type, placement=placement), + hosts=[HostSpec(h, labels=['foo']) for h in hosts], + unreachable_hosts=[], + daemons=daemons, + ).place() + assert len(hosts) == expected_len + for h in [h.hostname for h in hosts]: + assert h in in_set + + +@pytest.mark.parametrize("service_type,placement,hosts,daemons,expected_len,must_have", + [ # noqa: E128 + # hosts + (smaller) count, (more) existing + NodeAssignmentTest2( + 'mgr', + PlacementSpec(count=3, hosts='host3'.split()), + 'host1 host2 host3'.split(), + [], + 1, + ['host3'] + ), + # count + partial host list + NodeAssignmentTest2( + 'mgr', + PlacementSpec(count=2, hosts=['host3']), + 'host1 host2 host3'.split(), + [], + 1, + ['host3'] + ), + ]) +def test_node_assignment3(service_type, placement, hosts, + daemons, expected_len, must_have): + hosts, to_add, to_remove = HostAssignment( + spec=ServiceSpec(service_type, placement=placement), + hosts=[HostSpec(h) for h in hosts], + unreachable_hosts=[], + daemons=daemons, + ).place() + assert len(hosts) == expected_len + for h in must_have: + assert h in [h.hostname for h in hosts] + + +class NodeAssignmentTest4(NamedTuple): + spec: ServiceSpec + networks: Dict[str, Dict[str, Dict[str, List[str]]]] + daemons: List[DaemonDescription] + expected: List[str] + expected_add: List[str] + expected_remove: List[DaemonDescription] + + +@pytest.mark.parametrize("spec,networks,daemons,expected,expected_add,expected_remove", + [ # noqa: E128 + NodeAssignmentTest4( + ServiceSpec( + service_type='rgw', + service_id='foo', + placement=PlacementSpec(count=6, label='foo'), + networks=['10.0.0.0/8'], + ), + { + 'host1': {'10.0.0.0/8': {'eth0': ['10.0.0.1']}}, + 'host2': {'10.0.0.0/8': {'eth0': ['10.0.0.2']}}, + 'host3': {'192.168.0.0/16': {'eth0': ['192.168.0.1']}}, + }, + [], + ['rgw:host1(10.0.0.1:80)', 'rgw:host2(10.0.0.2:80)', + 'rgw:host1(10.0.0.1:81)', 'rgw:host2(10.0.0.2:81)', + 'rgw:host1(10.0.0.1:82)', 'rgw:host2(10.0.0.2:82)'], + ['rgw:host1(10.0.0.1:80)', 'rgw:host2(10.0.0.2:80)', + 'rgw:host1(10.0.0.1:81)', 'rgw:host2(10.0.0.2:81)', + 'rgw:host1(10.0.0.1:82)', 'rgw:host2(10.0.0.2:82)'], + [] + ), + NodeAssignmentTest4( + IngressSpec( + service_type='ingress', + service_id='rgw.foo', + frontend_port=443, + monitor_port=8888, + virtual_ip='10.0.0.20/8', + backend_service='rgw.foo', + placement=PlacementSpec(label='foo'), + networks=['10.0.0.0/8'], + ), + { + 'host1': {'10.0.0.0/8': {'eth0': ['10.0.0.1']}}, + 'host2': {'10.0.0.0/8': {'eth1': ['10.0.0.2']}}, + 'host3': {'192.168.0.0/16': {'eth2': ['192.168.0.1']}}, + }, + [], + ['haproxy:host1(10.0.0.1:443,8888)', 'haproxy:host2(10.0.0.2:443,8888)', + 'keepalived:host1', 'keepalived:host2'], + ['haproxy:host1(10.0.0.1:443,8888)', 'haproxy:host2(10.0.0.2:443,8888)', + 'keepalived:host1', 'keepalived:host2'], + [] + ), + NodeAssignmentTest4( + IngressSpec( + service_type='ingress', + service_id='rgw.foo', + frontend_port=443, + monitor_port=8888, + virtual_ip='10.0.0.20/8', + backend_service='rgw.foo', + placement=PlacementSpec(label='foo'), + networks=['10.0.0.0/8'], + ), + { + 'host1': {'10.0.0.0/8': {'eth0': ['10.0.0.1']}}, + 'host2': {'10.0.0.0/8': {'eth1': ['10.0.0.2']}}, + 'host3': {'192.168.0.0/16': {'eth2': ['192.168.0.1']}}, + }, + [ + DaemonDescription('haproxy', 'a', 'host1', ip='10.0.0.1', + ports=[443, 8888]), + DaemonDescription('keepalived', 'b', 'host2'), + DaemonDescription('keepalived', 'c', 'host3'), + ], + ['haproxy:host1(10.0.0.1:443,8888)', 'haproxy:host2(10.0.0.2:443,8888)', + 'keepalived:host1', 'keepalived:host2'], + ['haproxy:host2(10.0.0.2:443,8888)', + 'keepalived:host1'], + ['keepalived.c'] + ), + ]) +def test_node_assignment4(spec, networks, daemons, + expected, expected_add, expected_remove): + all_slots, to_add, to_remove = HostAssignment( + spec=spec, + hosts=[HostSpec(h, labels=['foo']) for h in networks.keys()], + unreachable_hosts=[], + daemons=daemons, + allow_colo=True, + networks=networks, + primary_daemon_type='haproxy' if spec.service_type == 'ingress' else spec.service_type, + per_host_daemon_type='keepalived' if spec.service_type == 'ingress' else None, + ).place() + + got = [str(p) for p in all_slots] + num_wildcard = 0 + for i in expected: + if i == '*': + num_wildcard += 1 + else: + assert i in got + got.remove(i) + assert num_wildcard == len(got) + + got = [str(p) for p in to_add] + num_wildcard = 0 + for i in expected_add: + if i == '*': + num_wildcard += 1 + else: + assert i in got + got.remove(i) + assert num_wildcard == len(got) + + assert sorted([d.name() for d in to_remove]) == sorted(expected_remove) + + +@pytest.mark.parametrize("placement", + [ # noqa: E128 + ('1 *'), + ('* label:foo'), + ('* host1 host2'), + ('hostname12hostname12hostname12hostname12hostname12hostname12hostname12'), # > 63 chars + ]) +def test_bad_placements(placement): + try: + PlacementSpec.from_string(placement.split(' ')) + assert False + except SpecValidationError: + pass + + +class NodeAssignmentTestBadSpec(NamedTuple): + service_type: str + placement: PlacementSpec + hosts: List[str] + daemons: List[DaemonDescription] + expected: str + + +@pytest.mark.parametrize("service_type,placement,hosts,daemons,expected", + [ # noqa: E128 + # unknown host + NodeAssignmentTestBadSpec( + 'mgr', + PlacementSpec(hosts=['unknownhost']), + ['knownhost'], + [], + "Cannot place on unknownhost: Unknown hosts" + ), + # unknown host pattern + NodeAssignmentTestBadSpec( + 'mgr', + PlacementSpec(host_pattern='unknownhost'), + ['knownhost'], + [], + "Cannot place : No matching hosts" + ), + # unknown label + NodeAssignmentTestBadSpec( + 'mgr', + PlacementSpec(label='unknownlabel'), + [], + [], + "Cannot place : No matching hosts for label unknownlabel" + ), + ]) +def test_bad_specs(service_type, placement, hosts, daemons, expected): + with pytest.raises(OrchestratorValidationError) as e: + hosts, to_add, to_remove = HostAssignment( + spec=ServiceSpec(service_type, placement=placement), + hosts=[HostSpec(h) for h in hosts], + unreachable_hosts=[], + daemons=daemons, + ).place() + assert str(e.value) == expected + + +class ActiveAssignmentTest(NamedTuple): + service_type: str + placement: PlacementSpec + hosts: List[str] + daemons: List[DaemonDescription] + expected: List[List[str]] + expected_add: List[List[str]] + expected_remove: List[List[str]] + + +@pytest.mark.parametrize("service_type,placement,hosts,daemons,expected,expected_add,expected_remove", + [ + ActiveAssignmentTest( + 'mgr', + PlacementSpec(count=2), + 'host1 host2 host3'.split(), + [ + DaemonDescription('mgr', 'a', 'host1', is_active=True), + DaemonDescription('mgr', 'b', 'host2'), + DaemonDescription('mgr', 'c', 'host3'), + ], + [['host1', 'host2'], ['host1', 'host3']], + [[]], + [['mgr.b'], ['mgr.c']] + ), + ActiveAssignmentTest( + 'mgr', + PlacementSpec(count=2), + 'host1 host2 host3'.split(), + [ + DaemonDescription('mgr', 'a', 'host1'), + DaemonDescription('mgr', 'b', 'host2'), + DaemonDescription('mgr', 'c', 'host3', is_active=True), + ], + [['host1', 'host3'], ['host2', 'host3']], + [[]], + [['mgr.a'], ['mgr.b']] + ), + ActiveAssignmentTest( + 'mgr', + PlacementSpec(count=1), + 'host1 host2 host3'.split(), + [ + DaemonDescription('mgr', 'a', 'host1'), + DaemonDescription('mgr', 'b', 'host2', is_active=True), + DaemonDescription('mgr', 'c', 'host3'), + ], + [['host2']], + [[]], + [['mgr.a', 'mgr.c']] + ), + ActiveAssignmentTest( + 'mgr', + PlacementSpec(count=1), + 'host1 host2 host3'.split(), + [ + DaemonDescription('mgr', 'a', 'host1'), + DaemonDescription('mgr', 'b', 'host2'), + DaemonDescription('mgr', 'c', 'host3', is_active=True), + ], + [['host3']], + [[]], + [['mgr.a', 'mgr.b']] + ), + ActiveAssignmentTest( + 'mgr', + PlacementSpec(count=1), + 'host1 host2 host3'.split(), + [ + DaemonDescription('mgr', 'a', 'host1', is_active=True), + DaemonDescription('mgr', 'b', 'host2'), + DaemonDescription('mgr', 'c', 'host3', is_active=True), + ], + [['host1'], ['host3']], + [[]], + [['mgr.a', 'mgr.b'], ['mgr.b', 'mgr.c']] + ), + ActiveAssignmentTest( + 'mgr', + PlacementSpec(count=2), + 'host1 host2 host3'.split(), + [ + DaemonDescription('mgr', 'a', 'host1'), + DaemonDescription('mgr', 'b', 'host2', is_active=True), + DaemonDescription('mgr', 'c', 'host3', is_active=True), + ], + [['host2', 'host3']], + [[]], + [['mgr.a']] + ), + ActiveAssignmentTest( + 'mgr', + PlacementSpec(count=1), + 'host1 host2 host3'.split(), + [ + DaemonDescription('mgr', 'a', 'host1', is_active=True), + DaemonDescription('mgr', 'b', 'host2', is_active=True), + DaemonDescription('mgr', 'c', 'host3', is_active=True), + ], + [['host1'], ['host2'], ['host3']], + [[]], + [['mgr.a', 'mgr.b'], ['mgr.b', 'mgr.c'], ['mgr.a', 'mgr.c']] + ), + ActiveAssignmentTest( + 'mgr', + PlacementSpec(count=1), + 'host1 host2 host3'.split(), + [ + DaemonDescription('mgr', 'a', 'host1', is_active=True), + DaemonDescription('mgr', 'a2', 'host1'), + DaemonDescription('mgr', 'b', 'host2'), + DaemonDescription('mgr', 'c', 'host3'), + ], + [['host1']], + [[]], + [['mgr.a2', 'mgr.b', 'mgr.c']] + ), + ActiveAssignmentTest( + 'mgr', + PlacementSpec(count=1), + 'host1 host2 host3'.split(), + [ + DaemonDescription('mgr', 'a', 'host1', is_active=True), + DaemonDescription('mgr', 'a2', 'host1', is_active=True), + DaemonDescription('mgr', 'b', 'host2'), + DaemonDescription('mgr', 'c', 'host3'), + ], + [['host1']], + [[]], + [['mgr.a', 'mgr.b', 'mgr.c'], ['mgr.a2', 'mgr.b', 'mgr.c']] + ), + ActiveAssignmentTest( + 'mgr', + PlacementSpec(count=2), + 'host1 host2 host3'.split(), + [ + DaemonDescription('mgr', 'a', 'host1', is_active=True), + DaemonDescription('mgr', 'a2', 'host1'), + DaemonDescription('mgr', 'b', 'host2'), + DaemonDescription('mgr', 'c', 'host3', is_active=True), + ], + [['host1', 'host3']], + [[]], + [['mgr.a2', 'mgr.b']] + ), + # Explicit placement should override preference for active daemon + ActiveAssignmentTest( + 'mgr', + PlacementSpec(count=1, hosts=['host1']), + 'host1 host2 host3'.split(), + [ + DaemonDescription('mgr', 'a', 'host1'), + DaemonDescription('mgr', 'b', 'host2'), + DaemonDescription('mgr', 'c', 'host3', is_active=True), + ], + [['host1']], + [[]], + [['mgr.b', 'mgr.c']] + ), + + ]) +def test_active_assignment(service_type, placement, hosts, daemons, expected, expected_add, expected_remove): + + spec = ServiceSpec(service_type=service_type, + service_id=None, + placement=placement) + + hosts, to_add, to_remove = HostAssignment( + spec=spec, + hosts=[HostSpec(h) for h in hosts], + unreachable_hosts=[], + daemons=daemons, + ).place() + assert sorted([h.hostname for h in hosts]) in expected + assert sorted([h.hostname for h in to_add]) in expected_add + assert sorted([h.name() for h in to_remove]) in expected_remove + + +class UnreachableHostsTest(NamedTuple): + service_type: str + placement: PlacementSpec + hosts: List[str] + unreachables_hosts: List[str] + daemons: List[DaemonDescription] + expected_add: List[List[str]] + expected_remove: List[List[str]] + + +@pytest.mark.parametrize("service_type,placement,hosts,unreachable_hosts,daemons,expected_add,expected_remove", + [ + UnreachableHostsTest( + 'mgr', + PlacementSpec(count=3), + 'host1 host2 host3'.split(), + ['host2'], + [], + [['host1', 'host3']], + [[]], + ), + UnreachableHostsTest( + 'mgr', + PlacementSpec(hosts=['host3']), + 'host1 host2 host3'.split(), + ['host1'], + [ + DaemonDescription('mgr', 'a', 'host1'), + DaemonDescription('mgr', 'b', 'host2'), + DaemonDescription('mgr', 'c', 'host3', is_active=True), + ], + [[]], + [['mgr.b']], + ), + UnreachableHostsTest( + 'mgr', + PlacementSpec(count=3), + 'host1 host2 host3 host4'.split(), + ['host1'], + [ + DaemonDescription('mgr', 'a', 'host1'), + DaemonDescription('mgr', 'b', 'host2'), + DaemonDescription('mgr', 'c', 'host3', is_active=True), + ], + [[]], + [[]], + ), + UnreachableHostsTest( + 'mgr', + PlacementSpec(count=1), + 'host1 host2 host3 host4'.split(), + 'host1 host3'.split(), + [ + DaemonDescription('mgr', 'a', 'host1'), + DaemonDescription('mgr', 'b', 'host2'), + DaemonDescription('mgr', 'c', 'host3', is_active=True), + ], + [[]], + [['mgr.b']], + ), + UnreachableHostsTest( + 'mgr', + PlacementSpec(count=3), + 'host1 host2 host3 host4'.split(), + ['host2'], + [], + [['host1', 'host3', 'host4']], + [[]], + ), + UnreachableHostsTest( + 'mgr', + PlacementSpec(count=3), + 'host1 host2 host3 host4'.split(), + 'host1 host4'.split(), + [], + [['host2', 'host3']], + [[]], + ), + + ]) +def test_unreachable_host(service_type, placement, hosts, unreachable_hosts, daemons, expected_add, expected_remove): + + spec = ServiceSpec(service_type=service_type, + service_id=None, + placement=placement) + + hosts, to_add, to_remove = HostAssignment( + spec=spec, + hosts=[HostSpec(h) for h in hosts], + unreachable_hosts=[HostSpec(h) for h in unreachable_hosts], + daemons=daemons, + ).place() + assert sorted([h.hostname for h in to_add]) in expected_add + assert sorted([h.name() for h in to_remove]) in expected_remove + + +class RescheduleFromOfflineTest(NamedTuple): + service_type: str + placement: PlacementSpec + hosts: List[str] + maintenance_hosts: List[str] + offline_hosts: List[str] + daemons: List[DaemonDescription] + expected_add: List[List[str]] + expected_remove: List[List[str]] + + +@pytest.mark.parametrize("service_type,placement,hosts,maintenance_hosts,offline_hosts,daemons,expected_add,expected_remove", + [ + RescheduleFromOfflineTest( + 'nfs', + PlacementSpec(count=2), + 'host1 host2 host3'.split(), + [], + ['host2'], + [ + DaemonDescription('nfs', 'a', 'host1'), + DaemonDescription('nfs', 'b', 'host2'), + ], + [['host3']], + [[]], + ), + RescheduleFromOfflineTest( + 'nfs', + PlacementSpec(count=2), + 'host1 host2 host3'.split(), + ['host2'], + [], + [ + DaemonDescription('nfs', 'a', 'host1'), + DaemonDescription('nfs', 'b', 'host2'), + ], + [[]], + [[]], + ), + RescheduleFromOfflineTest( + 'mon', + PlacementSpec(count=2), + 'host1 host2 host3'.split(), + [], + ['host2'], + [ + DaemonDescription('mon', 'a', 'host1'), + DaemonDescription('mon', 'b', 'host2'), + ], + [[]], + [[]], + ), + ]) +def test_remove_from_offline(service_type, placement, hosts, maintenance_hosts, offline_hosts, daemons, expected_add, expected_remove): + + spec = ServiceSpec(service_type=service_type, + service_id='test', + placement=placement) + + host_specs = [HostSpec(h) for h in hosts] + for h in host_specs: + if h.hostname in offline_hosts: + h.status = 'offline' + if h.hostname in maintenance_hosts: + h.status = 'maintenance' + + hosts, to_add, to_remove = HostAssignment( + spec=spec, + hosts=host_specs, + unreachable_hosts=[h for h in host_specs if h.status], + daemons=daemons, + ).place() + assert sorted([h.hostname for h in to_add]) in expected_add + assert sorted([h.name() for h in to_remove]) in expected_remove diff --git a/src/pybind/mgr/cephadm/tests/test_services.py b/src/pybind/mgr/cephadm/tests/test_services.py new file mode 100644 index 000000000..57cd12456 --- /dev/null +++ b/src/pybind/mgr/cephadm/tests/test_services.py @@ -0,0 +1,1031 @@ +from textwrap import dedent +import json +import yaml + +import pytest + +from unittest.mock import MagicMock, call, patch, ANY + +from cephadm.serve import CephadmServe +from cephadm.services.cephadmservice import MonService, MgrService, MdsService, RgwService, \ + RbdMirrorService, CrashService, CephadmDaemonDeploySpec +from cephadm.services.iscsi import IscsiService +from cephadm.services.nfs import NFSService +from cephadm.services.osd import OSDService +from cephadm.services.monitoring import GrafanaService, AlertmanagerService, PrometheusService, \ + NodeExporterService +from cephadm.services.exporter import CephadmExporter +from cephadm.module import CephadmOrchestrator +from ceph.deployment.service_spec import IscsiServiceSpec, MonitoringSpec, AlertManagerSpec, \ + ServiceSpec, RGWSpec, GrafanaSpec, SNMPGatewaySpec, IngressSpec, PlacementSpec +from cephadm.tests.fixtures import with_host, with_service, _run_cephadm + +from orchestrator import OrchestratorError +from orchestrator._interface import DaemonDescription + + +class FakeInventory: + def get_addr(self, name: str) -> str: + return '1.2.3.4' + + +class FakeMgr: + def __init__(self): + self.config = '' + self.check_mon_command = MagicMock(side_effect=self._check_mon_command) + self.mon_command = MagicMock(side_effect=self._check_mon_command) + self.template = MagicMock() + self.log = MagicMock() + self.inventory = FakeInventory() + + def _check_mon_command(self, cmd_dict, inbuf=None): + prefix = cmd_dict.get('prefix') + if prefix == 'get-cmd': + return 0, self.config, '' + if prefix == 'set-cmd': + self.config = cmd_dict.get('value') + return 0, 'value set', '' + return -1, '', 'error' + + def get_minimal_ceph_conf(self) -> str: + return '' + + def get_mgr_ip(self) -> str: + return '1.2.3.4' + + +class TestCephadmService: + def test_set_service_url_on_dashboard(self): + # pylint: disable=protected-access + mgr = FakeMgr() + service_url = 'http://svc:1000' + service = GrafanaService(mgr) + service._set_service_url_on_dashboard('svc', 'get-cmd', 'set-cmd', service_url) + assert mgr.config == service_url + + # set-cmd should not be called if value doesn't change + mgr.check_mon_command.reset_mock() + service._set_service_url_on_dashboard('svc', 'get-cmd', 'set-cmd', service_url) + mgr.check_mon_command.assert_called_once_with({'prefix': 'get-cmd'}) + + def _get_services(self, mgr): + # services: + osd_service = OSDService(mgr) + nfs_service = NFSService(mgr) + mon_service = MonService(mgr) + mgr_service = MgrService(mgr) + mds_service = MdsService(mgr) + rgw_service = RgwService(mgr) + rbd_mirror_service = RbdMirrorService(mgr) + grafana_service = GrafanaService(mgr) + alertmanager_service = AlertmanagerService(mgr) + prometheus_service = PrometheusService(mgr) + node_exporter_service = NodeExporterService(mgr) + crash_service = CrashService(mgr) + iscsi_service = IscsiService(mgr) + cephadm_exporter_service = CephadmExporter(mgr) + cephadm_services = { + 'mon': mon_service, + 'mgr': mgr_service, + 'osd': osd_service, + 'mds': mds_service, + 'rgw': rgw_service, + 'rbd-mirror': rbd_mirror_service, + 'nfs': nfs_service, + 'grafana': grafana_service, + 'alertmanager': alertmanager_service, + 'prometheus': prometheus_service, + 'node-exporter': node_exporter_service, + 'crash': crash_service, + 'iscsi': iscsi_service, + 'cephadm-exporter': cephadm_exporter_service, + } + return cephadm_services + + def test_get_auth_entity(self): + mgr = FakeMgr() + cephadm_services = self._get_services(mgr) + + for daemon_type in ['rgw', 'rbd-mirror', 'nfs', "iscsi"]: + assert "client.%s.id1" % (daemon_type) == \ + cephadm_services[daemon_type].get_auth_entity("id1", "host") + assert "client.%s.id1" % (daemon_type) == \ + cephadm_services[daemon_type].get_auth_entity("id1", "") + assert "client.%s.id1" % (daemon_type) == \ + cephadm_services[daemon_type].get_auth_entity("id1") + + assert "client.crash.host" == \ + cephadm_services["crash"].get_auth_entity("id1", "host") + with pytest.raises(OrchestratorError): + cephadm_services["crash"].get_auth_entity("id1", "") + cephadm_services["crash"].get_auth_entity("id1") + + assert "mon." == cephadm_services["mon"].get_auth_entity("id1", "host") + assert "mon." == cephadm_services["mon"].get_auth_entity("id1", "") + assert "mon." == cephadm_services["mon"].get_auth_entity("id1") + + assert "mgr.id1" == cephadm_services["mgr"].get_auth_entity("id1", "host") + assert "mgr.id1" == cephadm_services["mgr"].get_auth_entity("id1", "") + assert "mgr.id1" == cephadm_services["mgr"].get_auth_entity("id1") + + for daemon_type in ["osd", "mds"]: + assert "%s.id1" % daemon_type == \ + cephadm_services[daemon_type].get_auth_entity("id1", "host") + assert "%s.id1" % daemon_type == \ + cephadm_services[daemon_type].get_auth_entity("id1", "") + assert "%s.id1" % daemon_type == \ + cephadm_services[daemon_type].get_auth_entity("id1") + + # services based on CephadmService shouldn't have get_auth_entity + with pytest.raises(AttributeError): + for daemon_type in ['grafana', 'alertmanager', 'prometheus', 'node-exporter', 'cephadm-exporter']: + cephadm_services[daemon_type].get_auth_entity("id1", "host") + cephadm_services[daemon_type].get_auth_entity("id1", "") + cephadm_services[daemon_type].get_auth_entity("id1") + + +class TestISCSIService: + + mgr = FakeMgr() + iscsi_service = IscsiService(mgr) + + iscsi_spec = IscsiServiceSpec(service_type='iscsi', service_id="a") + iscsi_spec.daemon_type = "iscsi" + iscsi_spec.daemon_id = "a" + iscsi_spec.spec = MagicMock() + iscsi_spec.spec.daemon_type = "iscsi" + iscsi_spec.spec.ssl_cert = '' + iscsi_spec.api_user = "user" + iscsi_spec.api_password = "password" + iscsi_spec.api_port = 5000 + iscsi_spec.api_secure = False + iscsi_spec.ssl_cert = "cert" + iscsi_spec.ssl_key = "key" + + mgr.spec_store = MagicMock() + mgr.spec_store.all_specs.get.return_value = iscsi_spec + + def test_iscsi_client_caps(self): + + iscsi_daemon_spec = CephadmDaemonDeploySpec( + host='host', daemon_id='a', service_name=self.iscsi_spec.service_name()) + + self.iscsi_service.prepare_create(iscsi_daemon_spec) + + expected_caps = ['mon', + 'profile rbd, allow command "osd blocklist", allow command "config-key get" with "key" prefix "iscsi/"', + 'mgr', 'allow command "service status"', + 'osd', 'allow rwx'] + + expected_call = call({'prefix': 'auth get-or-create', + 'entity': 'client.iscsi.a', + 'caps': expected_caps}) + expected_call2 = call({'prefix': 'auth caps', + 'entity': 'client.iscsi.a', + 'caps': expected_caps}) + + assert expected_call in self.mgr.mon_command.mock_calls + assert expected_call2 in self.mgr.mon_command.mock_calls + + @patch('cephadm.utils.resolve_ip') + def test_iscsi_dashboard_config(self, mock_resolve_ip): + + self.mgr.check_mon_command = MagicMock() + self.mgr.check_mon_command.return_value = ('', '{"gateways": {}}', '') + + # Case 1: use IPV4 address + id1 = DaemonDescription(daemon_type='iscsi', hostname="testhost1", + daemon_id="a", ip='192.168.1.1') + daemon_list = [id1] + mock_resolve_ip.return_value = '192.168.1.1' + + self.iscsi_service.config_dashboard(daemon_list) + + dashboard_expected_call = call({'prefix': 'dashboard iscsi-gateway-add', + 'name': 'testhost1'}, + 'http://user:password@192.168.1.1:5000') + + assert dashboard_expected_call in self.mgr.check_mon_command.mock_calls + + # Case 2: use IPV6 address + self.mgr.check_mon_command.reset_mock() + + id1 = DaemonDescription(daemon_type='iscsi', hostname="testhost1", + daemon_id="a", ip='FEDC:BA98:7654:3210:FEDC:BA98:7654:3210') + mock_resolve_ip.return_value = 'FEDC:BA98:7654:3210:FEDC:BA98:7654:3210' + + self.iscsi_service.config_dashboard(daemon_list) + + dashboard_expected_call = call({'prefix': 'dashboard iscsi-gateway-add', + 'name': 'testhost1'}, + 'http://user:password@[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:5000') + + assert dashboard_expected_call in self.mgr.check_mon_command.mock_calls + + # Case 3: IPV6 Address . Secure protocol + self.mgr.check_mon_command.reset_mock() + + self.iscsi_spec.api_secure = True + + self.iscsi_service.config_dashboard(daemon_list) + + dashboard_expected_call = call({'prefix': 'dashboard iscsi-gateway-add', + 'name': 'testhost1'}, + 'https://user:password@[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:5000') + + assert dashboard_expected_call in self.mgr.check_mon_command.mock_calls + + +class TestMonitoring: + def _get_config(self, url: str) -> str: + return f""" + # This file is generated by cephadm. + # See https://prometheus.io/docs/alerting/configuration/ for documentation. + + global: + resolve_timeout: 5m + http_config: + tls_config: + insecure_skip_verify: true + + route: + receiver: 'default' + routes: + - group_by: ['alertname'] + group_wait: 10s + group_interval: 10s + repeat_interval: 1h + receiver: 'ceph-dashboard' + + receivers: + - name: 'default' + webhook_configs: + - name: 'ceph-dashboard' + webhook_configs: + - url: '{url}/api/prometheus_receiver' + """ + + @patch("cephadm.serve.CephadmServe._run_cephadm") + @patch("mgr_module.MgrModule.get") + def test_alertmanager_config(self, mock_get, _run_cephadm, + cephadm_module: CephadmOrchestrator): + _run_cephadm.return_value = ('{}', '', 0) + mock_get.return_value = {"services": {"dashboard": "http://[::1]:8080"}} + + with with_host(cephadm_module, 'test'): + with with_service(cephadm_module, AlertManagerSpec()): + y = dedent(self._get_config('http://localhost:8080')).lstrip() + _run_cephadm.assert_called_with( + 'test', + 'alertmanager.test', + 'deploy', + [ + '--name', 'alertmanager.test', + '--meta-json', '{"service_name": "alertmanager", "ports": [9093, 9094], "ip": null, "deployed_by": [], "rank": null, "rank_generation": null, "extra_container_args": null}', + '--config-json', '-', '--tcp-ports', '9093 9094' + ], + stdin=json.dumps({"files": {"alertmanager.yml": y}, "peers": []}), + image='') + + @patch("cephadm.serve.CephadmServe._run_cephadm") + @patch("mgr_module.MgrModule.get") + def test_alertmanager_config_v6(self, mock_get, _run_cephadm, + cephadm_module: CephadmOrchestrator): + dashboard_url = "http://[2001:db8:4321:0000:0000:0000:0000:0000]:8080" + _run_cephadm.return_value = ('{}', '', 0) + mock_get.return_value = {"services": {"dashboard": dashboard_url}} + + with with_host(cephadm_module, 'test'): + with with_service(cephadm_module, AlertManagerSpec()): + y = dedent(self._get_config(dashboard_url)).lstrip() + _run_cephadm.assert_called_with( + 'test', + 'alertmanager.test', + 'deploy', + [ + '--name', 'alertmanager.test', + '--meta-json', + '{"service_name": "alertmanager", "ports": [9093, 9094], "ip": null, "deployed_by": [], "rank": null, "rank_generation": null, "extra_container_args": null}', + '--config-json', '-', '--tcp-ports', '9093 9094' + ], + stdin=json.dumps({"files": {"alertmanager.yml": y}, "peers": []}), + image='') + + @patch("cephadm.serve.CephadmServe._run_cephadm") + @patch("mgr_module.MgrModule.get") + @patch("socket.getfqdn") + def test_alertmanager_config_v6_fqdn(self, mock_getfqdn, mock_get, _run_cephadm, + cephadm_module: CephadmOrchestrator): + _run_cephadm.return_value = ('{}', '', 0) + mock_getfqdn.return_value = "mgr.test.fqdn" + mock_get.return_value = {"services": { + "dashboard": "http://[2001:db8:4321:0000:0000:0000:0000:0000]:8080"}} + + with with_host(cephadm_module, 'test'): + with with_service(cephadm_module, AlertManagerSpec()): + y = dedent(self._get_config("http://mgr.test.fqdn:8080")).lstrip() + _run_cephadm.assert_called_with( + 'test', + 'alertmanager.test', + 'deploy', + [ + '--name', 'alertmanager.test', + '--meta-json', + '{"service_name": "alertmanager", "ports": [9093, 9094], "ip": null, "deployed_by": [], "rank": null, "rank_generation": null, "extra_container_args": null}', + '--config-json', '-', '--tcp-ports', '9093 9094' + ], + stdin=json.dumps({"files": {"alertmanager.yml": y}, "peers": []}), + image='') + + @patch("cephadm.serve.CephadmServe._run_cephadm") + @patch("mgr_module.MgrModule.get") + def test_alertmanager_config_v4(self, mock_get, _run_cephadm, cephadm_module: CephadmOrchestrator): + dashboard_url = "http://192.168.0.123:8080" + _run_cephadm.return_value = ('{}', '', 0) + mock_get.return_value = {"services": {"dashboard": dashboard_url}} + + with with_host(cephadm_module, 'test'): + with with_service(cephadm_module, AlertManagerSpec()): + y = dedent(self._get_config(dashboard_url)).lstrip() + _run_cephadm.assert_called_with( + 'test', + 'alertmanager.test', + 'deploy', + [ + '--name', 'alertmanager.test', + '--meta-json', '{"service_name": "alertmanager", "ports": [9093, 9094], "ip": null, "deployed_by": [], "rank": null, "rank_generation": null, "extra_container_args": null}', + '--config-json', '-', '--tcp-ports', '9093 9094' + ], + stdin=json.dumps({"files": {"alertmanager.yml": y}, "peers": []}), + image='') + + @patch("cephadm.serve.CephadmServe._run_cephadm") + @patch("mgr_module.MgrModule.get") + @patch("socket.getfqdn") + def test_alertmanager_config_v4_fqdn(self, mock_getfqdn, mock_get, _run_cephadm, + cephadm_module: CephadmOrchestrator): + _run_cephadm.return_value = ('{}', '', 0) + mock_getfqdn.return_value = "mgr.test.fqdn" + mock_get.return_value = {"services": {"dashboard": "http://192.168.0.123:8080"}} + + with with_host(cephadm_module, 'test'): + with with_service(cephadm_module, AlertManagerSpec()): + y = dedent(self._get_config("http://mgr.test.fqdn:8080")).lstrip() + _run_cephadm.assert_called_with( + 'test', + 'alertmanager.test', + 'deploy', + [ + '--name', 'alertmanager.test', + '--meta-json', + '{"service_name": "alertmanager", "ports": [9093, 9094], "ip": null, "deployed_by": [], "rank": null, "rank_generation": null, "extra_container_args": null}', + '--config-json', '-', '--tcp-ports', '9093 9094' + ], + stdin=json.dumps({"files": {"alertmanager.yml": y}, "peers": []}), + image='') + + @patch("cephadm.serve.CephadmServe._run_cephadm") + def test_prometheus_config(self, _run_cephadm, cephadm_module: CephadmOrchestrator): + _run_cephadm.return_value = ('{}', '', 0) + + with with_host(cephadm_module, 'test'): + with with_service(cephadm_module, MonitoringSpec('node-exporter')) as _, \ + with_service(cephadm_module, MonitoringSpec('prometheus')) as _: + + y = dedent(""" + # This file is generated by cephadm. + global: + scrape_interval: 10s + evaluation_interval: 10s + rule_files: + - /etc/prometheus/alerting/* + scrape_configs: + - job_name: 'ceph' + honor_labels: true + static_configs: + - targets: + - '[::1]:9283' + + - job_name: 'node' + static_configs: + - targets: ['[1::4]:9100'] + labels: + instance: 'test' + + """).lstrip() + + _run_cephadm.assert_called_with( + 'test', + 'prometheus.test', + 'deploy', + [ + '--name', 'prometheus.test', + '--meta-json', + '{"service_name": "prometheus", "ports": [9095], "ip": null, "deployed_by": [], "rank": null, "rank_generation": null, "extra_container_args": null}', + '--config-json', '-', + '--tcp-ports', '9095' + ], + stdin=json.dumps({"files": {"prometheus.yml": y, + "/etc/prometheus/alerting/custom_alerts.yml": ""}, + 'retention_time': '15d'}), + image='') + + @patch("cephadm.serve.CephadmServe._run_cephadm") + @patch("cephadm.module.CephadmOrchestrator.get_mgr_ip", lambda _: '1::4') + @patch("cephadm.services.monitoring.verify_tls", lambda *_: None) + def test_grafana_config(self, _run_cephadm, cephadm_module: CephadmOrchestrator): + _run_cephadm.return_value = ('{}', '', 0) + + with with_host(cephadm_module, 'test'): + cephadm_module.set_store('test/grafana_crt', 'c') + cephadm_module.set_store('test/grafana_key', 'k') + with with_service(cephadm_module, MonitoringSpec('prometheus')) as _, \ + with_service(cephadm_module, GrafanaSpec('grafana')) as _: + files = { + 'grafana.ini': dedent(""" + # This file is generated by cephadm. + [users] + default_theme = light + [auth.anonymous] + enabled = true + org_name = 'Main Org.' + org_role = 'Viewer' + [server] + domain = 'bootstrap.storage.lab' + protocol = https + cert_file = /etc/grafana/certs/cert_file + cert_key = /etc/grafana/certs/cert_key + http_port = 3000 + http_addr = + [security] + disable_initial_admin_creation = true + cookie_secure = true + cookie_samesite = none + allow_embedding = true""").lstrip(), # noqa: W291 + 'provisioning/datasources/ceph-dashboard.yml': dedent(""" + # This file is generated by cephadm. + deleteDatasources: + - name: 'Dashboard1' + orgId: 1 + + datasources: + - name: 'Dashboard1' + type: 'prometheus' + access: 'proxy' + orgId: 1 + url: 'http://[1::4]:9095' + basicAuth: false + isDefault: true + editable: false + """).lstrip(), + 'certs/cert_file': dedent(""" + # generated by cephadm + c""").lstrip(), + 'certs/cert_key': dedent(""" + # generated by cephadm + k""").lstrip(), + } + + _run_cephadm.assert_called_with( + 'test', + 'grafana.test', + 'deploy', + [ + '--name', 'grafana.test', + '--meta-json', + '{"service_name": "grafana", "ports": [3000], "ip": null, "deployed_by": [], "rank": null, "rank_generation": null, "extra_container_args": null}', + '--config-json', '-', '--tcp-ports', '3000'], + stdin=json.dumps({"files": files}), + image='') + + @patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) + def test_grafana_initial_admin_pw(self, cephadm_module: CephadmOrchestrator): + with with_host(cephadm_module, 'test'): + with with_service(cephadm_module, GrafanaSpec(initial_admin_password='secure')): + out = cephadm_module.cephadm_services['grafana'].generate_config( + CephadmDaemonDeploySpec('test', 'daemon', 'grafana')) + assert out == ( + { + 'files': + { + 'certs/cert_file': ANY, + 'certs/cert_key': ANY, + 'grafana.ini': + '# This file is generated by cephadm.\n' + '[users]\n' + ' default_theme = light\n' + '[auth.anonymous]\n' + ' enabled = true\n' + " org_name = 'Main Org.'\n" + " org_role = 'Viewer'\n" + '[server]\n' + " domain = 'bootstrap.storage.lab'\n" + ' protocol = https\n' + ' cert_file = /etc/grafana/certs/cert_file\n' + ' cert_key = /etc/grafana/certs/cert_key\n' + ' http_port = 3000\n' + ' http_addr = \n' + '[security]\n' + ' admin_user = admin\n' + ' admin_password = secure\n' + ' cookie_secure = true\n' + ' cookie_samesite = none\n' + ' allow_embedding = true', + 'provisioning/datasources/ceph-dashboard.yml': + '# This file is generated by cephadm.\n' + 'deleteDatasources:\n' + '\n' + 'datasources:\n' + } + }, + [], + ) + + @patch("cephadm.serve.CephadmServe._run_cephadm") + def test_monitoring_ports(self, _run_cephadm, cephadm_module: CephadmOrchestrator): + _run_cephadm.return_value = ('{}', '', 0) + + with with_host(cephadm_module, 'test'): + + yaml_str = """service_type: alertmanager +service_name: alertmanager +placement: + count: 1 +spec: + port: 4200 +""" + yaml_file = yaml.safe_load(yaml_str) + spec = ServiceSpec.from_json(yaml_file) + + with patch("cephadm.services.monitoring.AlertmanagerService.generate_config", return_value=({}, [])): + with with_service(cephadm_module, spec): + + CephadmServe(cephadm_module)._check_daemons() + + _run_cephadm.assert_called_with( + 'test', 'alertmanager.test', 'deploy', [ + '--name', 'alertmanager.test', + '--meta-json', '{"service_name": "alertmanager", "ports": [4200, 9094], "ip": null, "deployed_by": [], "rank": null, "rank_generation": null, "extra_container_args": null}', + '--config-json', '-', + '--tcp-ports', '4200 9094', + '--reconfig' + ], + stdin='{}', + image='') + + +class TestRGWService: + + @pytest.mark.parametrize( + "frontend, ssl, expected", + [ + ('beast', False, 'beast endpoint=[fd00:fd00:fd00:3000::1]:80'), + ('beast', True, + 'beast ssl_endpoint=[fd00:fd00:fd00:3000::1]:443 ssl_certificate=config://rgw/cert/rgw.foo'), + ('civetweb', False, 'civetweb port=[fd00:fd00:fd00:3000::1]:80'), + ('civetweb', True, + 'civetweb port=[fd00:fd00:fd00:3000::1]:443s ssl_certificate=config://rgw/cert/rgw.foo'), + ] + ) + @patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) + def test_rgw_update(self, frontend, ssl, expected, cephadm_module: CephadmOrchestrator): + with with_host(cephadm_module, 'host1'): + cephadm_module.cache.update_host_devices_networks( + 'host1', + dls=cephadm_module.cache.devices['host1'], + nets={ + 'fd00:fd00:fd00:3000::/64': { + 'if0': ['fd00:fd00:fd00:3000::1'] + } + }) + s = RGWSpec(service_id="foo", + networks=['fd00:fd00:fd00:3000::/64'], + ssl=ssl, + rgw_frontend_type=frontend) + with with_service(cephadm_module, s) as dds: + _, f, _ = cephadm_module.check_mon_command({ + 'prefix': 'config get', + 'who': f'client.{dds[0]}', + 'key': 'rgw_frontends', + }) + assert f == expected + + +class TestSNMPGateway: + + @patch("cephadm.serve.CephadmServe._run_cephadm") + def test_snmp_v2c_deployment(self, _run_cephadm, cephadm_module: CephadmOrchestrator): + _run_cephadm.return_value = ('{}', '', 0) + + spec = SNMPGatewaySpec( + snmp_version='V2c', + snmp_destination='192.168.1.1:162', + credentials={ + 'snmp_community': 'public' + }) + + config = { + "destination": spec.snmp_destination, + "snmp_version": spec.snmp_version, + "snmp_community": spec.credentials.get('snmp_community') + } + + with with_host(cephadm_module, 'test'): + with with_service(cephadm_module, spec): + _run_cephadm.assert_called_with( + 'test', + 'snmp-gateway.test', + 'deploy', + [ + '--name', 'snmp-gateway.test', + '--meta-json', + '{"service_name": "snmp-gateway", "ports": [9464], "ip": null, "deployed_by": [], "rank": null, "rank_generation": null, "extra_container_args": null}', + '--config-json', '-', + '--tcp-ports', '9464' + ], + stdin=json.dumps(config), + image='' + ) + + @patch("cephadm.serve.CephadmServe._run_cephadm") + def test_snmp_v2c_with_port(self, _run_cephadm, cephadm_module: CephadmOrchestrator): + _run_cephadm.return_value = ('{}', '', 0) + + spec = SNMPGatewaySpec( + snmp_version='V2c', + snmp_destination='192.168.1.1:162', + credentials={ + 'snmp_community': 'public' + }, + port=9465) + + config = { + "destination": spec.snmp_destination, + "snmp_version": spec.snmp_version, + "snmp_community": spec.credentials.get('snmp_community') + } + + with with_host(cephadm_module, 'test'): + with with_service(cephadm_module, spec): + _run_cephadm.assert_called_with( + 'test', + 'snmp-gateway.test', + 'deploy', + [ + '--name', 'snmp-gateway.test', + '--meta-json', + '{"service_name": "snmp-gateway", "ports": [9465], "ip": null, "deployed_by": [], "rank": null, "rank_generation": null, "extra_container_args": null}', + '--config-json', '-', + '--tcp-ports', '9465' + ], + stdin=json.dumps(config), + image='' + ) + + @patch("cephadm.serve.CephadmServe._run_cephadm") + def test_snmp_v3nopriv_deployment(self, _run_cephadm, cephadm_module: CephadmOrchestrator): + _run_cephadm.return_value = ('{}', '', 0) + + spec = SNMPGatewaySpec( + snmp_version='V3', + snmp_destination='192.168.1.1:162', + engine_id='8000C53F00000000', + credentials={ + 'snmp_v3_auth_username': 'myuser', + 'snmp_v3_auth_password': 'mypassword' + }) + + config = { + 'destination': spec.snmp_destination, + 'snmp_version': spec.snmp_version, + 'snmp_v3_auth_protocol': 'SHA', + 'snmp_v3_auth_username': 'myuser', + 'snmp_v3_auth_password': 'mypassword', + 'snmp_v3_engine_id': '8000C53F00000000' + } + + with with_host(cephadm_module, 'test'): + with with_service(cephadm_module, spec): + _run_cephadm.assert_called_with( + 'test', + 'snmp-gateway.test', + 'deploy', + [ + '--name', 'snmp-gateway.test', + '--meta-json', + '{"service_name": "snmp-gateway", "ports": [9464], "ip": null, "deployed_by": [], "rank": null, "rank_generation": null, "extra_container_args": null}', + '--config-json', '-', + '--tcp-ports', '9464' + ], + stdin=json.dumps(config), + image='' + ) + + @patch("cephadm.serve.CephadmServe._run_cephadm") + def test_snmp_v3priv_deployment(self, _run_cephadm, cephadm_module: CephadmOrchestrator): + _run_cephadm.return_value = ('{}', '', 0) + + spec = SNMPGatewaySpec( + snmp_version='V3', + snmp_destination='192.168.1.1:162', + engine_id='8000C53F00000000', + auth_protocol='MD5', + privacy_protocol='AES', + credentials={ + 'snmp_v3_auth_username': 'myuser', + 'snmp_v3_auth_password': 'mypassword', + 'snmp_v3_priv_password': 'mysecret', + }) + + config = { + 'destination': spec.snmp_destination, + 'snmp_version': spec.snmp_version, + 'snmp_v3_auth_protocol': 'MD5', + 'snmp_v3_auth_username': spec.credentials.get('snmp_v3_auth_username'), + 'snmp_v3_auth_password': spec.credentials.get('snmp_v3_auth_password'), + 'snmp_v3_engine_id': '8000C53F00000000', + 'snmp_v3_priv_protocol': spec.privacy_protocol, + 'snmp_v3_priv_password': spec.credentials.get('snmp_v3_priv_password'), + } + + with with_host(cephadm_module, 'test'): + with with_service(cephadm_module, spec): + _run_cephadm.assert_called_with( + 'test', + 'snmp-gateway.test', + 'deploy', + [ + '--name', 'snmp-gateway.test', + '--meta-json', + '{"service_name": "snmp-gateway", "ports": [9464], "ip": null, "deployed_by": [], "rank": null, "rank_generation": null, "extra_container_args": null}', + '--config-json', '-', + '--tcp-ports', '9464' + ], + stdin=json.dumps(config), + image='' + ) + + +class TestIngressService: + + @patch("cephadm.serve.CephadmServe._run_cephadm") + def test_ingress_config(self, _run_cephadm, cephadm_module: CephadmOrchestrator): + _run_cephadm.return_value = ('{}', '', 0) + + with with_host(cephadm_module, 'test'): + cephadm_module.cache.update_host_devices_networks( + 'test', + cephadm_module.cache.devices['test'], + { + '1.2.3.0/24': { + 'if0': ['1.2.3.4/32'] + } + } + ) + + # the ingress backend + s = RGWSpec(service_id="foo", placement=PlacementSpec(count=1), + rgw_frontend_type='beast') + + ispec = IngressSpec(service_type='ingress', + service_id='test', + backend_service='rgw.foo', + frontend_port=8089, + monitor_port=8999, + monitor_user='admin', + monitor_password='12345', + keepalived_password='12345', + virtual_interface_networks=['1.2.3.0/24'], + virtual_ip="1.2.3.4/32") + with with_service(cephadm_module, s) as _, with_service(cephadm_module, ispec) as _: + # generate the keepalived conf based on the specified spec + keepalived_generated_conf = cephadm_module.cephadm_services['ingress'].keepalived_generate_config( + CephadmDaemonDeploySpec(host='test', daemon_id='ingress', service_name=ispec.service_name())) + + keepalived_expected_conf = { + 'files': + { + 'keepalived.conf': + '# This file is generated by cephadm.\n' + 'vrrp_script check_backend {\n ' + 'script "/usr/bin/curl http://localhost:8999/health"\n ' + 'weight -20\n ' + 'interval 2\n ' + 'rise 2\n ' + 'fall 2\n}\n\n' + 'vrrp_instance VI_0 {\n ' + 'state MASTER\n ' + 'priority 100\n ' + 'interface if0\n ' + 'virtual_router_id 50\n ' + 'advert_int 1\n ' + 'authentication {\n ' + 'auth_type PASS\n ' + 'auth_pass 12345\n ' + '}\n ' + 'unicast_src_ip 1::4\n ' + 'unicast_peer {\n ' + '}\n ' + 'virtual_ipaddress {\n ' + '1.2.3.4/32 dev if0\n ' + '}\n ' + 'track_script {\n ' + 'check_backend\n }\n' + '}\n' + } + } + + # check keepalived config + assert keepalived_generated_conf[0] == keepalived_expected_conf + + # generate the haproxy conf based on the specified spec + haproxy_generated_conf = cephadm_module.cephadm_services['ingress'].haproxy_generate_config( + CephadmDaemonDeploySpec(host='test', daemon_id='ingress', service_name=ispec.service_name())) + + haproxy_expected_conf = { + 'files': + { + 'haproxy.cfg': + '# This file is generated by cephadm.' + '\nglobal\n log ' + '127.0.0.1 local2\n ' + 'chroot /var/lib/haproxy\n ' + 'pidfile /var/lib/haproxy/haproxy.pid\n ' + 'maxconn 8000\n ' + 'daemon\n ' + 'stats socket /var/lib/haproxy/stats\n' + '\ndefaults\n ' + 'mode http\n ' + 'log global\n ' + 'option httplog\n ' + 'option dontlognull\n ' + 'option http-server-close\n ' + 'option forwardfor except 127.0.0.0/8\n ' + 'option redispatch\n ' + 'retries 3\n ' + 'timeout queue 20s\n ' + 'timeout connect 5s\n ' + 'timeout http-request 1s\n ' + 'timeout http-keep-alive 5s\n ' + 'timeout client 1s\n ' + 'timeout server 1s\n ' + 'timeout check 5s\n ' + 'maxconn 8000\n' + '\nfrontend stats\n ' + 'mode http\n ' + 'bind 1.2.3.4:8999\n ' + 'bind localhost:8999\n ' + 'stats enable\n ' + 'stats uri /stats\n ' + 'stats refresh 10s\n ' + 'stats auth admin:12345\n ' + 'http-request use-service prometheus-exporter if { path /metrics }\n ' + 'monitor-uri /health\n' + '\nfrontend frontend\n ' + 'bind 1.2.3.4:8089\n ' + 'default_backend backend\n\n' + 'backend backend\n ' + 'option forwardfor\n ' + 'balance static-rr\n ' + 'option httpchk HEAD / HTTP/1.0\n ' + 'server ' + haproxy_generated_conf[1][0] + ' 1::4:80 check weight 100\n' + } + } + + assert haproxy_generated_conf[0] == haproxy_expected_conf + + @patch("cephadm.serve.CephadmServe._run_cephadm") + def test_ingress_config_multi_vips(self, _run_cephadm, cephadm_module: CephadmOrchestrator): + _run_cephadm.return_value = ('{}', '', 0) + + with with_host(cephadm_module, 'test'): + cephadm_module.cache.update_host_devices_networks('test', [], { + '1.2.3.0/24': { + 'if0': ['1.2.3.4/32'] + } + }) + + # Check the ingress with multiple VIPs + s = RGWSpec(service_id="foo", placement=PlacementSpec(count=1), + rgw_frontend_type='beast') + + ispec = IngressSpec(service_type='ingress', + service_id='test', + backend_service='rgw.foo', + frontend_port=8089, + monitor_port=8999, + monitor_user='admin', + monitor_password='12345', + keepalived_password='12345', + virtual_interface_networks=['1.2.3.0/24'], + virtual_ips_list=["1.2.3.4/32"]) + with with_service(cephadm_module, s) as _, with_service(cephadm_module, ispec) as _: + # generate the keepalived conf based on the specified spec + # Test with only 1 IP on the list, as it will fail with more VIPS but only one host. + keepalived_generated_conf = cephadm_module.cephadm_services['ingress'].keepalived_generate_config( + CephadmDaemonDeploySpec(host='test', daemon_id='ingress', service_name=ispec.service_name())) + + keepalived_expected_conf = { + 'files': + { + 'keepalived.conf': + '# This file is generated by cephadm.\n' + 'vrrp_script check_backend {\n ' + 'script "/usr/bin/curl http://localhost:8999/health"\n ' + 'weight -20\n ' + 'interval 2\n ' + 'rise 2\n ' + 'fall 2\n}\n\n' + 'vrrp_instance VI_0 {\n ' + 'state MASTER\n ' + 'priority 100\n ' + 'interface if0\n ' + 'virtual_router_id 50\n ' + 'advert_int 1\n ' + 'authentication {\n ' + 'auth_type PASS\n ' + 'auth_pass 12345\n ' + '}\n ' + 'unicast_src_ip 1::4\n ' + 'unicast_peer {\n ' + '}\n ' + 'virtual_ipaddress {\n ' + '1.2.3.4/32 dev if0\n ' + '}\n ' + 'track_script {\n ' + 'check_backend\n }\n' + '}\n' + } + } + + # check keepalived config + assert keepalived_generated_conf[0] == keepalived_expected_conf + + # generate the haproxy conf based on the specified spec + haproxy_generated_conf = cephadm_module.cephadm_services['ingress'].haproxy_generate_config( + CephadmDaemonDeploySpec(host='test', daemon_id='ingress', service_name=ispec.service_name())) + + haproxy_expected_conf = { + 'files': + { + 'haproxy.cfg': + '# This file is generated by cephadm.' + '\nglobal\n log ' + '127.0.0.1 local2\n ' + 'chroot /var/lib/haproxy\n ' + 'pidfile /var/lib/haproxy/haproxy.pid\n ' + 'maxconn 8000\n ' + 'daemon\n ' + 'stats socket /var/lib/haproxy/stats\n' + '\ndefaults\n ' + 'mode http\n ' + 'log global\n ' + 'option httplog\n ' + 'option dontlognull\n ' + 'option http-server-close\n ' + 'option forwardfor except 127.0.0.0/8\n ' + 'option redispatch\n ' + 'retries 3\n ' + 'timeout queue 20s\n ' + 'timeout connect 5s\n ' + 'timeout http-request 1s\n ' + 'timeout http-keep-alive 5s\n ' + 'timeout client 1s\n ' + 'timeout server 1s\n ' + 'timeout check 5s\n ' + 'maxconn 8000\n' + '\nfrontend stats\n ' + 'mode http\n ' + 'bind *:8999\n ' + 'bind localhost:8999\n ' + 'stats enable\n ' + 'stats uri /stats\n ' + 'stats refresh 10s\n ' + 'stats auth admin:12345\n ' + 'http-request use-service prometheus-exporter if { path /metrics }\n ' + 'monitor-uri /health\n' + '\nfrontend frontend\n ' + 'bind *:8089\n ' + 'default_backend backend\n\n' + 'backend backend\n ' + 'option forwardfor\n ' + 'balance static-rr\n ' + 'option httpchk HEAD / HTTP/1.0\n ' + 'server ' + + haproxy_generated_conf[1][0] + ' 1::4:80 check weight 100\n' + } + } + + assert haproxy_generated_conf[0] == haproxy_expected_conf + + +class TestCephFsMirror: + @patch("cephadm.serve.CephadmServe._run_cephadm") + def test_config(self, _run_cephadm, cephadm_module: CephadmOrchestrator): + _run_cephadm.return_value = ('{}', '', 0) + with with_host(cephadm_module, 'test'): + with with_service(cephadm_module, ServiceSpec('cephfs-mirror')): + cephadm_module.assert_issued_mon_command({ + 'prefix': 'mgr module enable', + 'module': 'mirroring' + }) diff --git a/src/pybind/mgr/cephadm/tests/test_spec.py b/src/pybind/mgr/cephadm/tests/test_spec.py new file mode 100644 index 000000000..54aa0a7ab --- /dev/null +++ b/src/pybind/mgr/cephadm/tests/test_spec.py @@ -0,0 +1,600 @@ +# Disable autopep8 for this file: + +# fmt: off + +import json + +import pytest + +from ceph.deployment.service_spec import ServiceSpec, NFSServiceSpec, RGWSpec, \ + IscsiServiceSpec, HostPlacementSpec, CustomContainerSpec +from orchestrator import DaemonDescription, OrchestratorError + + +@pytest.mark.parametrize( + "spec_json", + json.loads("""[ +{ + "placement": { + "count": 1 + }, + "service_type": "alertmanager" +}, +{ + "placement": { + "host_pattern": "*" + }, + "service_type": "crash" +}, +{ + "placement": { + "count": 1 + }, + "service_type": "grafana" +}, +{ + "placement": { + "count": 2 + }, + "service_type": "mgr" +}, +{ + "placement": { + "count": 5 + }, + "service_type": "mon" +}, +{ + "placement": { + "host_pattern": "*" + }, + "service_type": "node-exporter" +}, +{ + "placement": { + "count": 1 + }, + "service_type": "prometheus" +}, +{ + "placement": { + "hosts": [ + { + "hostname": "ceph-001", + "network": "", + "name": "" + } + ] + }, + "service_type": "rgw", + "service_id": "default-rgw-realm.eu-central-1.1", + "rgw_realm": "default-rgw-realm", + "rgw_zone": "eu-central-1" +}, +{ + "service_type": "osd", + "service_id": "osd_spec_default", + "placement": { + "host_pattern": "*" + }, + "data_devices": { + "model": "MC-55-44-XZ" + }, + "db_devices": { + "model": "SSD-123-foo" + }, + "wal_devices": { + "model": "NVME-QQQQ-987" + } +} +] +""") +) +def test_spec_octopus(spec_json): + # https://tracker.ceph.com/issues/44934 + # Those are real user data from early octopus. + # Please do not modify those JSON values. + + spec = ServiceSpec.from_json(spec_json) + + # just some verification that we can sill read old octopus specs + def convert_to_old_style_json(j): + j_c = dict(j.copy()) + j_c.pop('service_name', None) + if 'spec' in j_c: + spec = j_c.pop('spec') + j_c.update(spec) + if 'placement' in j_c: + if 'hosts' in j_c['placement']: + j_c['placement']['hosts'] = [ + { + 'hostname': HostPlacementSpec.parse(h).hostname, + 'network': HostPlacementSpec.parse(h).network, + 'name': HostPlacementSpec.parse(h).name + } + for h in j_c['placement']['hosts'] + ] + j_c.pop('objectstore', None) + j_c.pop('filter_logic', None) + return j_c + + assert spec_json == convert_to_old_style_json(spec.to_json()) + + +@pytest.mark.parametrize( + "dd_json", + json.loads("""[ + { + "hostname": "ceph-001", + "container_id": "d94d7969094d", + "container_image_id": "0881eb8f169f5556a292b4e2c01d683172b12830a62a9225a98a8e206bb734f0", + "container_image_name": "docker.io/prom/alertmanager:latest", + "daemon_id": "ceph-001", + "daemon_type": "alertmanager", + "version": "0.20.0", + "status": 1, + "status_desc": "running", + "last_refresh": "2020-04-03T15:31:48.725856", + "created": "2020-04-02T19:23:08.829543", + "started": "2020-04-03T07:29:16.932838", + "is_active": false + }, + { + "hostname": "ceph-001", + "container_id": "c4b036202241", + "container_image_id": "204a01f9b0b6710dd0c0af7f37ce7139c47ff0f0105d778d7104c69282dfbbf1", + "container_image_name": "docker.io/ceph/ceph:v15", + "daemon_id": "ceph-001", + "daemon_type": "crash", + "version": "15.2.0", + "status": 1, + "status_desc": "running", + "last_refresh": "2020-04-03T15:31:48.725903", + "created": "2020-04-02T19:23:11.390694", + "started": "2020-04-03T07:29:16.910897", + "is_active": false + }, + { + "hostname": "ceph-001", + "container_id": "5b7b94b48f31", + "container_image_id": "87a51ecf0b1c9a7b187b21c1b071425dafea0d765a96d5bc371c791169b3d7f4", + "container_image_name": "docker.io/ceph/ceph-grafana:latest", + "daemon_id": "ceph-001", + "daemon_type": "grafana", + "version": "6.6.2", + "status": 1, + "status_desc": "running", + "last_refresh": "2020-04-03T15:31:48.725950", + "created": "2020-04-02T19:23:52.025088", + "started": "2020-04-03T07:29:16.847972", + "is_active": false + }, + { + "hostname": "ceph-001", + "container_id": "9ca007280456", + "container_image_id": "204a01f9b0b6710dd0c0af7f37ce7139c47ff0f0105d778d7104c69282dfbbf1", + "container_image_name": "docker.io/ceph/ceph:v15", + "daemon_id": "ceph-001.gkjwqp", + "daemon_type": "mgr", + "version": "15.2.0", + "status": 1, + "status_desc": "running", + "last_refresh": "2020-04-03T15:31:48.725807", + "created": "2020-04-02T19:22:18.648584", + "started": "2020-04-03T07:29:16.856153", + "is_active": false + }, + { + "hostname": "ceph-001", + "container_id": "3d1ba9a2b697", + "container_image_id": "204a01f9b0b6710dd0c0af7f37ce7139c47ff0f0105d778d7104c69282dfbbf1", + "container_image_name": "docker.io/ceph/ceph:v15", + "daemon_id": "ceph-001", + "daemon_type": "mon", + "version": "15.2.0", + "status": 1, + "status_desc": "running", + "last_refresh": "2020-04-03T15:31:48.725715", + "created": "2020-04-02T19:22:13.863300", + "started": "2020-04-03T07:29:17.206024", + "is_active": false + }, + { + "hostname": "ceph-001", + "container_id": "36d026c68ba1", + "container_image_id": "e5a616e4b9cf68dfcad7782b78e118be4310022e874d52da85c55923fb615f87", + "container_image_name": "docker.io/prom/node-exporter:latest", + "daemon_id": "ceph-001", + "daemon_type": "node-exporter", + "version": "0.18.1", + "status": 1, + "status_desc": "running", + "last_refresh": "2020-04-03T15:31:48.725996", + "created": "2020-04-02T19:23:53.880197", + "started": "2020-04-03T07:29:16.880044", + "is_active": false + }, + { + "hostname": "ceph-001", + "container_id": "faf76193cbfe", + "container_image_id": "204a01f9b0b6710dd0c0af7f37ce7139c47ff0f0105d778d7104c69282dfbbf1", + "container_image_name": "docker.io/ceph/ceph:v15", + "daemon_id": "0", + "daemon_type": "osd", + "version": "15.2.0", + "status": 1, + "status_desc": "running", + "last_refresh": "2020-04-03T15:31:48.726088", + "created": "2020-04-02T20:35:02.991435", + "started": "2020-04-03T07:29:19.373956", + "is_active": false + }, + { + "hostname": "ceph-001", + "container_id": "f82505bae0f1", + "container_image_id": "204a01f9b0b6710dd0c0af7f37ce7139c47ff0f0105d778d7104c69282dfbbf1", + "container_image_name": "docker.io/ceph/ceph:v15", + "daemon_id": "1", + "daemon_type": "osd", + "version": "15.2.0", + "status": 1, + "status_desc": "running", + "last_refresh": "2020-04-03T15:31:48.726134", + "created": "2020-04-02T20:35:17.142272", + "started": "2020-04-03T07:29:19.374002", + "is_active": false + }, + { + "hostname": "ceph-001", + "container_id": "2708d84cd484", + "container_image_id": "358a0d2395fe711bb8258e8fb4b2d7865c0a9a6463969bcd1452ee8869ea6653", + "container_image_name": "docker.io/prom/prometheus:latest", + "daemon_id": "ceph-001", + "daemon_type": "prometheus", + "version": "2.17.1", + "status": 1, + "status_desc": "running", + "last_refresh": "2020-04-03T15:31:48.726042", + "created": "2020-04-02T19:24:10.281163", + "started": "2020-04-03T07:29:16.926292", + "is_active": false + }, + { + "hostname": "ceph-001", + "daemon_id": "default-rgw-realm.eu-central-1.1.ceph-001.ytywjo", + "daemon_type": "rgw", + "status": 1, + "status_desc": "starting", + "is_active": false + } +]""") +) +def test_dd_octopus(dd_json): + # https://tracker.ceph.com/issues/44934 + # Those are real user data from early octopus. + # Please do not modify those JSON values. + + # Convert datetime properties to old style. + # 2020-04-03T07:29:16.926292Z -> 2020-04-03T07:29:16.926292 + def convert_to_old_style_json(j): + for k in ['last_refresh', 'created', 'started', 'last_deployed', + 'last_configured']: + if k in j: + j[k] = j[k].rstrip('Z') + del j['daemon_name'] + return j + + assert dd_json == convert_to_old_style_json( + DaemonDescription.from_json(dd_json).to_json()) + + +@pytest.mark.parametrize("spec,dd,valid", +[ # noqa: E128 + # https://tracker.ceph.com/issues/44934 + ( + RGWSpec( + service_id="foo", + rgw_realm="default-rgw-realm", + rgw_zone="eu-central-1", + ), + DaemonDescription( + daemon_type='rgw', + daemon_id="foo.ceph-001.ytywjo", + hostname="ceph-001", + ), + True + ), + ( + # no realm + RGWSpec( + service_id="foo.bar", + rgw_zone="eu-central-1", + ), + DaemonDescription( + daemon_type='rgw', + daemon_id="foo.bar.ceph-001.ytywjo", + hostname="ceph-001", + ), + True + ), + ( + # no realm or zone + RGWSpec( + service_id="bar", + ), + DaemonDescription( + daemon_type='rgw', + daemon_id="bar.host.domain.tld.ytywjo", + hostname="host.domain.tld", + ), + True + ), + ( + # explicit naming + RGWSpec( + service_id="realm.zone", + ), + DaemonDescription( + daemon_type='rgw', + daemon_id="realm.zone.a", + hostname="smithi028", + ), + True + ), + ( + # without host + RGWSpec( + service_type='rgw', + service_id="foo", + ), + DaemonDescription( + daemon_type='rgw', + daemon_id="foo.hostname.ytywjo", + hostname=None, + ), + False + ), + ( + # without host (2) + RGWSpec( + service_type='rgw', + service_id="default-rgw-realm.eu-central-1.1", + ), + DaemonDescription( + daemon_type='rgw', + daemon_id="default-rgw-realm.eu-central-1.1.hostname.ytywjo", + hostname=None, + ), + False + ), + ( + # service_id contains hostname + # (sort of) https://tracker.ceph.com/issues/45294 + RGWSpec( + service_id="default.rgw.realm.ceph.001", + ), + DaemonDescription( + daemon_type='rgw', + daemon_id="default.rgw.realm.ceph.001.ceph.001.ytywjo", + hostname="ceph.001", + ), + True + ), + + # https://tracker.ceph.com/issues/45293 + ( + ServiceSpec( + service_type='mds', + service_id="a", + ), + DaemonDescription( + daemon_type='mds', + daemon_id="a.host1.abc123", + hostname="host1", + ), + True + ), + ( + # '.' char in service_id + ServiceSpec( + service_type='mds', + service_id="a.b.c", + ), + DaemonDescription( + daemon_type='mds', + daemon_id="a.b.c.host1.abc123", + hostname="host1", + ), + True + ), + + # https://tracker.ceph.com/issues/45617 + ( + # daemon_id does not contain hostname + ServiceSpec( + service_type='mds', + service_id="a", + ), + DaemonDescription( + daemon_type='mds', + daemon_id="a", + hostname="host1", + ), + True + ), + ( + # daemon_id only contains hostname + ServiceSpec( + service_type='mds', + service_id="host1", + ), + DaemonDescription( + daemon_type='mds', + daemon_id="host1", + hostname="host1", + ), + True + ), + + # https://tracker.ceph.com/issues/45399 + ( + # daemon_id only contains hostname + ServiceSpec( + service_type='mds', + service_id="a", + ), + DaemonDescription( + daemon_type='mds', + daemon_id="a.host1.abc123", + hostname="host1.site", + ), + True + ), + ( + NFSServiceSpec( + service_id="a", + ), + DaemonDescription( + daemon_type='nfs', + daemon_id="a.host1", + hostname="host1.site", + ), + True + ), + + # https://tracker.ceph.com/issues/45293 + ( + NFSServiceSpec( + service_id="a", + ), + DaemonDescription( + daemon_type='nfs', + daemon_id="a.host1", + hostname="host1", + ), + True + ), + ( + # service_id contains a '.' char + NFSServiceSpec( + service_id="a.b.c", + ), + DaemonDescription( + daemon_type='nfs', + daemon_id="a.b.c.host1", + hostname="host1", + ), + True + ), + ( + # trailing chars after hostname + NFSServiceSpec( + service_id="a.b.c", + ), + DaemonDescription( + daemon_type='nfs', + daemon_id="a.b.c.host1.abc123", + hostname="host1", + ), + True + ), + ( + # chars after hostname without '.' + NFSServiceSpec( + service_id="a", + ), + DaemonDescription( + daemon_type='nfs', + daemon_id="a.host1abc123", + hostname="host1", + ), + False + ), + ( + # chars before hostname without '.' + NFSServiceSpec( + service_id="a", + ), + DaemonDescription( + daemon_type='nfs', + daemon_id="ahost1.abc123", + hostname="host1", + ), + False + ), + + # https://tracker.ceph.com/issues/45293 + ( + IscsiServiceSpec( + service_type='iscsi', + service_id="a", + ), + DaemonDescription( + daemon_type='iscsi', + daemon_id="a.host1.abc123", + hostname="host1", + ), + True + ), + ( + # '.' char in service_id + IscsiServiceSpec( + service_type='iscsi', + service_id="a.b.c", + ), + DaemonDescription( + daemon_type='iscsi', + daemon_id="a.b.c.host1.abc123", + hostname="host1", + ), + True + ), + ( + # fixed daemon id for teuthology. + IscsiServiceSpec( + service_type='iscsi', + service_id='iscsi', + ), + DaemonDescription( + daemon_type='iscsi', + daemon_id="iscsi.a", + hostname="host1", + ), + True + ), + + ( + CustomContainerSpec( + service_type='container', + service_id='hello-world', + image='docker.io/library/hello-world:latest', + ), + DaemonDescription( + daemon_type='container', + daemon_id='hello-world.mgr0', + hostname='mgr0', + ), + True + ), + + ( + # daemon_id only contains hostname + ServiceSpec( + service_type='cephadm-exporter', + ), + DaemonDescription( + daemon_type='cephadm-exporter', + daemon_id="testhost", + hostname="testhost", + ), + True + ), +]) +def test_daemon_description_service_name(spec: ServiceSpec, + dd: DaemonDescription, + valid: bool): + if valid: + assert spec.service_name() == dd.service_name() + else: + with pytest.raises(OrchestratorError): + dd.service_name() diff --git a/src/pybind/mgr/cephadm/tests/test_template.py b/src/pybind/mgr/cephadm/tests/test_template.py new file mode 100644 index 000000000..f67304348 --- /dev/null +++ b/src/pybind/mgr/cephadm/tests/test_template.py @@ -0,0 +1,33 @@ +import pathlib + +import pytest + +from cephadm.template import TemplateMgr, UndefinedError, TemplateNotFoundError + + +def test_render(cephadm_module, fs): + template_base = (pathlib.Path(__file__).parent / '../templates').resolve() + fake_template = template_base / 'foo/bar' + fs.create_file(fake_template, contents='{{ cephadm_managed }}{{ var }}') + + template_mgr = TemplateMgr(cephadm_module) + value = 'test' + + # with base context + expected_text = '{}{}'.format(template_mgr.base_context['cephadm_managed'], value) + assert template_mgr.render('foo/bar', {'var': value}) == expected_text + + # without base context + with pytest.raises(UndefinedError): + template_mgr.render('foo/bar', {'var': value}, managed_context=False) + + # override the base context + context = { + 'cephadm_managed': 'abc', + 'var': value + } + assert template_mgr.render('foo/bar', context) == 'abc{}'.format(value) + + # template not found + with pytest.raises(TemplateNotFoundError): + template_mgr.render('foo/bar/2', {}) diff --git a/src/pybind/mgr/cephadm/tests/test_upgrade.py b/src/pybind/mgr/cephadm/tests/test_upgrade.py new file mode 100644 index 000000000..9368f4dc3 --- /dev/null +++ b/src/pybind/mgr/cephadm/tests/test_upgrade.py @@ -0,0 +1,322 @@ +import json +from unittest import mock + +import pytest + +from ceph.deployment.service_spec import PlacementSpec, ServiceSpec +from cephadm import CephadmOrchestrator +from cephadm.upgrade import CephadmUpgrade +from cephadm.serve import CephadmServe +from orchestrator import OrchestratorError, DaemonDescription +from .fixtures import _run_cephadm, wait, with_host, with_service + +from typing import List, Tuple, Optional + + +@mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) +def test_upgrade_start(cephadm_module: CephadmOrchestrator): + with with_host(cephadm_module, 'test'): + with with_host(cephadm_module, 'test2'): + with with_service(cephadm_module, ServiceSpec('mgr', placement=PlacementSpec(count=2)), status_running=True): + assert wait(cephadm_module, cephadm_module.upgrade_start( + 'image_id', None)) == 'Initiating upgrade to image_id' + + assert wait(cephadm_module, cephadm_module.upgrade_status() + ).target_image == 'image_id' + + assert wait(cephadm_module, cephadm_module.upgrade_pause() + ) == 'Paused upgrade to image_id' + + assert wait(cephadm_module, cephadm_module.upgrade_resume() + ) == 'Resumed upgrade to image_id' + + assert wait(cephadm_module, cephadm_module.upgrade_stop() + ) == 'Stopped upgrade to image_id' + + +@mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) +@pytest.mark.parametrize("use_repo_digest", + [ + False, + True + ]) +def test_upgrade_run(use_repo_digest, cephadm_module: CephadmOrchestrator): + with with_host(cephadm_module, 'host1'): + with with_host(cephadm_module, 'host2'): + cephadm_module.set_container_image('global', 'from_image') + cephadm_module.use_repo_digest = use_repo_digest + with with_service(cephadm_module, ServiceSpec('mgr', placement=PlacementSpec(host_pattern='*', count=2)), + CephadmOrchestrator.apply_mgr, '', status_running=True),\ + mock.patch("cephadm.module.CephadmOrchestrator.lookup_release_name", + return_value='foo'),\ + mock.patch("cephadm.module.CephadmOrchestrator.version", + new_callable=mock.PropertyMock) as version_mock,\ + mock.patch("cephadm.module.CephadmOrchestrator.get", + return_value={ + # capture fields in both mon and osd maps + "require_osd_release": "pacific", + "min_mon_release": 16, + }): + version_mock.return_value = 'ceph version 18.2.1 (somehash)' + assert wait(cephadm_module, cephadm_module.upgrade_start( + 'to_image', None)) == 'Initiating upgrade to to_image' + + assert wait(cephadm_module, cephadm_module.upgrade_status() + ).target_image == 'to_image' + + def _versions_mock(cmd): + return json.dumps({ + 'mgr': { + 'ceph version 1.2.3 (asdf) blah': 1 + } + }) + + cephadm_module._mon_command_mock_versions = _versions_mock + + with mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm(json.dumps({ + 'image_id': 'image_id', + 'repo_digests': ['to_image@repo_digest'], + 'ceph_version': 'ceph version 18.2.3 (hash)', + }))): + + cephadm_module.upgrade._do_upgrade() + + assert cephadm_module.upgrade_status is not None + + with mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm( + json.dumps([ + dict( + name=list(cephadm_module.cache.daemons['host1'].keys())[0], + style='cephadm', + fsid='fsid', + container_id='container_id', + container_image_id='image_id', + container_image_digests=['to_image@repo_digest'], + deployed_by=['to_image@repo_digest'], + version='version', + state='running', + ) + ]) + )): + CephadmServe(cephadm_module)._refresh_hosts_and_daemons() + + with mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm(json.dumps({ + 'image_id': 'image_id', + 'repo_digests': ['to_image@repo_digest'], + 'ceph_version': 'ceph version 18.2.3 (hash)', + }))): + cephadm_module.upgrade._do_upgrade() + + _, image, _ = cephadm_module.check_mon_command({ + 'prefix': 'config get', + 'who': 'global', + 'key': 'container_image', + }) + if use_repo_digest: + assert image == 'to_image@repo_digest' + else: + assert image == 'to_image' + + +def test_upgrade_state_null(cephadm_module: CephadmOrchestrator): + # This test validates https://tracker.ceph.com/issues/47580 + cephadm_module.set_store('upgrade_state', 'null') + CephadmUpgrade(cephadm_module) + assert CephadmUpgrade(cephadm_module).upgrade_state is None + + +@mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) +def test_not_enough_mgrs(cephadm_module: CephadmOrchestrator): + with with_host(cephadm_module, 'host1'): + with with_service(cephadm_module, ServiceSpec('mgr', placement=PlacementSpec(count=1)), CephadmOrchestrator.apply_mgr, ''): + with pytest.raises(OrchestratorError): + wait(cephadm_module, cephadm_module.upgrade_start('image_id', None)) + + +@mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) +@mock.patch("cephadm.CephadmOrchestrator.check_mon_command") +def test_enough_mons_for_ok_to_stop(check_mon_command, cephadm_module: CephadmOrchestrator): + # only 2 monitors, not enough for ok-to-stop to ever pass + check_mon_command.return_value = ( + 0, '{"monmap": {"mons": [{"name": "mon.1"}, {"name": "mon.2"}]}}', '') + assert not cephadm_module.upgrade._enough_mons_for_ok_to_stop() + + # 3 monitors, ok-to-stop should work fine + check_mon_command.return_value = ( + 0, '{"monmap": {"mons": [{"name": "mon.1"}, {"name": "mon.2"}, {"name": "mon.3"}]}}', '') + assert cephadm_module.upgrade._enough_mons_for_ok_to_stop() + + +@mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) +@mock.patch("cephadm.module.HostCache.get_daemons_by_service") +@mock.patch("cephadm.CephadmOrchestrator.get") +def test_enough_mds_for_ok_to_stop(get, get_daemons_by_service, cephadm_module: CephadmOrchestrator): + get.side_effect = [{'filesystems': [{'mdsmap': {'fs_name': 'test', 'max_mds': 1}}]}] + get_daemons_by_service.side_effect = [[DaemonDescription()]] + assert not cephadm_module.upgrade._enough_mds_for_ok_to_stop( + DaemonDescription(daemon_type='mds', daemon_id='test.host1.gfknd', service_name='mds.test')) + + get.side_effect = [{'filesystems': [{'mdsmap': {'fs_name': 'myfs.test', 'max_mds': 2}}]}] + get_daemons_by_service.side_effect = [[DaemonDescription(), DaemonDescription()]] + assert not cephadm_module.upgrade._enough_mds_for_ok_to_stop( + DaemonDescription(daemon_type='mds', daemon_id='myfs.test.host1.gfknd', service_name='mds.myfs.test')) + + get.side_effect = [{'filesystems': [{'mdsmap': {'fs_name': 'myfs.test', 'max_mds': 1}}]}] + get_daemons_by_service.side_effect = [[DaemonDescription(), DaemonDescription()]] + assert cephadm_module.upgrade._enough_mds_for_ok_to_stop( + DaemonDescription(daemon_type='mds', daemon_id='myfs.test.host1.gfknd', service_name='mds.myfs.test')) + + +@pytest.mark.parametrize( + "upgraded, not_upgraded, daemon_types, hosts, services, should_block", + # [ ([(type, host, id), ... ], [...], [daemon types], [hosts], [services], True/False), ... ] + [ + ( # valid, upgrade mgr daemons + [], + [('mgr', 'a', 'a.x'), ('mon', 'a', 'a')], + ['mgr'], + None, + None, + False + ), + ( # invalid, can't upgrade mons until mgr is upgraded + [], + [('mgr', 'a', 'a.x'), ('mon', 'a', 'a')], + ['mon'], + None, + None, + True + ), + ( # invalid, can't upgrade mon service until all mgr daemons are upgraded + [], + [('mgr', 'a', 'a.x'), ('mon', 'a', 'a')], + None, + None, + ['mon'], + True + ), + ( # valid, upgrade mgr service + [], + [('mgr', 'a', 'a.x'), ('mon', 'a', 'a')], + None, + None, + ['mgr'], + False + ), + ( # valid, mgr is already upgraded so can upgrade mons + [('mgr', 'a', 'a.x')], + [('mon', 'a', 'a')], + ['mon'], + None, + None, + False + ), + ( # invalid, can't upgrade all daemons on b b/c un-upgraded mgr on a + [], + [('mgr', 'b', 'b.y'), ('mon', 'a', 'a')], + None, + ['a'], + None, + True + ), + ( # valid, only daemon on b is a mgr + [], + [('mgr', 'a', 'a.x'), ('mgr', 'b', 'b.y'), ('mon', 'a', 'a')], + None, + ['b'], + None, + False + ), + ( # invalid, can't upgrade mon on a while mgr on b is un-upgraded + [], + [('mgr', 'a', 'a.x'), ('mgr', 'b', 'b.y'), ('mon', 'a', 'a')], + None, + ['a'], + None, + True + ), + ( # valid, only upgrading the mgr on a + [], + [('mgr', 'a', 'a.x'), ('mgr', 'b', 'b.y'), ('mon', 'a', 'a')], + ['mgr'], + ['a'], + None, + False + ), + ( # valid, mgr daemon not on b are upgraded + [('mgr', 'a', 'a.x')], + [('mgr', 'b', 'b.y'), ('mon', 'a', 'a')], + None, + ['b'], + None, + False + ), + ( # valid, all the necessary hosts are covered, mgr on c is already upgraded + [('mgr', 'c', 'c.z')], + [('mgr', 'a', 'a.x'), ('mgr', 'b', 'b.y'), ('mon', 'a', 'a'), ('osd', 'c', '0')], + None, + ['a', 'b'], + None, + False + ), + ( # invalid, can't upgrade mon on a while mgr on b is un-upgraded + [], + [('mgr', 'a', 'a.x'), ('mgr', 'b', 'b.y'), ('mon', 'a', 'a')], + ['mgr', 'mon'], + ['a'], + None, + True + ), + ( # valid, only mon not on "b" is upgraded already. Case hit while making teuthology test + [('mon', 'a', 'a')], + [('mon', 'b', 'x'), ('mon', 'b', 'y'), ('osd', 'a', '1'), ('osd', 'b', '2')], + ['mon', 'osd'], + ['b'], + None, + False + ), + ] +) +@mock.patch("cephadm.module.HostCache.get_daemons") +@mock.patch("cephadm.serve.CephadmServe._get_container_image_info") +@mock.patch('cephadm.module.SpecStore.__getitem__') +def test_staggered_upgrade_validation( + get_spec, + get_image_info, + get_daemons, + upgraded: List[Tuple[str, str, str]], + not_upgraded: List[Tuple[str, str, str, str]], + daemon_types: Optional[str], + hosts: Optional[str], + services: Optional[str], + should_block: bool, + cephadm_module: CephadmOrchestrator, +): + def to_dds(ts: List[Tuple[str, str]], upgraded: bool) -> List[DaemonDescription]: + dds = [] + digest = 'new_image@repo_digest' if upgraded else 'old_image@repo_digest' + for t in ts: + dds.append(DaemonDescription(daemon_type=t[0], + hostname=t[1], + daemon_id=t[2], + container_image_digests=[digest], + deployed_by=[digest],)) + return dds + get_daemons.return_value = to_dds(upgraded, True) + to_dds(not_upgraded, False) + get_image_info.return_value = ('new_id', 'ceph version 99.99.99 (hash)', ['new_image@repo_digest']) + + class FakeSpecDesc(): + def __init__(self, spec): + self.spec = spec + + def _get_spec(s): + return FakeSpecDesc(ServiceSpec(s)) + + get_spec.side_effect = _get_spec + if should_block: + with pytest.raises(OrchestratorError): + cephadm_module.upgrade._validate_upgrade_filters( + 'new_image_name', daemon_types, hosts, services) + else: + cephadm_module.upgrade._validate_upgrade_filters( + 'new_image_name', daemon_types, hosts, services) diff --git a/src/pybind/mgr/cephadm/upgrade.py b/src/pybind/mgr/cephadm/upgrade.py new file mode 100644 index 000000000..db39fe76a --- /dev/null +++ b/src/pybind/mgr/cephadm/upgrade.py @@ -0,0 +1,1167 @@ +import json +import logging +import time +import uuid +from typing import TYPE_CHECKING, Optional, Dict, List, Tuple, Any + +import orchestrator +from cephadm.registry import Registry +from cephadm.serve import CephadmServe +from cephadm.services.cephadmservice import CephadmDaemonDeploySpec +from cephadm.utils import ceph_release_to_major, name_to_config_section, CEPH_UPGRADE_ORDER, \ + MONITORING_STACK_TYPES, CEPH_TYPES, GATEWAY_TYPES +from orchestrator import OrchestratorError, DaemonDescription, DaemonDescriptionStatus, daemon_type_to_service + +if TYPE_CHECKING: + from .module import CephadmOrchestrator + + +logger = logging.getLogger(__name__) + +# from ceph_fs.h +CEPH_MDSMAP_ALLOW_STANDBY_REPLAY = (1 << 5) + + +def normalize_image_digest(digest: str, default_registry: str) -> str: + """ + Normal case: + >>> normalize_image_digest('ceph/ceph', 'docker.io') + 'docker.io/ceph/ceph' + + No change: + >>> normalize_image_digest('quay.ceph.io/ceph/ceph', 'docker.io') + 'quay.ceph.io/ceph/ceph' + + >>> normalize_image_digest('docker.io/ubuntu', 'docker.io') + 'docker.io/ubuntu' + + >>> normalize_image_digest('localhost/ceph', 'docker.io') + 'localhost/ceph' + """ + known_shortnames = [ + 'ceph/ceph', + 'ceph/daemon', + 'ceph/daemon-base', + ] + for image in known_shortnames: + if digest.startswith(image): + return f'{default_registry}/{digest}' + return digest + + +class UpgradeState: + def __init__(self, + target_name: str, + progress_id: str, + target_id: Optional[str] = None, + target_digests: Optional[List[str]] = None, + target_version: Optional[str] = None, + error: Optional[str] = None, + paused: Optional[bool] = None, + fs_original_max_mds: Optional[Dict[str, int]] = None, + fs_original_allow_standby_replay: Optional[Dict[str, bool]] = None, + daemon_types: Optional[List[str]] = None, + hosts: Optional[List[str]] = None, + services: Optional[List[str]] = None, + total_count: Optional[int] = None, + remaining_count: Optional[int] = None, + ): + self._target_name: str = target_name # Use CephadmUpgrade.target_image instead. + self.progress_id: str = progress_id + self.target_id: Optional[str] = target_id + self.target_digests: Optional[List[str]] = target_digests + self.target_version: Optional[str] = target_version + self.error: Optional[str] = error + self.paused: bool = paused or False + self.fs_original_max_mds: Optional[Dict[str, int]] = fs_original_max_mds + self.fs_original_allow_standby_replay: Optional[Dict[str, + bool]] = fs_original_allow_standby_replay + self.daemon_types = daemon_types + self.hosts = hosts + self.services = services + self.total_count = total_count + self.remaining_count = remaining_count + + def to_json(self) -> dict: + return { + 'target_name': self._target_name, + 'progress_id': self.progress_id, + 'target_id': self.target_id, + 'target_digests': self.target_digests, + 'target_version': self.target_version, + 'fs_original_max_mds': self.fs_original_max_mds, + 'fs_original_allow_standby_replay': self.fs_original_allow_standby_replay, + 'error': self.error, + 'paused': self.paused, + 'daemon_types': self.daemon_types, + 'hosts': self.hosts, + 'services': self.services, + 'total_count': self.total_count, + 'remaining_count': self.remaining_count, + } + + @classmethod + def from_json(cls, data: dict) -> Optional['UpgradeState']: + valid_params = UpgradeState.__init__.__code__.co_varnames + if data: + c = {k: v for k, v in data.items() if k in valid_params} + if 'repo_digest' in c: + c['target_digests'] = [c.pop('repo_digest')] + return cls(**c) + else: + return None + + +class CephadmUpgrade: + UPGRADE_ERRORS = [ + 'UPGRADE_NO_STANDBY_MGR', + 'UPGRADE_FAILED_PULL', + 'UPGRADE_REDEPLOY_DAEMON', + 'UPGRADE_BAD_TARGET_VERSION', + 'UPGRADE_EXCEPTION' + ] + + def __init__(self, mgr: "CephadmOrchestrator"): + self.mgr = mgr + + t = self.mgr.get_store('upgrade_state') + if t: + self.upgrade_state: Optional[UpgradeState] = UpgradeState.from_json(json.loads(t)) + else: + self.upgrade_state = None + + @property + def target_image(self) -> str: + assert self.upgrade_state + if not self.mgr.use_repo_digest: + return self.upgrade_state._target_name + if not self.upgrade_state.target_digests: + return self.upgrade_state._target_name + + # FIXME: we assume the first digest is the best one to use + return self.upgrade_state.target_digests[0] + + def upgrade_status(self) -> orchestrator.UpgradeStatusSpec: + r = orchestrator.UpgradeStatusSpec() + if self.upgrade_state: + r.target_image = self.target_image + r.in_progress = True + r.progress, r.services_complete = self._get_upgrade_info() + r.is_paused = self.upgrade_state.paused + + if self.upgrade_state.daemon_types is not None: + which_str = f'Upgrading daemons of type(s) {",".join(self.upgrade_state.daemon_types)}' + if self.upgrade_state.hosts is not None: + which_str += f' on host(s) {",".join(self.upgrade_state.hosts)}' + elif self.upgrade_state.services is not None: + which_str = f'Upgrading daemons in service(s) {",".join(self.upgrade_state.services)}' + if self.upgrade_state.hosts is not None: + which_str += f' on host(s) {",".join(self.upgrade_state.hosts)}' + elif self.upgrade_state.hosts is not None: + which_str = f'Upgrading all daemons on host(s) {",".join(self.upgrade_state.hosts)}' + else: + which_str = 'Upgrading all daemon types on all hosts' + if self.upgrade_state.total_count is not None and self.upgrade_state.remaining_count is not None: + which_str += f'. Upgrade limited to {self.upgrade_state.total_count} daemons ({self.upgrade_state.remaining_count} remaining).' + r.which = which_str + + # accessing self.upgrade_info_str will throw an exception if it + # has not been set in _do_upgrade yet + try: + r.message = self.upgrade_info_str + except AttributeError: + pass + if self.upgrade_state.error: + r.message = 'Error: ' + self.upgrade_state.error + elif self.upgrade_state.paused: + r.message = 'Upgrade paused' + return r + + def _get_upgrade_info(self) -> Tuple[str, List[str]]: + if not self.upgrade_state or not self.upgrade_state.target_digests: + return '', [] + + daemons = self._get_filtered_daemons() + + if any(not d.container_image_digests for d in daemons if d.daemon_type == 'mgr'): + return '', [] + + completed_daemons = [(d.daemon_type, any(d in self.upgrade_state.target_digests for d in ( + d.container_image_digests or []))) for d in daemons if d.daemon_type] + + done = len([True for completion in completed_daemons if completion[1]]) + + completed_types = list(set([completion[0] for completion in completed_daemons if all( + c[1] for c in completed_daemons if c[0] == completion[0])])) + + return '%s/%s daemons upgraded' % (done, len(daemons)), completed_types + + def _get_filtered_daemons(self) -> List[DaemonDescription]: + # Return the set of daemons set to be upgraded with out current + # filtering parameters (or all daemons in upgrade order if no filtering + # parameter are set). + assert self.upgrade_state is not None + if self.upgrade_state.daemon_types is not None: + daemons = [d for d in self.mgr.cache.get_daemons( + ) if d.daemon_type in self.upgrade_state.daemon_types] + elif self.upgrade_state.services is not None: + daemons = [] + for service in self.upgrade_state.services: + daemons += self.mgr.cache.get_daemons_by_service(service) + else: + daemons = [d for d in self.mgr.cache.get_daemons( + ) if d.daemon_type in CEPH_UPGRADE_ORDER] + if self.upgrade_state.hosts is not None: + daemons = [d for d in daemons if d.hostname in self.upgrade_state.hosts] + return daemons + + def _get_current_version(self) -> Tuple[int, int, str]: + current_version = self.mgr.version.split('ceph version ')[1] + (current_major, current_minor, _) = current_version.split('-')[0].split('.', 2) + return (int(current_major), int(current_minor), current_version) + + def _check_target_version(self, version: str) -> Optional[str]: + try: + (major, minor, _) = version.split('.', 2) + assert int(minor) >= 0 + # patch might be a number or {number}-g{sha1} + except ValueError: + return 'version must be in the form X.Y.Z (e.g., 15.2.3)' + if int(major) < 15 or (int(major) == 15 and int(minor) < 2): + return 'cephadm only supports octopus (15.2.0) or later' + + # to far a jump? + current_version = self.mgr.version.split('ceph version ')[1] + (current_major, current_minor, _) = current_version.split('-')[0].split('.', 2) + if int(current_major) < int(major) - 2: + return f'ceph can only upgrade 1 or 2 major versions at a time; {current_version} -> {version} is too big a jump' + if int(current_major) > int(major): + return f'ceph cannot downgrade major versions (from {current_version} to {version})' + if int(current_major) == int(major): + if int(current_minor) > int(minor): + return f'ceph cannot downgrade to a {"rc" if minor == "1" else "dev"} release' + + # check mon min + monmap = self.mgr.get("mon_map") + mon_min = monmap.get("min_mon_release", 0) + if mon_min < int(major) - 2: + return f'min_mon_release ({mon_min}) < target {major} - 2; first complete an upgrade to an earlier release' + + # check osd min + osdmap = self.mgr.get("osd_map") + osd_min_name = osdmap.get("require_osd_release", "argonaut") + osd_min = ceph_release_to_major(osd_min_name) + if osd_min < int(major) - 2: + return f'require_osd_release ({osd_min_name} or {osd_min}) < target {major} - 2; first complete an upgrade to an earlier release' + + return None + + def upgrade_ls(self, image: Optional[str], tags: bool) -> Dict: + if not image: + image = self.mgr.container_image_base + reg_name, bare_image = image.split('/', 1) + reg = Registry(reg_name) + versions = [] + r: Dict[Any, Any] = { + "image": image, + "registry": reg_name, + "bare_image": bare_image, + } + + try: + ls = reg.get_tags(bare_image) + except ValueError as e: + raise OrchestratorError(f'{e}') + if not tags: + for t in ls: + if t[0] != 'v': + continue + v = t[1:].split('.') + if len(v) != 3: + continue + if '-' in v[2]: + continue + versions.append('.'.join(v)) + r["versions"] = sorted( + versions, + key=lambda k: list(map(int, k.split('.'))), + reverse=True + ) + else: + r["tags"] = sorted(ls) + return r + + def upgrade_start(self, image: str, version: str, daemon_types: Optional[List[str]] = None, + hosts: Optional[List[str]] = None, services: Optional[List[str]] = None, limit: Optional[int] = None) -> str: + if self.mgr.mode != 'root': + raise OrchestratorError('upgrade is not supported in %s mode' % ( + self.mgr.mode)) + if version: + version_error = self._check_target_version(version) + if version_error: + raise OrchestratorError(version_error) + target_name = self.mgr.container_image_base + ':v' + version + elif image: + target_name = normalize_image_digest(image, self.mgr.default_registry) + else: + raise OrchestratorError('must specify either image or version') + + if daemon_types is not None or services is not None or hosts is not None: + self._validate_upgrade_filters(target_name, daemon_types, hosts, services) + + if self.upgrade_state: + if self.upgrade_state._target_name != target_name: + raise OrchestratorError( + 'Upgrade to %s (not %s) already in progress' % + (self.upgrade_state._target_name, target_name)) + if self.upgrade_state.paused: + self.upgrade_state.paused = False + self._save_upgrade_state() + return 'Resumed upgrade to %s' % self.target_image + return 'Upgrade to %s in progress' % self.target_image + + running_mgr_count = len([daemon for daemon in self.mgr.cache.get_daemons_by_type( + 'mgr') if daemon.status == DaemonDescriptionStatus.running]) + + if running_mgr_count < 2: + raise OrchestratorError('Need at least 2 running mgr daemons for upgrade') + + self.mgr.log.info('Upgrade: Started with target %s' % target_name) + self.upgrade_state = UpgradeState( + target_name=target_name, + progress_id=str(uuid.uuid4()), + daemon_types=daemon_types, + hosts=hosts, + services=services, + total_count=limit, + remaining_count=limit, + ) + self._update_upgrade_progress(0.0) + self._save_upgrade_state() + self._clear_upgrade_health_checks() + self.mgr.event.set() + return 'Initiating upgrade to %s' % (target_name) + + def _validate_upgrade_filters(self, target_name: str, daemon_types: Optional[List[str]] = None, hosts: Optional[List[str]] = None, services: Optional[List[str]] = None) -> None: + def _latest_type(dtypes: List[str]) -> str: + # [::-1] gives the list in reverse + for daemon_type in CEPH_UPGRADE_ORDER[::-1]: + if daemon_type in dtypes: + return daemon_type + return '' + + def _get_earlier_daemons(dtypes: List[str], candidates: List[DaemonDescription]) -> List[DaemonDescription]: + # this function takes a list of daemon types and first finds the daemon + # type from that list that is latest in our upgrade order. Then, from + # that latest type, it filters the list of candidate daemons received + # for daemons with types earlier in the upgrade order than the latest + # type found earlier. That filtered list of daemons is returned. The + # purpose of this function is to help in finding daemons that must have + # already been upgraded for the given filtering parameters (--daemon-types, + # --services, --hosts) to be valid. + latest = _latest_type(dtypes) + if not latest: + return [] + earlier_types = '|'.join(CEPH_UPGRADE_ORDER).split(latest)[0].split('|')[:-1] + earlier_types = [t for t in earlier_types if t not in dtypes] + return [d for d in candidates if d.daemon_type in earlier_types] + + if self.upgrade_state: + raise OrchestratorError('Cannot set values for --daemon-types, --services or --hosts when upgrade already in progress.') + try: + target_id, target_version, target_digests = CephadmServe(self.mgr)._get_container_image_info(target_name) + except OrchestratorError as e: + raise OrchestratorError(f'Failed to pull {target_name}: {str(e)}') + # what we need to do here is build a list of daemons that must already be upgraded + # in order for the user's selection of daemons to upgrade to be valid. for example, + # if they say --daemon-types 'osd,mds' but mons have not been upgraded, we block. + daemons = [d for d in self.mgr.cache.get_daemons() if d.daemon_type not in MONITORING_STACK_TYPES] + err_msg_base = 'Cannot start upgrade. ' + # "dtypes" will later be filled in with the types of daemons that will be upgraded with the given parameters + dtypes = [] + if daemon_types is not None: + dtypes = daemon_types + if hosts is not None: + dtypes = [_latest_type(dtypes)] + other_host_daemons = [ + d for d in daemons if d.hostname is not None and d.hostname not in hosts] + daemons = _get_earlier_daemons(dtypes, other_host_daemons) + else: + daemons = _get_earlier_daemons(dtypes, daemons) + err_msg_base += 'Daemons with types earlier in upgrade order than given types need upgrading.\n' + elif services is not None: + # for our purposes here we can effectively convert our list of services into the + # set of daemon types the services contain. This works because we don't allow --services + # and --daemon-types at the same time and we only allow services of the same type + sspecs = [self.mgr.spec_store[s].spec for s in services if self.mgr.spec_store[s].spec is not None] + stypes = list(set([s.service_type for s in sspecs])) + if len(stypes) != 1: + raise OrchestratorError('Doing upgrade by service only support services of one type at ' + f'a time. Found service types: {stypes}') + for stype in stypes: + dtypes += orchestrator.service_to_daemon_types(stype) + dtypes = list(set(dtypes)) + if hosts is not None: + other_host_daemons = [ + d for d in daemons if d.hostname is not None and d.hostname not in hosts] + daemons = _get_earlier_daemons(dtypes, other_host_daemons) + else: + daemons = _get_earlier_daemons(dtypes, daemons) + err_msg_base += 'Daemons with types earlier in upgrade order than daemons from given services need upgrading.\n' + elif hosts is not None: + # hosts must be handled a bit differently. For this, we really need to find all the daemon types + # that reside on hosts in the list of hosts we will upgrade. Then take the type from + # that list that is latest in the upgrade order and check if any daemons on hosts not in the + # provided list of hosts have a daemon with a type earlier in the upgrade order that is not upgraded. + dtypes = list(set([d.daemon_type for d in daemons if d.daemon_type is not None and d.hostname in hosts])) + other_hosts_daemons = [d for d in daemons if d.hostname is not None and d.hostname not in hosts] + daemons = _get_earlier_daemons([_latest_type(dtypes)], other_hosts_daemons) + err_msg_base += 'Daemons with types earlier in upgrade order than daemons on given host need upgrading.\n' + need_upgrade_self, n1, n2, _ = self._detect_need_upgrade(daemons, target_digests) + if need_upgrade_self and ('mgr' not in dtypes or (daemon_types is None and services is None)): + # also report active mgr as needing to be upgraded. It is not included in the resulting list + # by default as it is treated special and handled via the need_upgrade_self bool + n1.insert(0, (self.mgr.mgr_service.get_active_daemon(self.mgr.cache.get_daemons_by_type('mgr')), True)) + if n1 or n2: + raise OrchestratorError(f'{err_msg_base}Please first upgrade ' + f'{", ".join(list(set([d[0].name() for d in n1] + [d[0].name() for d in n2])))}\n' + f'NOTE: Enforced upgrade order is: {" -> ".join(CEPH_TYPES + GATEWAY_TYPES)}') + + def upgrade_pause(self) -> str: + if not self.upgrade_state: + raise OrchestratorError('No upgrade in progress') + if self.upgrade_state.paused: + return 'Upgrade to %s already paused' % self.target_image + self.upgrade_state.paused = True + self.mgr.log.info('Upgrade: Paused upgrade to %s' % self.target_image) + self._save_upgrade_state() + return 'Paused upgrade to %s' % self.target_image + + def upgrade_resume(self) -> str: + if not self.upgrade_state: + raise OrchestratorError('No upgrade in progress') + if not self.upgrade_state.paused: + return 'Upgrade to %s not paused' % self.target_image + self.upgrade_state.paused = False + self.upgrade_state.error = '' + self.mgr.log.info('Upgrade: Resumed upgrade to %s' % self.target_image) + self._save_upgrade_state() + self.mgr.event.set() + return 'Resumed upgrade to %s' % self.target_image + + def upgrade_stop(self) -> str: + if not self.upgrade_state: + return 'No upgrade in progress' + if self.upgrade_state.progress_id: + self.mgr.remote('progress', 'complete', + self.upgrade_state.progress_id) + target_image = self.target_image + self.mgr.log.info('Upgrade: Stopped') + self.upgrade_state = None + self._save_upgrade_state() + self._clear_upgrade_health_checks() + self.mgr.event.set() + return 'Stopped upgrade to %s' % target_image + + def continue_upgrade(self) -> bool: + """ + Returns false, if nothing was done. + :return: + """ + if self.upgrade_state and not self.upgrade_state.paused: + try: + self._do_upgrade() + except Exception as e: + self._fail_upgrade('UPGRADE_EXCEPTION', { + 'severity': 'error', + 'summary': 'Upgrade: failed due to an unexpected exception', + 'count': 1, + 'detail': [f'Unexpected exception occurred during upgrade process: {str(e)}'], + }) + return False + return True + return False + + def _wait_for_ok_to_stop( + self, s: DaemonDescription, + known: Optional[List[str]] = None, # NOTE: output argument! + ) -> bool: + # only wait a little bit; the service might go away for something + assert s.daemon_type is not None + assert s.daemon_id is not None + tries = 4 + while tries > 0: + if not self.upgrade_state or self.upgrade_state.paused: + return False + + # setting force flag to retain old functionality. + # note that known is an output argument for ok_to_stop() + r = self.mgr.cephadm_services[daemon_type_to_service(s.daemon_type)].ok_to_stop([ + s.daemon_id], known=known, force=True) + + if not r.retval: + logger.info(f'Upgrade: {r.stdout}') + return True + logger.info(f'Upgrade: {r.stderr}') + + time.sleep(15) + tries -= 1 + return False + + def _clear_upgrade_health_checks(self) -> None: + for k in self.UPGRADE_ERRORS: + if k in self.mgr.health_checks: + del self.mgr.health_checks[k] + self.mgr.set_health_checks(self.mgr.health_checks) + + def _fail_upgrade(self, alert_id: str, alert: dict) -> None: + assert alert_id in self.UPGRADE_ERRORS + if not self.upgrade_state: + # this could happen if the user canceled the upgrade while we + # were doing something + return + + logger.error('Upgrade: Paused due to %s: %s' % (alert_id, + alert['summary'])) + self.upgrade_state.error = alert_id + ': ' + alert['summary'] + self.upgrade_state.paused = True + self._save_upgrade_state() + self.mgr.health_checks[alert_id] = alert + self.mgr.set_health_checks(self.mgr.health_checks) + + def _update_upgrade_progress(self, progress: float) -> None: + if not self.upgrade_state: + assert False, 'No upgrade in progress' + + if not self.upgrade_state.progress_id: + self.upgrade_state.progress_id = str(uuid.uuid4()) + self._save_upgrade_state() + self.mgr.remote('progress', 'update', self.upgrade_state.progress_id, + ev_msg='Upgrade to %s' % ( + self.upgrade_state.target_version or self.target_image + ), + ev_progress=progress, + add_to_ceph_s=True) + + def _save_upgrade_state(self) -> None: + if not self.upgrade_state: + self.mgr.set_store('upgrade_state', None) + return + self.mgr.set_store('upgrade_state', json.dumps(self.upgrade_state.to_json())) + + def get_distinct_container_image_settings(self) -> Dict[str, str]: + # get all distinct container_image settings + image_settings = {} + ret, out, err = self.mgr.check_mon_command({ + 'prefix': 'config dump', + 'format': 'json', + }) + config = json.loads(out) + for opt in config: + if opt['name'] == 'container_image': + image_settings[opt['section']] = opt['value'] + return image_settings + + def _prepare_for_mds_upgrade( + self, + target_major: str, + need_upgrade: List[DaemonDescription] + ) -> bool: + # scale down all filesystems to 1 MDS + assert self.upgrade_state + if not self.upgrade_state.fs_original_max_mds: + self.upgrade_state.fs_original_max_mds = {} + if not self.upgrade_state.fs_original_allow_standby_replay: + self.upgrade_state.fs_original_allow_standby_replay = {} + fsmap = self.mgr.get("fs_map") + continue_upgrade = True + for fs in fsmap.get('filesystems', []): + fscid = fs["id"] + mdsmap = fs["mdsmap"] + fs_name = mdsmap["fs_name"] + + # disable allow_standby_replay? + if mdsmap['flags'] & CEPH_MDSMAP_ALLOW_STANDBY_REPLAY: + self.mgr.log.info('Upgrade: Disabling standby-replay for filesystem %s' % ( + fs_name + )) + if fscid not in self.upgrade_state.fs_original_allow_standby_replay: + self.upgrade_state.fs_original_allow_standby_replay[fscid] = True + self._save_upgrade_state() + ret, out, err = self.mgr.check_mon_command({ + 'prefix': 'fs set', + 'fs_name': fs_name, + 'var': 'allow_standby_replay', + 'val': '0', + }) + continue_upgrade = False + continue + + # scale down this filesystem? + if mdsmap["max_mds"] > 1: + self.mgr.log.info('Upgrade: Scaling down filesystem %s' % ( + fs_name + )) + if fscid not in self.upgrade_state.fs_original_max_mds: + self.upgrade_state.fs_original_max_mds[fscid] = mdsmap['max_mds'] + self._save_upgrade_state() + ret, out, err = self.mgr.check_mon_command({ + 'prefix': 'fs set', + 'fs_name': fs_name, + 'var': 'max_mds', + 'val': '1', + }) + continue_upgrade = False + continue + + if not (mdsmap['in'] == [0] and len(mdsmap['up']) <= 1): + self.mgr.log.info('Upgrade: Waiting for fs %s to scale down to reach 1 MDS' % (fs_name)) + time.sleep(10) + continue_upgrade = False + continue + + if len(mdsmap['up']) == 0: + self.mgr.log.warning("Upgrade: No mds is up; continuing upgrade procedure to poke things in the right direction") + # This can happen because the current version MDS have + # incompatible compatsets; the mons will not do any promotions. + # We must upgrade to continue. + elif len(mdsmap['up']) > 0: + mdss = list(mdsmap['info'].values()) + assert len(mdss) == 1 + lone_mds = mdss[0] + if lone_mds['state'] != 'up:active': + self.mgr.log.info('Upgrade: Waiting for mds.%s to be up:active (currently %s)' % ( + lone_mds['name'], + lone_mds['state'], + )) + time.sleep(10) + continue_upgrade = False + continue + else: + assert False + + return continue_upgrade + + def _enough_mons_for_ok_to_stop(self) -> bool: + # type () -> bool + ret, out, err = self.mgr.check_mon_command({ + 'prefix': 'quorum_status', + }) + try: + j = json.loads(out) + except Exception: + raise OrchestratorError('failed to parse quorum status') + + mons = [m['name'] for m in j['monmap']['mons']] + return len(mons) > 2 + + def _enough_mds_for_ok_to_stop(self, mds_daemon: DaemonDescription) -> bool: + # type (DaemonDescription) -> bool + + # find fs this mds daemon belongs to + fsmap = self.mgr.get("fs_map") + for fs in fsmap.get('filesystems', []): + mdsmap = fs["mdsmap"] + fs_name = mdsmap["fs_name"] + + assert mds_daemon.daemon_id + if fs_name != mds_daemon.service_name().split('.', 1)[1]: + # wrong fs for this mds daemon + continue + + # get number of mds daemons for this fs + mds_count = len( + [daemon for daemon in self.mgr.cache.get_daemons_by_service(mds_daemon.service_name())]) + + # standby mds daemons for this fs? + if mdsmap["max_mds"] < mds_count: + return True + return False + + return True # if mds has no fs it should pass ok-to-stop + + def _detect_need_upgrade(self, daemons: List[DaemonDescription], target_digests: Optional[List[str]] = None) -> Tuple[bool, List[Tuple[DaemonDescription, bool]], List[Tuple[DaemonDescription, bool]], int]: + # this function takes a list of daemons and container digests. The purpose + # is to go through each daemon and check if the current container digests + # for that daemon match the target digests. The purpose being that we determine + # if a daemon is upgraded to a certain container image or not based on what + # container digests it has. By checking the current digests against the + # targets we can determine which daemons still need to be upgraded + need_upgrade_self = False + need_upgrade: List[Tuple[DaemonDescription, bool]] = [] + need_upgrade_deployer: List[Tuple[DaemonDescription, bool]] = [] + done = 0 + if target_digests is None: + target_digests = [] + for d in daemons: + assert d.daemon_type is not None + assert d.daemon_id is not None + assert d.hostname is not None + correct_digest = False + if (any(d in target_digests for d in (d.container_image_digests or [])) + or d.daemon_type in MONITORING_STACK_TYPES): + logger.debug('daemon %s.%s container digest correct' % ( + d.daemon_type, d.daemon_id)) + correct_digest = True + if any(d in target_digests for d in (d.deployed_by or [])): + logger.debug('daemon %s.%s deployed by correct version' % ( + d.daemon_type, d.daemon_id)) + done += 1 + continue + + if self.mgr.daemon_is_self(d.daemon_type, d.daemon_id): + logger.info('Upgrade: Need to upgrade myself (mgr.%s)' % + self.mgr.get_mgr_id()) + need_upgrade_self = True + continue + + if correct_digest: + logger.debug('daemon %s.%s not deployed by correct version' % ( + d.daemon_type, d.daemon_id)) + need_upgrade_deployer.append((d, True)) + else: + logger.debug('daemon %s.%s not correct (%s, %s, %s)' % ( + d.daemon_type, d.daemon_id, + d.container_image_name, d.container_image_digests, d.version)) + need_upgrade.append((d, False)) + + return (need_upgrade_self, need_upgrade, need_upgrade_deployer, done) + + def _to_upgrade(self, need_upgrade: List[Tuple[DaemonDescription, bool]], target_image: str) -> Tuple[bool, List[Tuple[DaemonDescription, bool]]]: + to_upgrade: List[Tuple[DaemonDescription, bool]] = [] + known_ok_to_stop: List[str] = [] + for d_entry in need_upgrade: + d = d_entry[0] + assert d.daemon_type is not None + assert d.daemon_id is not None + assert d.hostname is not None + + if not d.container_image_id: + if d.container_image_name == target_image: + logger.debug( + 'daemon %s has unknown container_image_id but has correct image name' % (d.name())) + continue + + if known_ok_to_stop: + if d.name() in known_ok_to_stop: + logger.info(f'Upgrade: {d.name()} is also safe to restart') + to_upgrade.append(d_entry) + continue + + if d.daemon_type == 'osd': + # NOTE: known_ok_to_stop is an output argument for + # _wait_for_ok_to_stop + if not self._wait_for_ok_to_stop(d, known_ok_to_stop): + return False, to_upgrade + + if d.daemon_type == 'mon' and self._enough_mons_for_ok_to_stop(): + if not self._wait_for_ok_to_stop(d, known_ok_to_stop): + return False, to_upgrade + + if d.daemon_type == 'mds' and self._enough_mds_for_ok_to_stop(d): + if not self._wait_for_ok_to_stop(d, known_ok_to_stop): + return False, to_upgrade + + to_upgrade.append(d_entry) + + # if we don't have a list of others to consider, stop now + if d.daemon_type in ['osd', 'mds', 'mon'] and not known_ok_to_stop: + break + return True, to_upgrade + + def _upgrade_daemons(self, to_upgrade: List[Tuple[DaemonDescription, bool]], target_image: str, target_digests: Optional[List[str]] = None) -> None: + assert self.upgrade_state is not None + num = 1 + if target_digests is None: + target_digests = [] + for d_entry in to_upgrade: + if self.upgrade_state.remaining_count is not None and self.upgrade_state.remaining_count <= 0 and not d_entry[1]: + self.mgr.log.info(f'Hit upgrade limit of {self.upgrade_state.total_count}. Stopping upgrade') + return + d = d_entry[0] + assert d.daemon_type is not None + assert d.daemon_id is not None + assert d.hostname is not None + + # make sure host has latest container image + out, errs, code = CephadmServe(self.mgr)._run_cephadm( + d.hostname, '', 'inspect-image', [], + image=target_image, no_fsid=True, error_ok=True) + if code or not any(d in target_digests for d in json.loads(''.join(out)).get('repo_digests', [])): + logger.info('Upgrade: Pulling %s on %s' % (target_image, + d.hostname)) + self.upgrade_info_str = 'Pulling %s image on host %s' % ( + target_image, d.hostname) + out, errs, code = CephadmServe(self.mgr)._run_cephadm( + d.hostname, '', 'pull', [], + image=target_image, no_fsid=True, error_ok=True) + if code: + self._fail_upgrade('UPGRADE_FAILED_PULL', { + 'severity': 'warning', + 'summary': 'Upgrade: failed to pull target image', + 'count': 1, + 'detail': [ + 'failed to pull %s on host %s' % (target_image, + d.hostname)], + }) + return + r = json.loads(''.join(out)) + if not any(d in target_digests for d in r.get('repo_digests', [])): + logger.info('Upgrade: image %s pull on %s got new digests %s (not %s), restarting' % ( + target_image, d.hostname, r['repo_digests'], target_digests)) + self.upgrade_info_str = 'Image %s pull on %s got new digests %s (not %s), restarting' % ( + target_image, d.hostname, r['repo_digests'], target_digests) + self.upgrade_state.target_digests = r['repo_digests'] + self._save_upgrade_state() + return + + self.upgrade_info_str = 'Currently upgrading %s daemons' % (d.daemon_type) + + if len(to_upgrade) > 1: + logger.info('Upgrade: Updating %s.%s (%d/%d)' % (d.daemon_type, d.daemon_id, num, min(len(to_upgrade), self.upgrade_state.remaining_count if self.upgrade_state.remaining_count is not None else 9999999))) + else: + logger.info('Upgrade: Updating %s.%s' % + (d.daemon_type, d.daemon_id)) + action = 'Upgrading' if not d_entry[1] else 'Redeploying' + try: + daemon_spec = CephadmDaemonDeploySpec.from_daemon_description(d) + self.mgr._daemon_action( + daemon_spec, + 'redeploy', + image=target_image if not d_entry[1] else None + ) + except Exception as e: + self._fail_upgrade('UPGRADE_REDEPLOY_DAEMON', { + 'severity': 'warning', + 'summary': f'{action} daemon {d.name()} on host {d.hostname} failed.', + 'count': 1, + 'detail': [ + f'Upgrade daemon: {d.name()}: {e}' + ], + }) + return + num += 1 + if self.upgrade_state.remaining_count is not None and not d_entry[1]: + self.upgrade_state.remaining_count -= 1 + self._save_upgrade_state() + + def _handle_need_upgrade_self(self, need_upgrade_self: bool, upgrading_mgrs: bool) -> None: + if need_upgrade_self: + try: + self.mgr.mgr_service.fail_over() + except OrchestratorError as e: + self._fail_upgrade('UPGRADE_NO_STANDBY_MGR', { + 'severity': 'warning', + 'summary': f'Upgrade: {e}', + 'count': 1, + 'detail': [ + 'The upgrade process needs to upgrade the mgr, ' + 'but it needs at least one standby to proceed.', + ], + }) + return + + return # unreachable code, as fail_over never returns + elif upgrading_mgrs: + if 'UPGRADE_NO_STANDBY_MGR' in self.mgr.health_checks: + del self.mgr.health_checks['UPGRADE_NO_STANDBY_MGR'] + self.mgr.set_health_checks(self.mgr.health_checks) + + def _set_container_images(self, daemon_type: str, target_image: str, image_settings: Dict[str, str]) -> None: + # push down configs + daemon_type_section = name_to_config_section(daemon_type) + if image_settings.get(daemon_type_section) != target_image: + logger.info('Upgrade: Setting container_image for all %s' % + daemon_type) + self.mgr.set_container_image(daemon_type_section, target_image) + to_clean = [] + for section in image_settings.keys(): + if section.startswith(name_to_config_section(daemon_type) + '.'): + to_clean.append(section) + if to_clean: + logger.debug('Upgrade: Cleaning up container_image for %s' % + to_clean) + for section in to_clean: + ret, image, err = self.mgr.check_mon_command({ + 'prefix': 'config rm', + 'name': 'container_image', + 'who': section, + }) + + def _complete_osd_upgrade(self, target_major: str, target_major_name: str) -> None: + osdmap = self.mgr.get("osd_map") + osd_min_name = osdmap.get("require_osd_release", "argonaut") + osd_min = ceph_release_to_major(osd_min_name) + if osd_min < int(target_major): + logger.info( + f'Upgrade: Setting require_osd_release to {target_major} {target_major_name}') + ret, _, err = self.mgr.check_mon_command({ + 'prefix': 'osd require-osd-release', + 'release': target_major_name, + }) + + def _complete_mds_upgrade(self) -> None: + assert self.upgrade_state is not None + if self.upgrade_state.fs_original_max_mds: + for fs in self.mgr.get("fs_map")['filesystems']: + fscid = fs["id"] + fs_name = fs['mdsmap']['fs_name'] + new_max = self.upgrade_state.fs_original_max_mds.get(fscid, 1) + if new_max > 1: + self.mgr.log.info('Upgrade: Scaling up filesystem %s max_mds to %d' % ( + fs_name, new_max + )) + ret, _, err = self.mgr.check_mon_command({ + 'prefix': 'fs set', + 'fs_name': fs_name, + 'var': 'max_mds', + 'val': str(new_max), + }) + + self.upgrade_state.fs_original_max_mds = {} + self._save_upgrade_state() + if self.upgrade_state.fs_original_allow_standby_replay: + for fs in self.mgr.get("fs_map")['filesystems']: + fscid = fs["id"] + fs_name = fs['mdsmap']['fs_name'] + asr = self.upgrade_state.fs_original_allow_standby_replay.get(fscid, False) + if asr: + self.mgr.log.info('Upgrade: Enabling allow_standby_replay on filesystem %s' % ( + fs_name + )) + ret, _, err = self.mgr.check_mon_command({ + 'prefix': 'fs set', + 'fs_name': fs_name, + 'var': 'allow_standby_replay', + 'val': '1' + }) + + self.upgrade_state.fs_original_allow_standby_replay = {} + self._save_upgrade_state() + + def _mark_upgrade_complete(self) -> None: + if not self.upgrade_state: + logger.debug('_mark_upgrade_complete upgrade already marked complete, exiting') + return + logger.info('Upgrade: Complete!') + if self.upgrade_state.progress_id: + self.mgr.remote('progress', 'complete', + self.upgrade_state.progress_id) + self.upgrade_state = None + self._save_upgrade_state() + + def _do_upgrade(self): + # type: () -> None + if not self.upgrade_state: + logger.debug('_do_upgrade no state, exiting') + return + + target_image = self.target_image + target_id = self.upgrade_state.target_id + target_digests = self.upgrade_state.target_digests + target_version = self.upgrade_state.target_version + + first = False + if not target_id or not target_version or not target_digests: + # need to learn the container hash + logger.info('Upgrade: First pull of %s' % target_image) + self.upgrade_info_str = 'Doing first pull of %s image' % (target_image) + try: + target_id, target_version, target_digests = CephadmServe(self.mgr)._get_container_image_info( + target_image) + except OrchestratorError as e: + self._fail_upgrade('UPGRADE_FAILED_PULL', { + 'severity': 'warning', + 'summary': 'Upgrade: failed to pull target image', + 'count': 1, + 'detail': [str(e)], + }) + return + if not target_version: + self._fail_upgrade('UPGRADE_FAILED_PULL', { + 'severity': 'warning', + 'summary': 'Upgrade: failed to pull target image', + 'count': 1, + 'detail': ['unable to extract ceph version from container'], + }) + return + self.upgrade_state.target_id = target_id + # extract the version portion of 'ceph version {version} ({sha1})' + self.upgrade_state.target_version = target_version.split(' ')[2] + self.upgrade_state.target_digests = target_digests + self._save_upgrade_state() + target_image = self.target_image + first = True + + if target_digests is None: + target_digests = [] + if target_version.startswith('ceph version '): + # tolerate/fix upgrade state from older version + self.upgrade_state.target_version = target_version.split(' ')[2] + target_version = self.upgrade_state.target_version + (target_major, _) = target_version.split('.', 1) + target_major_name = self.mgr.lookup_release_name(int(target_major)) + + if first: + logger.info('Upgrade: Target is version %s (%s)' % ( + target_version, target_major_name)) + logger.info('Upgrade: Target container is %s, digests %s' % ( + target_image, target_digests)) + + version_error = self._check_target_version(target_version) + if version_error: + self._fail_upgrade('UPGRADE_BAD_TARGET_VERSION', { + 'severity': 'error', + 'summary': f'Upgrade: cannot upgrade/downgrade to {target_version}', + 'count': 1, + 'detail': [version_error], + }) + return + + image_settings = self.get_distinct_container_image_settings() + + # Older monitors (pre-v16.2.5) asserted that FSMap::compat == + # MDSMap::compat for all fs. This is no longer the case beginning in + # v16.2.5. We must disable the sanity checks during upgrade. + # N.B.: we don't bother confirming the operator has not already + # disabled this or saving the config value. + self.mgr.check_mon_command({ + 'prefix': 'config set', + 'name': 'mon_mds_skip_sanity', + 'value': '1', + 'who': 'mon', + }) + + if self.upgrade_state.daemon_types is not None: + logger.debug(f'Filtering daemons to upgrade by daemon types: {self.upgrade_state.daemon_types}') + daemons = [d for d in self.mgr.cache.get_daemons() if d.daemon_type in self.upgrade_state.daemon_types] + elif self.upgrade_state.services is not None: + logger.debug(f'Filtering daemons to upgrade by services: {self.upgrade_state.daemon_types}') + daemons = [] + for service in self.upgrade_state.services: + daemons += self.mgr.cache.get_daemons_by_service(service) + else: + daemons = [d for d in self.mgr.cache.get_daemons() if d.daemon_type in CEPH_UPGRADE_ORDER] + if self.upgrade_state.hosts is not None: + logger.debug(f'Filtering daemons to upgrade by hosts: {self.upgrade_state.hosts}') + daemons = [d for d in daemons if d.hostname in self.upgrade_state.hosts] + upgraded_daemon_count: int = 0 + for daemon_type in CEPH_UPGRADE_ORDER: + if self.upgrade_state.remaining_count is not None and self.upgrade_state.remaining_count <= 0: + # we hit our limit and should end the upgrade + # except for cases where we only need to redeploy, but not actually upgrade + # the image (which we don't count towards our limit). This case only occurs with mgr + # and monitoring stack daemons. Additionally, this case is only valid if + # the active mgr is already upgraded. + if any(d in target_digests for d in self.mgr.get_active_mgr_digests()): + if daemon_type not in MONITORING_STACK_TYPES and daemon_type != 'mgr': + continue + else: + self._mark_upgrade_complete() + return + logger.debug('Upgrade: Checking %s daemons' % daemon_type) + daemons_of_type = [d for d in daemons if d.daemon_type == daemon_type] + + need_upgrade_self, need_upgrade, need_upgrade_deployer, done = self._detect_need_upgrade(daemons_of_type, target_digests) + upgraded_daemon_count += done + self._update_upgrade_progress(upgraded_daemon_count / len(daemons)) + + # make sure mgr and monitoring stack daemons are properly redeployed in staggered upgrade scenarios + if daemon_type == 'mgr' or daemon_type in MONITORING_STACK_TYPES: + if any(d in target_digests for d in self.mgr.get_active_mgr_digests()): + need_upgrade_names = [d[0].name() for d in need_upgrade] + [d[0].name() for d in need_upgrade_deployer] + dds = [d for d in self.mgr.cache.get_daemons_by_type(daemon_type) if d.name() not in need_upgrade_names] + need_upgrade_active, n1, n2, __ = self._detect_need_upgrade(dds, target_digests) + if not n1: + if not need_upgrade_self and need_upgrade_active: + need_upgrade_self = True + need_upgrade_deployer += n2 + else: + # no point in trying to redeploy with new version if active mgr is not on the new version + need_upgrade_deployer = [] + + if not need_upgrade_self: + # only after the mgr itself is upgraded can we expect daemons to have + # deployed_by == target_digests + need_upgrade += need_upgrade_deployer + + # prepare filesystems for daemon upgrades? + if ( + daemon_type == 'mds' + and need_upgrade + and not self._prepare_for_mds_upgrade(target_major, [d_entry[0] for d_entry in need_upgrade]) + ): + return + + if need_upgrade: + self.upgrade_info_str = 'Currently upgrading %s daemons' % (daemon_type) + + _continue, to_upgrade = self._to_upgrade(need_upgrade, target_image) + if not _continue: + return + self._upgrade_daemons(to_upgrade, target_image, target_digests) + if to_upgrade: + return + + self._handle_need_upgrade_self(need_upgrade_self, daemon_type == 'mgr') + + # following bits of _do_upgrade are for completing upgrade for given + # types. If we haven't actually finished upgrading all the daemons + # of this type, we should exit the loop here + _, n1, n2, _ = self._detect_need_upgrade(self.mgr.cache.get_daemons_by_type(daemon_type), target_digests) + if n1 or n2: + continue + + # complete mon upgrade? + if daemon_type == 'mon': + if not self.mgr.get("have_local_config_map"): + logger.info('Upgrade: Restarting mgr now that mons are running pacific') + need_upgrade_self = True + + self._handle_need_upgrade_self(need_upgrade_self, daemon_type == 'mgr') + + # make sure 'ceph versions' agrees + ret, out_ver, err = self.mgr.check_mon_command({ + 'prefix': 'versions', + }) + j = json.loads(out_ver) + for version, count in j.get(daemon_type, {}).items(): + short_version = version.split(' ')[2] + if short_version != target_version: + logger.warning( + 'Upgrade: %d %s daemon(s) are %s != target %s' % + (count, daemon_type, short_version, target_version)) + + self._set_container_images(daemon_type, target_image, image_settings) + + logger.debug('Upgrade: All %s daemons are up to date.' % daemon_type) + + # complete osd upgrade? + if daemon_type == 'osd': + self._complete_osd_upgrade(target_major, target_major_name) + + # complete mds upgrade? + if daemon_type == 'mds': + self._complete_mds_upgrade() + + logger.debug('Upgrade: Upgraded %s daemon(s).' % daemon_type) + + # clean up + logger.info('Upgrade: Finalizing container_image settings') + self.mgr.set_container_image('global', target_image) + + for daemon_type in CEPH_UPGRADE_ORDER: + ret, image, err = self.mgr.check_mon_command({ + 'prefix': 'config rm', + 'name': 'container_image', + 'who': name_to_config_section(daemon_type), + }) + + self.mgr.check_mon_command({ + 'prefix': 'config rm', + 'name': 'mon_mds_skip_sanity', + 'who': 'mon', + }) + + self._mark_upgrade_complete() + return diff --git a/src/pybind/mgr/cephadm/utils.py b/src/pybind/mgr/cephadm/utils.py new file mode 100644 index 000000000..a4940c361 --- /dev/null +++ b/src/pybind/mgr/cephadm/utils.py @@ -0,0 +1,136 @@ +import logging +import json +import socket +from enum import Enum +from functools import wraps +from typing import Optional, Callable, TypeVar, List, NewType, TYPE_CHECKING, Any, NamedTuple +from orchestrator import OrchestratorError + +if TYPE_CHECKING: + from cephadm import CephadmOrchestrator + +T = TypeVar('T') +logger = logging.getLogger(__name__) + +ConfEntity = NewType('ConfEntity', str) + + +class CephadmNoImage(Enum): + token = 1 + + +# ceph daemon types that use the ceph container image. +# NOTE: order important here as these are used for upgrade order +CEPH_TYPES = ['mgr', 'mon', 'crash', 'osd', 'mds', 'rgw', 'rbd-mirror', 'cephfs-mirror'] +GATEWAY_TYPES = ['iscsi', 'nfs'] +MONITORING_STACK_TYPES = ['node-exporter', 'prometheus', 'alertmanager', 'grafana'] +RESCHEDULE_FROM_OFFLINE_HOSTS_TYPES = ['nfs'] + +CEPH_UPGRADE_ORDER = CEPH_TYPES + GATEWAY_TYPES + MONITORING_STACK_TYPES + +# these daemon types use the ceph container image +CEPH_IMAGE_TYPES = CEPH_TYPES + ['iscsi', 'nfs'] + +# Used for _run_cephadm used for check-host etc that don't require an --image parameter +cephadmNoImage = CephadmNoImage.token + + +class ContainerInspectInfo(NamedTuple): + image_id: str + ceph_version: Optional[str] + repo_digests: Optional[List[str]] + + +def name_to_config_section(name: str) -> ConfEntity: + """ + Map from daemon names to ceph entity names (as seen in config) + """ + daemon_type = name.split('.', 1)[0] + if daemon_type in ['rgw', 'rbd-mirror', 'nfs', 'crash', 'iscsi']: + return ConfEntity('client.' + name) + elif daemon_type in ['mon', 'osd', 'mds', 'mgr', 'client']: + return ConfEntity(name) + else: + return ConfEntity('mon') + + +def forall_hosts(f: Callable[..., T]) -> Callable[..., List[T]]: + @wraps(f) + def forall_hosts_wrapper(*args: Any) -> List[T]: + from cephadm.module import CephadmOrchestrator + + # Some weired logic to make calling functions with multiple arguments work. + if len(args) == 1: + vals = args[0] + self = None + elif len(args) == 2: + self, vals = args + else: + assert 'either f([...]) or self.f([...])' + + def do_work(arg: Any) -> T: + if not isinstance(arg, tuple): + arg = (arg, ) + try: + if self: + return f(self, *arg) + return f(*arg) + except Exception: + logger.exception(f'executing {f.__name__}({args}) failed.') + raise + + assert CephadmOrchestrator.instance is not None + return CephadmOrchestrator.instance._worker_pool.map(do_work, vals) + + return forall_hosts_wrapper + + +def get_cluster_health(mgr: 'CephadmOrchestrator') -> str: + # check cluster health + ret, out, err = mgr.check_mon_command({ + 'prefix': 'health', + 'format': 'json', + }) + try: + j = json.loads(out) + except ValueError: + msg = 'Failed to parse health status: Cannot decode JSON' + logger.exception('%s: \'%s\'' % (msg, out)) + raise OrchestratorError('failed to parse health status') + + return j['status'] + + +def is_repo_digest(image_name: str) -> bool: + """ + repo digest are something like "ceph/ceph@sha256:blablabla" + """ + return '@' in image_name + + +def resolve_ip(hostname: str) -> str: + try: + r = socket.getaddrinfo(hostname, None, flags=socket.AI_CANONNAME, + type=socket.SOCK_STREAM) + # pick first v4 IP, if present + for a in r: + if a[0] == socket.AF_INET: + return a[4][0] + return r[0][4][0] + except socket.gaierror as e: + raise OrchestratorError(f"Cannot resolve ip for host {hostname}: {e}") + + +def ceph_release_to_major(release: str) -> int: + return ord(release[0]) - ord('a') + 1 + + +def file_mode_to_str(mode: int) -> str: + r = '' + for shift in range(0, 9, 3): + r = ( + f'{"r" if (mode >> shift) & 4 else "-"}' + f'{"w" if (mode >> shift) & 2 else "-"}' + f'{"x" if (mode >> shift) & 1 else "-"}' + ) + r + return r diff --git a/src/pybind/mgr/cephadm/vagrant.config.example.json b/src/pybind/mgr/cephadm/vagrant.config.example.json new file mode 100644 index 000000000..5b1890924 --- /dev/null +++ b/src/pybind/mgr/cephadm/vagrant.config.example.json @@ -0,0 +1,13 @@ +/** + * To use a permenant config copy this file to "vagrant.config.json", + * edit it and remove this comment beacuase comments are not allowed + * in a valid JSON file. + */ + +{ + "mgrs": 1, + "mons": 1, + "osds": 1, + "disks": 2 +} + diff --git a/src/pybind/mgr/cli_api/__init__.py b/src/pybind/mgr/cli_api/__init__.py new file mode 100644 index 000000000..a52284054 --- /dev/null +++ b/src/pybind/mgr/cli_api/__init__.py @@ -0,0 +1,10 @@ +from .module import CLI + +__all__ = [ + "CLI", +] + +import os +if 'UNITTEST' in os.environ: + import tests # noqa # pylint: disable=unused-import + __all__.append(tests.__name__) diff --git a/src/pybind/mgr/cli_api/module.py b/src/pybind/mgr/cli_api/module.py new file mode 100755 index 000000000..79b042eb0 --- /dev/null +++ b/src/pybind/mgr/cli_api/module.py @@ -0,0 +1,120 @@ +import concurrent.futures +import functools +import inspect +import logging +import time +import errno +from typing import Any, Callable, Dict, List + +from mgr_module import MgrModule, HandleCommandResult, CLICommand, API + +logger = logging.getLogger() +get_time = time.perf_counter + + +def pretty_json(obj: Any) -> Any: + import json + return json.dumps(obj, sort_keys=True, indent=2) + + +class CephCommander: + """ + Utility class to inspect Python functions and generate corresponding + CephCommand signatures (see src/mon/MonCommand.h for details) + """ + + def __init__(self, func: Callable): + self.func = func + self.signature = inspect.signature(func) + self.params = self.signature.parameters + + def to_ceph_signature(self) -> Dict[str, str]: + """ + Generate CephCommand signature (dict-like) + """ + return { + 'prefix': f'mgr cli {self.func.__name__}', + 'perm': API.perm.get(self.func) + } + + +class MgrAPIReflector(type): + """ + Metaclass to register COMMANDS and Command Handlers via CLICommand + decorator + """ + + def __new__(cls, name, bases, dct): # type: ignore + klass = super().__new__(cls, name, bases, dct) + cls.threaded_benchmark_runner = None + for base in bases: + for name, func in inspect.getmembers(base, cls.is_public): + # However not necessary (CLICommand uses a registry) + # save functions to klass._cli_{n}() methods. This + # can help on unit testing + wrapper = cls.func_wrapper(func) + command = CLICommand(**CephCommander(func).to_ceph_signature())( # type: ignore + wrapper) + setattr( + klass, + f'_cli_{name}', + command) + return klass + + @staticmethod + def is_public(func: Callable) -> bool: + return ( + inspect.isfunction(func) + and not func.__name__.startswith('_') + and API.expose.get(func) + ) + + @staticmethod + def func_wrapper(func: Callable) -> Callable: + @functools.wraps(func) + def wrapper(self, *args, **kwargs) -> HandleCommandResult: # type: ignore + return HandleCommandResult(stdout=pretty_json( + func(self, *args, **kwargs))) + + # functools doesn't change the signature when wrapping a function + # so we do it manually + signature = inspect.signature(func) + wrapper.__signature__ = signature # type: ignore + return wrapper + + +class CLI(MgrModule, metaclass=MgrAPIReflector): + @CLICommand('mgr cli_benchmark') + def benchmark(self, iterations: int, threads: int, func_name: str, + func_args: List[str] = None) -> HandleCommandResult: # type: ignore + func_args = () if func_args is None else func_args + if iterations and threads: + try: + func = getattr(self, func_name) + except AttributeError: + return HandleCommandResult(errno.EINVAL, + stderr="Could not find the public " + "function you are requesting") + else: + raise BenchmarkException("Number of calls and number " + "of parallel calls must be greater than 0") + + def timer(*args: Any) -> float: + time_start = get_time() + func(*func_args) + return get_time() - time_start + + with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as executor: + results_iter = executor.map(timer, range(iterations)) + results = list(results_iter) + + stats = { + "avg": sum(results) / len(results), + "max": max(results), + "min": min(results), + } + return HandleCommandResult(stdout=pretty_json(stats)) + + +class BenchmarkException(Exception): + pass diff --git a/src/pybind/mgr/cli_api/tests/__init__.py b/src/pybind/mgr/cli_api/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/pybind/mgr/cli_api/tests/test_cli_api.py b/src/pybind/mgr/cli_api/tests/test_cli_api.py new file mode 100644 index 000000000..ee42dc96a --- /dev/null +++ b/src/pybind/mgr/cli_api/tests/test_cli_api.py @@ -0,0 +1,40 @@ +import unittest + +from ..module import CLI, BenchmarkException, HandleCommandResult + + +class BenchmarkRunnerTest(unittest.TestCase): + def setUp(self): + self.cli = CLI('CLI', 0, 0) + + def test_number_of_calls_on_start_fails(self): + with self.assertRaises(BenchmarkException) as ctx: + self.cli.benchmark(0, 10, 'list_servers', []) + self.assertEqual(str(ctx.exception), + "Number of calls and number " + "of parallel calls must be greater than 0") + + def test_number_of_parallel_calls_on_start_fails(self): + with self.assertRaises(BenchmarkException) as ctx: + self.cli.benchmark(100, 0, 'list_servers', []) + self.assertEqual(str(ctx.exception), + "Number of calls and number " + "of parallel calls must be greater than 0") + + def test_number_of_parallel_calls_on_start_works(self): + CLI.benchmark(10, 10, "get", "osd_map") + + def test_function_name_fails(self): + for iterations in [0, 1]: + threads = 0 if iterations else 1 + with self.assertRaises(BenchmarkException) as ctx: + self.cli.benchmark(iterations, threads, 'fake_method', []) + self.assertEqual(str(ctx.exception), + "Number of calls and number " + "of parallel calls must be greater than 0") + result: HandleCommandResult = self.cli.benchmark(1, 1, 'fake_method', []) + self.assertEqual(result.stderr, "Could not find the public " + "function you are requesting") + + def test_function_name_works(self): + CLI.benchmark(10, 10, "get", "osd_map") diff --git a/src/pybind/mgr/crash/__init__.py b/src/pybind/mgr/crash/__init__.py new file mode 100644 index 000000000..8f210ac92 --- /dev/null +++ b/src/pybind/mgr/crash/__init__.py @@ -0,0 +1 @@ +from .module import Module diff --git a/src/pybind/mgr/crash/module.py b/src/pybind/mgr/crash/module.py new file mode 100644 index 000000000..f2b65fbc3 --- /dev/null +++ b/src/pybind/mgr/crash/module.py @@ -0,0 +1,412 @@ +import hashlib +from mgr_module import MgrModule +import datetime +import errno +import json +from collections import defaultdict +from prettytable import PrettyTable +import re +from threading import Event, Lock + + +DATEFMT = '%Y-%m-%dT%H:%M:%S.%f' +OLD_DATEFMT = '%Y-%m-%d %H:%M:%S.%f' + +MAX_WAIT = 600 +MIN_WAIT = 60 + +class Module(MgrModule): + MODULE_OPTIONS = [ + { + 'name': 'warn_recent_interval', + 'type': 'secs', + 'default': 60*60*24*14, + 'desc': 'time interval in which to warn about recent crashes', + 'runtime': True, + }, + { + 'name': 'retain_interval', + 'type': 'secs', + 'default': 60*60*24 * 365, + 'desc': 'how long to retain crashes before pruning them', + 'runtime': True, + }, + ] + + def __init__(self, *args, **kwargs): + super(Module, self).__init__(*args, **kwargs) + self.crashes = None + self.crashes_lock = Lock() + self.run = True + self.event = Event() + + def shutdown(self): + self.run = False + self.event.set() + + def serve(self): + self.config_notify() + while self.run: + with self.crashes_lock: + self._refresh_health_checks() + self._prune(self.retain_interval) + wait = min(MAX_WAIT, max(self.warn_recent_interval / 100, MIN_WAIT)) + self.event.wait(wait) + self.event.clear() + + def config_notify(self): + for opt in self.MODULE_OPTIONS: + setattr(self, + opt['name'], + self.get_module_option(opt['name'])) + self.log.debug(' mgr option %s = %s', + opt['name'], getattr(self, opt['name'])) + + def _load_crashes(self): + raw = self.get_store_prefix('crash/') + self.crashes = {k[6:]: json.loads(m) for (k, m) in raw.items()} + + def _refresh_health_checks(self): + if not self.crashes: + self._load_crashes() + cutoff = datetime.datetime.utcnow() - datetime.timedelta( + seconds=self.warn_recent_interval) + recent = { + crashid: crash for crashid, crash in self.crashes.items() + if self.time_from_string(crash['timestamp']) > cutoff and 'archived' not in crash + } + num = len(recent) + health_checks = {} + if recent: + detail = [ + '%s crashed on host %s at %s' % ( + crash.get('entity_name', 'unidentified daemon'), + crash.get('utsname_hostname', '(unknown)'), + crash.get('timestamp', 'unknown time')) + for (_, crash) in recent.items()] + if num > 30: + detail = detail[0:30] + detail.append('and %d more' % (num - 30)) + self.log.debug('detail %s' % detail) + health_checks['RECENT_CRASH'] = { + 'severity': 'warning', + 'summary': '%d daemons have recently crashed' % (num), + 'count': num, + 'detail': detail, + } + self.set_health_checks(health_checks) + + def handle_command(self, inbuf, command): + with self.crashes_lock: + if not self.crashes: + self._load_crashes() + for cmd in self.COMMANDS: + if cmd['cmd'].startswith(command['prefix']): + handler = cmd['handler'] + break + if handler is None: + return errno.EINVAL, '', 'unknown command %s' % command['prefix'] + + return handler(self, command, inbuf) + + def time_from_string(self, timestr): + # drop the 'Z' timezone indication, it's always UTC + timestr = timestr.rstrip('Z') + try: + return datetime.datetime.strptime(timestr, DATEFMT) + except ValueError: + return datetime.datetime.strptime(timestr, OLD_DATEFMT) + + def validate_crash_metadata(self, inbuf): + # raise any exceptions to caller + metadata = json.loads(inbuf) + for f in ['crash_id', 'timestamp']: + if f not in metadata: + raise AttributeError("missing '%s' field" % f) + time = self.time_from_string(metadata['timestamp']) + return metadata + + def timestamp_filter(self, f): + """ + Filter crash reports by timestamp. + + :param f: f(time) return true to keep crash report + :returns: crash reports for which f(time) returns true + """ + def inner(pair): + _, crash = pair + time = self.time_from_string(crash["timestamp"]) + return f(time) + return filter(inner, self.crashes.items()) + + # stack signature helpers + + def sanitize_backtrace(self, bt): + ret = list() + for func_record in bt: + # split into two fields on last space, take the first one, + # strip off leading ( and trailing ) + func_plus_offset = func_record.rsplit(' ', 1)[0][1:-1] + ret.append(func_plus_offset.split('+')[0]) + + return ret + + ASSERT_MATCHEXPR = re.compile(r'(?s)(.*) thread .* time .*(: .*)\n') + + def sanitize_assert_msg(self, msg): + # (?s) allows matching newline. get everything up to "thread" and + # then after-and-including the last colon-space. This skips the + # thread id, timestamp, and file:lineno, because file is already in + # the beginning, and lineno may vary. + return ''.join(self.ASSERT_MATCHEXPR.match(msg).groups()) + + def calc_sig(self, bt, assert_msg): + sig = hashlib.sha256() + for func in self.sanitize_backtrace(bt): + sig.update(func.encode()) + if assert_msg: + sig.update(self.sanitize_assert_msg(assert_msg).encode()) + return ''.join('%02x' % c for c in sig.digest()) + + # command handlers + + def do_info(self, cmd, inbuf): + crashid = cmd['id'] + crash = self.crashes.get(crashid) + if not crash: + return errno.EINVAL, '', 'crash info: %s not found' % crashid + val = json.dumps(crash, indent=4, sort_keys=True) + return 0, val, '' + + def do_post(self, cmd, inbuf): + try: + metadata = self.validate_crash_metadata(inbuf) + except Exception as e: + return errno.EINVAL, '', 'malformed crash metadata: %s' % e + if 'backtrace' in metadata: + metadata['stack_sig'] = self.calc_sig( + metadata.get('backtrace'), metadata.get('assert_msg')) + crashid = metadata['crash_id'] + + if crashid not in self.crashes: + self.crashes[crashid] = metadata + key = 'crash/%s' % crashid + self.set_store(key, json.dumps(metadata)) + self._refresh_health_checks() + return 0, '', '' + + def ls(self): + if not self.crashes: + self._load_crashes() + return self.do_ls({'prefix': 'crash ls'}, '') + + def do_ls(self, cmd, inbuf): + if cmd['prefix'] == 'crash ls': + t = self.crashes.values() + else: + t = [crash for crashid, crash in self.crashes.items() + if 'archived' not in crash] + r = sorted(t, key=lambda i: i.get('crash_id')) + if cmd.get('format') == 'json' or cmd.get('format') == 'json-pretty': + return 0, json.dumps(r, indent=4, sort_keys=True), '' + else: + table = PrettyTable(['ID', 'ENTITY', 'NEW'], + border=False) + table.left_padding_width = 0 + table.right_padding_width = 2 + table.align['ID'] = 'l' + table.align['ENTITY'] = 'l' + for c in r: + table.add_row([c.get('crash_id'), + c.get('entity_name','unknown'), + '' if 'archived' in c else '*']) + return 0, table.get_string(), '' + + def do_rm(self, cmd, inbuf): + crashid = cmd['id'] + if crashid in self.crashes: + del self.crashes[crashid] + key = 'crash/%s' % crashid + self.set_store(key, None) # removes key + self._refresh_health_checks() + return 0, '', '' + + def do_prune(self, cmd, inbuf): + keep = cmd['keep'] + try: + keep = int(keep) + except ValueError: + return errno.EINVAL, '', 'keep argument must be integer' + + self._prune(keep * 60*60*24) + return 0, '', '' + + def _prune(self, seconds): + now = datetime.datetime.utcnow() + cutoff = now - datetime.timedelta(seconds=seconds) + removed_any = False + # make a copy of the list, since we'll modify self.crashes below + to_prune = list(self.timestamp_filter(lambda ts: ts <= cutoff)) + for crashid, crash in to_prune: + del self.crashes[crashid] + key = 'crash/%s' % crashid + self.set_store(key, None) + removed_any = True + if removed_any: + self._refresh_health_checks() + + def do_archive(self, cmd, inbuf): + crashid = cmd['id'] + crash = self.crashes.get(crashid) + if not crash: + return errno.EINVAL, '', 'crash info: %s not found' % crashid + if not crash.get('archived'): + crash['archived'] = str(datetime.datetime.utcnow()) + self.crashes[crashid] = crash + key = 'crash/%s' % crashid + self.set_store(key, json.dumps(crash)) + self._refresh_health_checks() + return 0, '', '' + + def do_archive_all(self, cmd, inbuf): + for crashid, crash in self.crashes.items(): + if not crash.get('archived'): + crash['archived'] = str(datetime.datetime.utcnow()) + self.crashes[crashid] = crash + key = 'crash/%s' % crashid + self.set_store(key, json.dumps(crash)) + self._refresh_health_checks() + return 0, '', '' + + def do_stat(self, cmd, inbuf): + # age in days for reporting, ordered smallest first + bins = [1, 3, 7] + retlines = list() + + def binstr(bindict): + binlines = list() + count = len(bindict['idlist']) + if count: + binlines.append( + '%d older than %s days old:' % (count, bindict['age']) + ) + for crashid in bindict['idlist']: + binlines.append(crashid) + return '\n'.join(binlines) + + total = 0 + now = datetime.datetime.utcnow() + for i, age in enumerate(bins): + agelimit = now - datetime.timedelta(days=age) + bins[i] = { + 'age': age, + 'agelimit': agelimit, + 'idlist': list() + } + + for crashid, crash in self.crashes.items(): + total += 1 + stamp = self.time_from_string(crash['timestamp']) + for i, bindict in enumerate(bins): + if stamp <= bindict['agelimit']: + bindict['idlist'].append(crashid) + # don't count this one again + continue + + retlines.append('%d crashes recorded' % total) + + for bindict in bins: + retlines.append(binstr(bindict)) + return 0, '\n'.join(retlines), '' + + def do_json_report(self, cmd, inbuf): + """ + Return a machine readable summary of recent crashes. + """ + try: + hours = int(cmd['hours']) + except ValueError: + return errno.EINVAL, '', ' argument must be integer' + + report = defaultdict(lambda: 0) + for crashid, crash in self.crashes.items(): + pname = crash.get("process_name", "unknown") + if not pname: + pname = "unknown" + report[pname] += 1 + + return 0, '', json.dumps(report, sort_keys=True) + + def self_test(self): + # test time conversion + timestr = '2018-06-22T20:35:38.058818Z' + old_timestr = '2018-06-22 20:35:38.058818Z' + dt = self.time_from_string(timestr) + if dt != datetime.datetime(2018, 6, 22, 20, 35, 38, 58818): + raise RuntimeError('time_from_string() failed') + dt = self.time_from_string(old_timestr) + if dt != datetime.datetime(2018, 6, 22, 20, 35, 38, 58818): + raise RuntimeError('time_from_string() (old) failed') + + COMMANDS = [ + { + 'cmd': 'crash info name=id,type=CephString', + 'desc': 'show crash dump metadata', + 'perm': 'r', + 'handler': do_info, + }, + { + 'cmd': 'crash ls', + 'desc': 'Show new and archived crash dumps', + 'perm': 'r', + 'handler': do_ls, + }, + { + 'cmd': 'crash ls-new', + 'desc': 'Show new crash dumps', + 'perm': 'r', + 'handler': do_ls, + }, + { + 'cmd': 'crash post', + 'desc': 'Add a crash dump (use -i )', + 'perm': 'rw', + 'handler': do_post, + }, + { + 'cmd': 'crash prune name=keep,type=CephString', + 'desc': 'Remove crashes older than days', + 'perm': 'rw', + 'handler': do_prune, + }, + { + 'cmd': 'crash rm name=id,type=CephString', + 'desc': 'Remove a saved crash ', + 'perm': 'rw', + 'handler': do_rm, + }, + { + 'cmd': 'crash stat', + 'desc': 'Summarize recorded crashes', + 'perm': 'r', + 'handler': do_stat, + }, + { + 'cmd': 'crash json_report name=hours,type=CephString', + 'desc': 'Crashes in the last hours', + 'perm': 'r', + 'handler': do_json_report, + }, + { + 'cmd': 'crash archive name=id,type=CephString', + 'desc': 'Acknowledge a crash and silence health warning(s)', + 'perm': 'w', + 'handler': do_archive, + }, + { + 'cmd': 'crash archive-all', + 'desc': 'Acknowledge all new crashes and silence health warning(s)', + 'perm': 'w', + 'handler': do_archive_all, + }, + ] diff --git a/src/pybind/mgr/dashboard/.coveragerc b/src/pybind/mgr/dashboard/.coveragerc new file mode 100644 index 000000000..29a63192c --- /dev/null +++ b/src/pybind/mgr/dashboard/.coveragerc @@ -0,0 +1,7 @@ +[run] +omit = tests/* + */python*/* + ceph_module_mock.py + __init__.py + */mgr_module.py + diff --git a/src/pybind/mgr/dashboard/.editorconfig b/src/pybind/mgr/dashboard/.editorconfig new file mode 100644 index 000000000..a831e3da1 --- /dev/null +++ b/src/pybind/mgr/dashboard/.editorconfig @@ -0,0 +1,29 @@ +# EditorConfig helps developers define and maintain consistent coding styles +# between different editors and IDEs.: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +# Set default charset +[*.{js,py}] +charset = utf-8 + +# 4 space indentation for Python files +[*.py] +indent_style = space +indent_size = 4 + +# Indentation override for all JS under frontend directory +[frontend/**.js] +indent_style = space +indent_size = 2 + +# Indentation override for all HTML under frontend directory +[frontend/**.html] +indent_style = space +indent_size = 2 diff --git a/src/pybind/mgr/dashboard/.gitignore b/src/pybind/mgr/dashboard/.gitignore new file mode 100644 index 000000000..d457a7db3 --- /dev/null +++ b/src/pybind/mgr/dashboard/.gitignore @@ -0,0 +1,15 @@ +.coverage* +htmlcov +.tox +coverage.xml +junit*xml +.cache +ceph.conf + +# IDE +.vscode +*.egg +.env + +# virtualenv +venv diff --git a/src/pybind/mgr/dashboard/.pylintrc b/src/pybind/mgr/dashboard/.pylintrc new file mode 100644 index 000000000..79dfbad7d --- /dev/null +++ b/src/pybind/mgr/dashboard/.pylintrc @@ -0,0 +1,541 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code +# TODO: remove racially insensitive terms when this becomes fixed: https://github.com/PyCQA/pylint/issues/3669 +extension-pkg-whitelist=rados,rbd,math,cephfs + +# Add files or directories to the blocklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blocklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +init-hook='import sys; sys.path.append("./")' + +# Use multiple processes to speed up Pylint. +jobs=1 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Specify a configuration file. +#rcfile= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +disable=import-star-module-level, + raw-checker-failed, + bad-inline-option, + locally-disabled, + locally-enabled, + suppressed-message, + useless-suppression, + apply-builtin, + basestring-builtin, + buffer-builtin, + cmp-builtin, + coerce-builtin, + execfile-builtin, + file-builtin, + long-builtin, + raw_input-builtin, + reduce-builtin, + standarderror-builtin, + unicode-builtin, + coerce-method, + delslice-method, + getslice-method, + setslice-method, + no-absolute-import, + old-division, + dict-iter-method, + dict-view-method, + next-method-called, + metaclass-assignment, + indexing-exception, + reload-builtin, + oct-method, + hex-method, + nonzero-method, + cmp-method, + input-builtin, + round-builtin, + intern-builtin, + unichr-builtin, + map-builtin-not-iterating, + zip-builtin-not-iterating, + range-builtin-not-iterating, + filter-builtin-not-iterating, + using-cmp-argument, + eq-without-hash, + div-method, + idiv-method, + rdiv-method, + exception-message-attribute, + invalid-str-codec, + sys-max-int, + bad-python3-import, + next-method-defined, + dict-items-not-iterating, + dict-keys-not-iterating, + dict-values-not-iterating, + missing-docstring, + invalid-name, + no-self-use, + too-few-public-methods, + no-member, + too-many-arguments, + too-many-locals, + too-many-statements, + useless-object-inheritance, + relative-beyond-top-level, + raise-missing-from, + super-with-arguments, + import-outside-toplevel, + unsubscriptable-object + + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio).You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=optparse.Values,sys.exit + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins + + +[BASIC] + +# Naming style matching correct argument names +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style +#argument-rgx= + +# Naming style matching correct attribute names +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Naming style matching correct class attribute names +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style +#class-attribute-rgx= + +# Naming style matching correct class names +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming-style +#class-rgx= + +# Naming style matching correct constant names +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma +good-names=i, + j, + k, + ex, + Run, + _ + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# Naming style matching correct inline iteration names +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style +#inlinevar-rgx= + +# Naming style matching correct method names +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style +#method-rgx= + +# Naming style matching correct module names +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style +#variable-rgx= + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module +max-module-lines=1000 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma, + dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules=cherrypy,distutils,rados,rbd,cephfs + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=yes + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[IMPORTS] + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub, + TERMIOS, + Bastion, + rexec + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=10 + +# Maximum number of boolean expressions in a if statement +max-bool-expr=5 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of statements in function / method body +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/src/pybind/mgr/dashboard/CMakeLists.txt b/src/pybind/mgr/dashboard/CMakeLists.txt new file mode 100644 index 000000000..0c8d46d65 --- /dev/null +++ b/src/pybind/mgr/dashboard/CMakeLists.txt @@ -0,0 +1,141 @@ +include(CMakeParseArguments) +function(add_npm_command) + set(options NODEENV) + set(single_kw OUTPUT COMMENT WORKING_DIRECTORY) + set(multi_kw COMMAND DEPENDS) + cmake_parse_arguments(NC "${options}" "${single_kw}" "${multi_kw}" ${ARGN}) + string(REPLACE ";" " " command "${NC_COMMAND}") + if(NC_NODEENV) + string(REGEX REPLACE "^(.*(npm|npx) .*)$" ". ${mgr-dashboard-nodeenv-dir}/bin/activate && \\1 && deactivate" command ${command}) + endif() + string(REPLACE " " ";" command "${command}") + add_custom_command( + OUTPUT "${NC_OUTPUT}" + COMMAND ${command} + DEPENDS ${NC_DEPENDS} + WORKING_DIRECTORY "${NC_WORKING_DIRECTORY}" + COMMENT ${NC_COMMENT}) +endfunction(add_npm_command) + +function(add_npm_options) + set(commands) + cmake_parse_arguments(NC "" "NODEENV_DIR;TARGET" "OPTION" ${ARGN}) + foreach(opt ${NC_OPTION}) + string(REPLACE "=" ";" opt ${opt}) + list(GET opt 0 key) + list(GET opt 1 value) + list(APPEND commands + COMMAND + . ${NC_NODEENV_DIR}/bin/activate && + npm config set ${key} ${value} --userconfig ${NC_NODEENV_DIR}/.npmrc && + deactivate) + endforeach() + add_custom_target(${NC_TARGET} + ${commands} + DEPENDS ${NC_NODEENV_DIR}/bin/npm + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) +endfunction(add_npm_options) + +if(WITH_SYSTEM_NPM) + set(mgr-dashboard-nodeenv-dir ) + set(nodeenv "") + add_custom_target(mgr-dashboard-frontend-deps + DEPENDS frontend/node_modules + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/src/pybind/mgr/dashboard/frontend + ) +else(WITH_SYSTEM_NPM) + set(mgr-dashboard-nodeenv-dir ${CMAKE_CURRENT_BINARY_DIR}/node-env) + set(nodeenv NODEENV) + set(mgr-dashboard-userconfig --userconfig ${mgr-dashboard-nodeenv-dir}/.npmrc) + if(DEFINED ENV{NODE_MIRROR}) + set(node_mirror_opt "--mirror=$ENV{NODE_MIRROR}") + endif() + add_custom_command( + OUTPUT "${mgr-dashboard-nodeenv-dir}/bin/npm" + COMMAND ${CMAKE_SOURCE_DIR}/src/tools/setup-virtualenv.sh --python=${MGR_PYTHON_EXECUTABLE} ${mgr-dashboard-nodeenv-dir} + COMMAND ${mgr-dashboard-nodeenv-dir}/bin/pip install nodeenv + COMMAND ${mgr-dashboard-nodeenv-dir}/bin/nodeenv --verbose ${node_mirror_opt} -p --node=12.18.2 + COMMAND mkdir ${mgr-dashboard-nodeenv-dir}/.npm + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "dashboard nodeenv is being installed" + ) + if(DEFINED ENV{NPM_REGISTRY}) + set(npm_registry_opts "OPTION" "registry=$ENV{NPM_REGISTRY}") + endif() + add_npm_options( + NODEENV_DIR ${mgr-dashboard-nodeenv-dir} + TARGET mgr-dashboard-nodeenv + OPTION python=${MGR_PYTHON_EXECUTABLE} + OPTION cache=${mgr-dashboard-nodeenv-dir}/.npm + ${npm_registry_opts}) + add_custom_target(mgr-dashboard-frontend-deps + DEPENDS frontend/node_modules mgr-dashboard-nodeenv + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/src/pybind/mgr/dashboard/frontend + ) +endif(WITH_SYSTEM_NPM) + +add_npm_command( + OUTPUT "${CMAKE_SOURCE_DIR}/src/pybind/mgr/dashboard/frontend/node_modules" + COMMAND CYPRESS_CACHE_FOLDER=${CMAKE_SOURCE_DIR}/build/src/pybind/mgr/dashboard/cypress NG_CLI_ANALYTICS=false npm ci -f ${mgr-dashboard-userconfig} + DEPENDS frontend/package.json + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/src/pybind/mgr/dashboard/frontend + COMMENT "dashboard frontend dependencies are being installed" + ${nodeenv} +) + +# Glob some frontend files. With CMake 3.6, this can be simplified +# to *.ts *.html. Just add: +# list(FILTER frontend_src INCLUDE REGEX "frontend/src") +file( + GLOB_RECURSE frontend_src + frontend/src/*.ts + frontend/src/*.html + frontend/src/*/*.ts + frontend/src/*/*.html + frontend/src/*/*/*.ts + frontend/src/*/*/*.html + frontend/src/*/*/*/*.ts + frontend/src/*/*/*/*.html + frontend/src/*/*/*/*/*.ts + frontend/src/*/*/*/*/*.html + frontend/src/*/*/*/*/*/*.ts + frontend/src/*/*/*/*/*/*.html) + +# these files are generated during build +list(REMOVE_ITEM frontend_src + ${CMAKE_SOURCE_DIR}/src/pybind/mgr/dashboard/frontend/src/environments/environment.prod.ts + ${CMAKE_SOURCE_DIR}/src/pybind/mgr/dashboard/frontend/src/environments/environment.ts) + +execute_process( + COMMAND bash -c "jq -r .config.locale ${CMAKE_SOURCE_DIR}/src/pybind/mgr/dashboard/frontend/package.json" + OUTPUT_VARIABLE default_lang + OUTPUT_STRIP_TRAILING_WHITESPACE) + +if(NOT CMAKE_BUILD_TYPE STREQUAL Debug) + set(npm_args "--prod --progress=false") +else() + set(npm_args "--progress=false") +endif() + +add_npm_command( + OUTPUT "${CMAKE_SOURCE_DIR}/src/pybind/mgr/dashboard/frontend/dist" + COMMAND DASHBOARD_FRONTEND_LANGS="${DASHBOARD_FRONTEND_LANGS}" npm run build:localize -- ${npm_args} + DEPENDS ${frontend_src} frontend/node_modules + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/src/pybind/mgr/dashboard/frontend + COMMENT "dashboard frontend is being created" + ${nodeenv} +) +add_custom_target(mgr-dashboard-frontend-build + ALL + DEPENDS frontend/dist mgr-dashboard-frontend-deps + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/src/pybind/mgr/dashboard/frontend) + +add_dependencies(tests mgr-dashboard-frontend-build) + +if(WITH_TESTS) + include(AddCephTest) + add_tox_test(mgr-dashboard-py3 TOX_ENVS py3) + add_tox_test(mgr-dashboard-lint TOX_ENVS lint) + add_tox_test(mgr-dashboard-check TOX_ENVS check) + add_tox_test(mgr-dashboard-openapi TOX_ENVS openapi-check) +endif() diff --git a/src/pybind/mgr/dashboard/HACKING.rst b/src/pybind/mgr/dashboard/HACKING.rst new file mode 100644 index 000000000..39c3d6744 --- /dev/null +++ b/src/pybind/mgr/dashboard/HACKING.rst @@ -0,0 +1,10 @@ +Ceph Dashboard Developer Documentation +====================================== + +Note: The content of this file has been moved into the Ceph Developer Guide. + +If you're interested in helping with the development of the dashboard, please +see ``/doc/dev/developer_guide/dash_devel.rst`` or the `online version +`_ for +details on how to set up a development environment and other development-related +topics. diff --git a/src/pybind/mgr/dashboard/README.rst b/src/pybind/mgr/dashboard/README.rst new file mode 100644 index 000000000..623ba2528 --- /dev/null +++ b/src/pybind/mgr/dashboard/README.rst @@ -0,0 +1,35 @@ +Ceph Dashboard +============== + +Overview +-------- + +The Ceph Dashboard is a built-in web-based Ceph management and monitoring +application to administer various aspects and objects of the cluster. It is +implemented as a Ceph Manager module. + +Enabling and Starting the Dashboard +----------------------------------- + +If you want to start the dashboard from within a development environment, you +need to have built Ceph (see the toplevel ``README.md`` file and the `developer +documentation `_ for +details on how to accomplish this. + +If you use the ``vstart.sh`` script to start up your development cluster, it +will configure and enable the dashboard automatically. The URL and login +credentials are displayed when the script finishes. + +Please see the `Ceph Dashboard documentation +`_ for details on how to +enable and configure the dashboard manually and how to configure other settings, +e.g. access to the Ceph object gateway. + +Working on the Dashboard Code +----------------------------- + +If you're interested in helping with the development of the dashboard, please +see ``/doc/dev/dev_guide/dash_devel.rst`` or the `online version +`_ for +details on how to set up a development environment and other development-related +topics. diff --git a/src/pybind/mgr/dashboard/__init__.py b/src/pybind/mgr/dashboard/__init__.py new file mode 100644 index 000000000..5156403e3 --- /dev/null +++ b/src/pybind/mgr/dashboard/__init__.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# pylint: disable=wrong-import-position,global-statement,protected-access +""" +ceph dashboard module +""" +from __future__ import absolute_import + +import os + +import cherrypy + +if 'COVERAGE_ENABLED' in os.environ: + import coverage # pylint: disable=import-error + __cov = coverage.Coverage(config_file="{}/.coveragerc".format(os.path.dirname(__file__)), + data_suffix=True) + __cov.start() + cherrypy.engine.subscribe('after_request', __cov.save) + cherrypy.engine.subscribe('stop', __cov.stop) + +if 'UNITTEST' not in os.environ: + class _ModuleProxy(object): + def __init__(self): + self._mgr = None + + def init(self, module_inst): + self._mgr = module_inst + + def __getattr__(self, item): + if self._mgr is None: + raise AttributeError("global manager module instance not initialized") + return getattr(self._mgr, item) + + mgr = _ModuleProxy() + +else: + import logging + logging.basicConfig(level=logging.DEBUG) + logging.root.handlers[0].setLevel(logging.DEBUG) + os.environ['PATH'] = '{}:{}'.format(os.path.abspath('../../../../build/bin'), + os.environ['PATH']) + import sys + + # Used to allow the running of a tox-based yml doc generator from the dashboard directory + if os.path.abspath(sys.path[0]) == os.getcwd(): + sys.path.pop(0) + + from tests import mock # type: ignore + + mgr = mock.Mock() + mgr.get_frontend_path.side_effect = lambda: os.path.abspath("./frontend/dist") + + import rbd + + # Api tests do not mock rbd as opposed to dashboard unit tests. Both + # use UNITTEST env variable. + if isinstance(rbd, mock.Mock): + rbd.RBD_MIRROR_IMAGE_MODE_JOURNAL = 0 + rbd.RBD_MIRROR_IMAGE_MODE_SNAPSHOT = 1 + +# DO NOT REMOVE: required for ceph-mgr to load a module +from .module import Module, StandbyModule # noqa: F401 diff --git a/src/pybind/mgr/dashboard/api/__init__.py b/src/pybind/mgr/dashboard/api/__init__.py new file mode 100644 index 000000000..c3961685a --- /dev/null +++ b/src/pybind/mgr/dashboard/api/__init__.py @@ -0,0 +1 @@ +from __future__ import absolute_import diff --git a/src/pybind/mgr/dashboard/api/doc.py b/src/pybind/mgr/dashboard/api/doc.py new file mode 100644 index 000000000..172d59d0a --- /dev/null +++ b/src/pybind/mgr/dashboard/api/doc.py @@ -0,0 +1,53 @@ +from enum import Enum +from typing import Any, Dict, List, Optional + + +class SchemaType(Enum): + """ + Representation of the type property of a schema object: + http://spec.openapis.org/oas/v3.0.3.html#schema-object + """ + ARRAY = 'array' + BOOLEAN = 'boolean' + INTEGER = 'integer' + NUMBER = 'number' + OBJECT = 'object' + STRING = 'string' + + def __str__(self): + return str(self.value) + + +class Schema: + """ + Representation of a schema object: + http://spec.openapis.org/oas/v3.0.3.html#schema-object + """ + + def __init__(self, schema_type: SchemaType = SchemaType.OBJECT, + properties: Optional[Dict] = None, required: Optional[List] = None): + self._type = schema_type + self._properties = properties if properties else {} + self._required = required if required else [] + + def as_dict(self) -> Dict[str, Any]: + schema: Dict[str, Any] = {'type': str(self._type)} + + if self._type == SchemaType.ARRAY: + items = Schema(properties=self._properties) + schema['items'] = items.as_dict() + else: + schema['properties'] = self._properties + + if self._required: + schema['required'] = self._required + + return schema + + +class SchemaInput: + """ + Simple DTO to transfer data in a structured manner for creating a schema object. + """ + type: SchemaType + params: List[Any] diff --git a/src/pybind/mgr/dashboard/awsauth.py b/src/pybind/mgr/dashboard/awsauth.py new file mode 100644 index 000000000..ad8dc20fa --- /dev/null +++ b/src/pybind/mgr/dashboard/awsauth.py @@ -0,0 +1,165 @@ +# -*- coding: utf-8 -*- +# pylint: disable-all +# +# Copyright (c) 2012-2013 Paul Tax All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# 3. Neither the name of Infrae nor the names of its contributors may +# be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INFRAE OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import hmac +from base64 import encodebytes as encodestring +from email.utils import formatdate +from hashlib import sha1 as sha +from urllib.parse import unquote, urlparse + +from requests.auth import AuthBase + + +class S3Auth(AuthBase): + + """Attaches AWS Authentication to the given Request object.""" + + service_base_url = 's3.amazonaws.com' + # List of Query String Arguments of Interest + special_params = [ + 'acl', 'location', 'logging', 'partNumber', 'policy', 'requestPayment', + 'torrent', 'versioning', 'versionId', 'versions', 'website', 'uploads', + 'uploadId', 'response-content-type', 'response-content-language', + 'response-expires', 'response-cache-control', 'delete', 'lifecycle', + 'response-content-disposition', 'response-content-encoding', 'tagging', + 'notification', 'cors' + ] + + def __init__(self, access_key, secret_key, service_url=None): + if service_url: + self.service_base_url = service_url + self.access_key = str(access_key) + self.secret_key = str(secret_key) + + def __call__(self, r): + # Create date header if it is not created yet. + if 'date' not in r.headers and 'x-amz-date' not in r.headers: + r.headers['date'] = formatdate( + timeval=None, + localtime=False, + usegmt=True) + signature = self.get_signature(r) + signature = signature.decode('utf-8') + r.headers['Authorization'] = 'AWS %s:%s' % (self.access_key, signature) + return r + + def get_signature(self, r): + canonical_string = self.get_canonical_string( + r.url, r.headers, r.method) + key = self.secret_key.encode('utf-8') + msg = canonical_string.encode('utf-8') + h = hmac.new(key, msg, digestmod=sha) + return encodestring(h.digest()).strip() + + def get_canonical_string(self, url, headers, method): + parsedurl = urlparse(url) + objectkey = parsedurl.path[1:] + query_args = sorted(parsedurl.query.split('&')) + + bucket = parsedurl.netloc[:-len(self.service_base_url)] + if len(bucket) > 1: + # remove last dot + bucket = bucket[:-1] + + interesting_headers = { + 'content-md5': '', + 'content-type': '', + 'date': ''} + for key in headers: + lk = key.lower() + try: + if isinstance(lk, bytes): + lk = lk.decode('utf-8') + except UnicodeDecodeError: + pass + if headers[key] and (lk in interesting_headers.keys() + or lk.startswith('x-amz-')): + interesting_headers[lk] = headers[key].strip() + + # If x-amz-date is used it supersedes the date header. + if 'x-amz-date' in interesting_headers: + interesting_headers['date'] = '' + + buf = '%s\n' % method + for key in sorted(interesting_headers.keys()): + val = interesting_headers[key] + if key.startswith('x-amz-'): + buf += '%s:%s\n' % (key, val) + else: + buf += '%s\n' % val + + # append the bucket if it exists + if bucket != '': + buf += '/%s' % bucket + + # add the objectkey. even if it doesn't exist, add the slash + buf += '/%s' % objectkey + + params_found = False + + # handle special query string arguments + for q in query_args: + k = q.split('=')[0] + if k in self.special_params: + buf += '&' if params_found else '?' + params_found = True + + try: + k, v = q.split('=', 1) + + except ValueError: + buf += q + + else: + # Riak CS multipart upload ids look like this, `TFDSheOgTxC2Tsh1qVK73A==`, + # is should be escaped to be included as part of a query string. + # + # A requests mp upload part request may look like + # resp = requests.put( + # 'https://url_here', + # params={ + # 'partNumber': 1, + # 'uploadId': 'TFDSheOgTxC2Tsh1qVK73A==' + # }, + # data='some data', + # auth=S3Auth('access_key', 'secret_key') + # ) + # + # Requests automatically escapes the values in the `params` dict, so now + # our uploadId is `TFDSheOgTxC2Tsh1qVK73A%3D%3D`, + # if we sign the request with the encoded value the signature will + # not be valid, we'll get 403 Access Denied. + # So we unquote, this is no-op if the value isn't encoded. + buf += '{key}={value}'.format(key=k, value=unquote(v)) + + return buf diff --git a/src/pybind/mgr/dashboard/cherrypy_backports.py b/src/pybind/mgr/dashboard/cherrypy_backports.py new file mode 100644 index 000000000..26787200d --- /dev/null +++ b/src/pybind/mgr/dashboard/cherrypy_backports.py @@ -0,0 +1,199 @@ +# -*- coding: utf-8 -*- +""" +Copyright © 2004-2019, CherryPy Team (team@cherrypy.org) + +All rights reserved. + +* * * + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of CherryPy nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +from pkg_resources import parse_version + +# The SSL code in CherryPy 3.5.0 is buggy. It was fixed long ago, +# but 3.5.0 is still shipping in major linux distributions +# (Fedora 27, Ubuntu Xenial), so we must monkey patch it to get SSL working. + + +def patch_http_connection_init(v): + # It was fixed in 3.7.0. Exact lower bound version is probably earlier, + # but 3.5.0 is what this monkey patch is tested on. + if parse_version("3.5.0") <= v < parse_version("3.7.0"): + from cherrypy.wsgiserver.wsgiserver2 import CP_fileobject, HTTPConnection + + def fixed_init(hc_self, server, sock, makefile=CP_fileobject): + hc_self.server = server + hc_self.socket = sock + hc_self.rfile = makefile(sock, "rb", hc_self.rbufsize) + hc_self.wfile = makefile(sock, "wb", hc_self.wbufsize) + hc_self.requests_seen = 0 + + HTTPConnection.__init__ = fixed_init + + +# When the CherryPy server in 3.2.2 (and later) starts it attempts to verify +# that the ports its listening on are in fact bound. When using the any address +# "::" it tries both ipv4 and ipv6, and in some environments (e.g. kubernetes) +# ipv6 isn't yet configured / supported and CherryPy throws an uncaught +# exception. +def skip_wait_for_occupied_port(v): + # the issue was fixed in 3.2.3. it's present in 3.2.2 (current version on + # centos:7) and back to at least 3.0.0. + if parse_version("3.1.2") <= v < parse_version("3.2.3"): + # https://github.com/cherrypy/cherrypy/issues/1100 + from cherrypy.process import servers + servers.wait_for_occupied_port = lambda host, port: None + + +# cherrypy.wsgiserver was extracted wsgiserver into cheroot in cherrypy v9.0.0 +def patch_builtin_ssl_wrap(v, new_wrap): + if v < parse_version("9.0.0"): + from cherrypy.wsgiserver.ssl_builtin import BuiltinSSLAdapter as builtin_ssl + else: + from cheroot.ssl.builtin import BuiltinSSLAdapter as builtin_ssl + builtin_ssl.wrap = new_wrap(builtin_ssl.wrap) + + +def accept_exceptions_from_builtin_ssl(v): + # the fix was included by cheroot v5.2.0, which was included by cherrypy + # 10.2.0. + if v < parse_version("10.2.0"): + # see https://github.com/cherrypy/cheroot/pull/4 + import ssl + + def accept_ssl_errors(func): + def wrapper(self, sock): + try: + return func(self, sock) + except ssl.SSLError as e: + if e.errno == ssl.SSL_ERROR_SSL: + # Check if it's one of the known errors + # Errors that are caught by PyOpenSSL, but thrown by + # built-in ssl + _block_errors = ('unknown protocol', 'unknown ca', 'unknown_ca', + 'unknown error', + 'https proxy request', 'inappropriate fallback', + 'wrong version number', + 'no shared cipher', 'certificate unknown', + 'ccs received early', + 'certificate verify failed', # client cert w/o trusted CA + 'version too low', # caused by SSL3 connections + 'unsupported protocol', # caused by TLS1 connections + 'sslv3 alert bad certificate') + for error_text in _block_errors: + if error_text in e.args[1].lower(): + # Accepted error, let's pass + return None, {} + raise + return wrapper + patch_builtin_ssl_wrap(v, accept_ssl_errors) + + +def accept_socket_error_0(v): + # see https://github.com/cherrypy/cherrypy/issues/1618 + try: + import cheroot + cheroot_version = parse_version(cheroot.__version__) + except ImportError: + pass + + if v < parse_version("9.0.0") or cheroot_version < parse_version("6.5.5"): + generic_socket_error = OSError + + def accept_socket_error_0(func): + def wrapper(self, sock): + try: + return func(self, sock) + except generic_socket_error as e: + """It is unclear why exactly this happens. + + It's reproducible only with openssl>1.0 and stdlib ``ssl`` wrapper. + In CherryPy it's triggered by Checker plugin, which connects + to the app listening to the socket port in TLS mode via plain + HTTP during startup (from the same process). + + Ref: https://github.com/cherrypy/cherrypy/issues/1618 + """ + import ssl + is_error0 = e.args == (0, 'Error') + IS_ABOVE_OPENSSL10 = ssl.OPENSSL_VERSION_INFO >= (1, 1) + del ssl + if is_error0 and IS_ABOVE_OPENSSL10: + return None, {} + raise + return wrapper + patch_builtin_ssl_wrap(v, accept_socket_error_0) + + +def patch_request_unique_id(v): + """ + Older versions of cherrypy don't include request.unique_id field (a lazily + calculated UUID4). + + Monkey-patching is preferred over alternatives as inheritance, as it'd break + type checks (cherrypy/lib/cgtools.py: `isinstance(obj, _cprequest.Request)`) + """ + if v < parse_version('11.1.0'): + import uuid + from functools import update_wrapper + + from cherrypy._cprequest import Request + + class LazyUUID4(object): + def __str__(self): + """Return UUID4 and keep it for future calls.""" + return str(self.uuid4) + + @property + def uuid4(self): + """Provide unique id on per-request basis using UUID4. + It's evaluated lazily on render. + """ + try: + self._uuid4 # type: ignore + except AttributeError: + # evaluate on first access + self._uuid4 = uuid.uuid4() + + return self._uuid4 + + old_init = Request.__init__ + + def init_with_unique_id(self, *args, **kwargs): + old_init(self, *args, **kwargs) + self.unique_id = LazyUUID4() + + Request.__init__ = update_wrapper(init_with_unique_id, old_init) + + +def patch_cherrypy(v): + ver = parse_version(v) + patch_http_connection_init(ver) + skip_wait_for_occupied_port(ver) + accept_exceptions_from_builtin_ssl(ver) + accept_socket_error_0(ver) + patch_request_unique_id(ver) diff --git a/src/pybind/mgr/dashboard/ci/cephadm/bootstrap-cluster.sh b/src/pybind/mgr/dashboard/ci/cephadm/bootstrap-cluster.sh new file mode 100755 index 000000000..5a3fa4349 --- /dev/null +++ b/src/pybind/mgr/dashboard/ci/cephadm/bootstrap-cluster.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +set -x + +export PATH=/root/bin:$PATH +mkdir /root/bin + +cp /mnt/{{ ceph_dev_folder }}/src/cephadm/cephadm /root/bin/cephadm +chmod +x /root/bin/cephadm +mkdir -p /etc/ceph +mon_ip=$(ifconfig eth0 | grep 'inet ' | awk '{ print $2}') + +bootstrap_extra_options='--allow-fqdn-hostname --dashboard-password-noupdate' + +# commenting the below lines. Uncomment it when any extra options are +# needed for the bootstrap. +# bootstrap_extra_options_not_expanded='' +# {% if expanded_cluster is not defined %} +# bootstrap_extra_options+=" ${bootstrap_extra_options_not_expanded}" +# {% endif %} + +cephadm bootstrap --mon-ip $mon_ip --initial-dashboard-password {{ admin_password }} --shared_ceph_folder /mnt/{{ ceph_dev_folder }} ${bootstrap_extra_options} + +fsid=$(cat /etc/ceph/ceph.conf | grep fsid | awk '{ print $3}') +cephadm_shell="cephadm shell --fsid ${fsid} -c /etc/ceph/ceph.conf -k /etc/ceph/ceph.client.admin.keyring" + +{% for number in range(1, nodes) %} + ssh-copy-id -f -i /etc/ceph/ceph.pub -o StrictHostKeyChecking=no root@{{ prefix }}-node-0{{ number }} + {% if expanded_cluster is defined %} + ${cephadm_shell} ceph orch host add {{ prefix }}-node-0{{ number }} + {% endif %} +{% endfor %} + +{% if expanded_cluster is defined %} + ${cephadm_shell} ceph orch apply osd --all-available-devices +{% endif %} diff --git a/src/pybind/mgr/dashboard/ci/cephadm/ceph_cluster.yml b/src/pybind/mgr/dashboard/ci/cephadm/ceph_cluster.yml new file mode 100755 index 000000000..5d9f0723d --- /dev/null +++ b/src/pybind/mgr/dashboard/ci/cephadm/ceph_cluster.yml @@ -0,0 +1,45 @@ +parameters: + nodes: 4 + node_ip_offset: 100 + pool: ceph-dashboard + network: ceph-dashboard + gateway: 192.168.100.1 + netmask: 255.255.255.0 + prefix: ceph + numcpus: 1 + memory: 2048 + image: fedora34 + notify: false + admin_password: password + disks: + - 15 + - 5 + - 5 + +{% for number in range(0, nodes) %} +{{ prefix }}-node-0{{ number }}: + image: {{ image }} + numcpus: {{ numcpus }} + memory: {{ memory }} + reserveip: true + reservedns: true + sharedkey: true + nets: + - name: {{ network }} + ip: 192.168.100.{{ node_ip_offset + number }} + gateway: {{ gateway }} + mask: {{ netmask }} + dns: {{ gateway }} + disks: {{ disks }} + pool: {{ pool }} + sharedfolders: [{{ ceph_dev_folder }}] + files: + - bootstrap-cluster.sh + cmds: + - dnf -y install python3 chrony lvm2 podman + - sed -i "s/SELINUX=enforcing/SELINUX=permissive/" /etc/selinux/config + - setenforce 0 + {% if number == 0 %} + - bash /root/bootstrap-cluster.sh + {% endif %} +{% endfor %} diff --git a/src/pybind/mgr/dashboard/ci/cephadm/run-cephadm-e2e-tests.sh b/src/pybind/mgr/dashboard/ci/cephadm/run-cephadm-e2e-tests.sh new file mode 100755 index 000000000..063b544f4 --- /dev/null +++ b/src/pybind/mgr/dashboard/ci/cephadm/run-cephadm-e2e-tests.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash + +set -ex + +: ${CYPRESS_BASE_URL:=''} +: ${CYPRESS_LOGIN_USER:='admin'} +: ${CYPRESS_LOGIN_PWD:='password'} +: ${CYPRESS_ARGS:=''} +: ${DASHBOARD_PORT:='8443'} + +get_vm_ip () { + local ip=$(kcli info vm "$1" -f ip -v | grep -Eo '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}') + echo -n $ip +} + +if [[ -n "${JENKINS_HOME}" || (-z "${CYPRESS_BASE_URL}" && -z "$(get_vm_ip ceph-node-00)") ]]; then + . "$(dirname $0)"/start-cluster.sh + + CYPRESS_BASE_URL="https://$(get_vm_ip ceph-node-00):${DASHBOARD_PORT}" +fi + +export CYPRESS_BASE_URL CYPRESS_LOGIN_USER CYPRESS_LOGIN_PWD + +cypress_run () { + local specs="$1" + local timeout="$2" + local override_config="ignoreTestFiles=*.po.ts,retries=0,testFiles=${specs},chromeWebSecurity=false" + if [[ -n "$timeout" ]]; then + override_config="${override_config},defaultCommandTimeout=${timeout}" + fi + + rm -f cypress/reports/results-*.xml || true + + npx --no-install cypress run ${CYPRESS_ARGS} --browser chrome --headless --config "$override_config" +} + +: ${CEPH_DEV_FOLDER:=${PWD}} + +cd ${CEPH_DEV_FOLDER}/src/pybind/mgr/dashboard/frontend + +# check if the prometheus daemon is running +# before starting the e2e tests + +PROMETHEUS_RUNNING_COUNT=$(kcli ssh -u root ceph-node-00 'cephadm shell "ceph orch ls --service_name=prometheus --format=json"' | jq -r '.[] | .status.running') +while [[ $PROMETHEUS_RUNNING_COUNT -lt 1 ]]; do + PROMETHEUS_RUNNING_COUNT=$(kcli ssh -u root ceph-node-00 'cephadm shell "ceph orch ls --service_name=prometheus --format=json"' | jq -r '.[] | .status.running') +done + +cypress_run ["orchestrator/workflow/*.feature, orchestrator/workflow/*-spec.ts"] +cypress_run "orchestrator/grafana/*.feature" diff --git a/src/pybind/mgr/dashboard/ci/cephadm/start-cluster.sh b/src/pybind/mgr/dashboard/ci/cephadm/start-cluster.sh new file mode 100755 index 000000000..26fbd8a7c --- /dev/null +++ b/src/pybind/mgr/dashboard/ci/cephadm/start-cluster.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash + +set -eEx + +cleanup() { + set +x + if [[ -n "$JENKINS_HOME" ]]; then + echo "Starting cleanup..." + kcli delete plan -y ceph || true + kcli delete network ceph-dashboard -y + docker container prune -f + echo "Cleanup completed." + fi +} + +on_error() { + set +x + if [ "$1" != "0" ]; then + echo "ERROR $1 thrown on line $2" + echo + echo "Collecting info..." + echo + echo "Saving MGR logs:" + echo + mkdir -p ${CEPH_DEV_FOLDER}/logs + kcli ssh -u root -- ceph-node-00 'cephadm logs -n \$(cephadm ls | grep -Eo "mgr\.ceph[0-9a-z.-]+" | head -n 1) -- --no-tail --no-pager' > ${CEPH_DEV_FOLDER}/logs/mgr.cephadm.log + for vm_id in {0..3} + do + local vm="ceph-node-0${vm_id}" + echo "Saving journalctl from VM ${vm}:" + echo + kcli ssh -u root -- ${vm} 'journalctl --no-tail --no-pager -t cloud-init' > ${CEPH_DEV_FOLDER}/logs/journal.ceph-node-0${vm_id}.log || true + echo "Saving container logs:" + echo + kcli ssh -u root -- ${vm} 'podman logs --names --since 30s \$(podman ps -aq)' > ${CEPH_DEV_FOLDER}/logs/container.ceph-node-0${vm_id}.log || true + done + echo "TEST FAILED." + fi +} + +trap 'on_error $? $LINENO' ERR +trap 'cleanup $? $LINENO' EXIT + +sed -i '/ceph-node-/d' $HOME/.ssh/known_hosts + +: ${CEPH_DEV_FOLDER:=${PWD}} +EXTRA_PARAMS='' +DEV_MODE='' +# Check script args/options. +for arg in "$@"; do + shift + case "$arg" in + "--dev-mode") DEV_MODE='true'; EXTRA_PARAMS+=" -P dev_mode=${DEV_MODE}" ;; + "--expanded") EXTRA_PARAMS+=" -P expanded_cluster=true" ;; + esac +done + +kcli delete plan -y ceph || true + +# Build dashboard frontend (required to start the module). +cd ${CEPH_DEV_FOLDER}/src/pybind/mgr/dashboard/frontend +export NG_CLI_ANALYTICS=false +if [[ -n "$JENKINS_HOME" ]]; then + npm cache clean --force +fi +npm ci +FRONTEND_BUILD_OPTS='-- --prod' +if [[ -n "${DEV_MODE}" ]]; then + FRONTEND_BUILD_OPTS+=' --deleteOutputPath=false --watch' +fi +npm run build ${FRONTEND_BUILD_OPTS} & + +cd ${CEPH_DEV_FOLDER} +: ${VM_IMAGE:='fedora34'} +: ${VM_IMAGE_URL:='https://fedora.mirror.liteserver.nl/linux/releases/34/Cloud/x86_64/images/Fedora-Cloud-Base-34-1.2.x86_64.qcow2'} +kcli download image -p ceph-dashboard -u ${VM_IMAGE_URL} ${VM_IMAGE} +kcli delete plan -y ceph || true +kcli create plan -f src/pybind/mgr/dashboard/ci/cephadm/ceph_cluster.yml \ + -P ceph_dev_folder=${CEPH_DEV_FOLDER} \ + ${EXTRA_PARAMS} ceph + +: ${CLUSTER_DEBUG:=0} +: ${DASHBOARD_CHECK_INTERVAL:=10} +while [[ -z $(kcli ssh -u root -- ceph-node-00 'journalctl --no-tail --no-pager -t cloud-init' | grep "kcli boot finished") ]]; do + sleep ${DASHBOARD_CHECK_INTERVAL} + kcli list vm + if [[ ${CLUSTER_DEBUG} != 0 ]]; then + kcli ssh -u root -- ceph-node-00 'podman ps -a' + kcli ssh -u root -- ceph-node-00 'podman logs --names --since 30s \$(podman ps -aq)' + fi + kcli ssh -u root -- ceph-node-00 'journalctl -n 100 --no-pager -t cloud-init' +done diff --git a/src/pybind/mgr/dashboard/ci/check_grafana_dashboards.py b/src/pybind/mgr/dashboard/ci/check_grafana_dashboards.py new file mode 100644 index 000000000..d37337b40 --- /dev/null +++ b/src/pybind/mgr/dashboard/ci/check_grafana_dashboards.py @@ -0,0 +1,179 @@ +# -*- coding: utf-8 -*- +# pylint: disable=F0401 +""" +This script does: +* Scan through Angular html templates and extract tags +* Check if every tag has a corresponding Grafana dashboard by `uid` + +Usage: + python + + \ No newline at end of file diff --git a/src/pybind/mgr/dashboard/frontend/dist/en-US/main.c3b711a3156fe72f66f4.js b/src/pybind/mgr/dashboard/frontend/dist/en-US/main.c3b711a3156fe72f66f4.js new file mode 100644 index 000000000..0933ce611 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/dist/en-US/main.c3b711a3156fe72f66f4.js @@ -0,0 +1,3 @@ +var $localize=Object.assign(void 0===$localize?{}:$localize,{locale:"en-US"}); +"use strict";(function(global){global.ng=global.ng||{};global.ng.common=global.ng.common||{};global.ng.common.locales=global.ng.common.locales||{};const u=undefined;function plural(n){const i=Math.floor(Math.abs(n)),v=n.toString().replace(/^[^.]*\.?/,"").length;if(i===1&&v===0)return 1;return 5}global.ng.common.locales["en-us-posix"]=["en-US-POSIX",[["a","p"],["AM","PM"],u],[["AM","PM"],u,u],[["S","M","T","W","T","F","S"],["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],["Su","Mo","Tu","We","Th","Fr","Sa"]],u,[["J","F","M","A","M","J","J","A","S","O","N","D"],["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],["January","February","March","April","May","June","July","August","September","October","November","December"]],u,[["B","A"],["BC","AD"],["Before Christ","Anno Domini"]],0,[6,0],["M/d/yy","MMM d, y","MMMM d, y","EEEE, MMMM d, y"],["h:mm a","h:mm:ss a","h:mm:ss a z","h:mm:ss a zzzz"],["{1}, {0}",u,"{1} 'at' {0}",u],[".",",",";","%","+","-","E","\xD7","0/00","INF","NaN",":"],["0.######","0%","\xA4\xA00.00","0.000000E+000"],"USD","$","US Dollar",{},"ltr",plural,[[["mi","n","in the morning","in the afternoon","in the evening","at night"],["midnight","noon","in the morning","in the afternoon","in the evening","at night"],u],[["midnight","noon","morning","afternoon","evening","night"],u,u],["00:00","12:00",["06:00","12:00"],["12:00","18:00"],["18:00","21:00"],["21:00","06:00"]]]]})(typeof globalThis!=="undefined"&&globalThis||typeof global!=="undefined"&&global||typeof window!=="undefined"&&window);; +var vm=Math.pow;(self.webpackChunkceph_dashboard=self.webpackChunkceph_dashboard||[]).push([[179],{98255:v=>{function T(i){return Promise.resolve().then(()=>{var r=new Error("Cannot find module '"+i+"'");throw r.code="MODULE_NOT_FOUND",r})}T.keys=()=>[],T.resolve=T,T.id=98255,v.exports=T},56083:(v,T,i)=>{"use strict";i.d(T,{l3:()=>p,_j:()=>r,LC:()=>u,ZN:()=>H,jt:()=>e,vP:()=>y,SB:()=>A,oB:()=>S,eR:()=>L,X$:()=>d,ZE:()=>se,k1:()=>Ee});class r{}class u{}const p="*";function d(ie,he){return{type:7,name:ie,definitions:he,options:{}}}function e(ie,he=null){return{type:4,styles:he,timings:ie}}function y(ie,he=null){return{type:2,steps:ie,options:he}}function S(ie){return{type:6,styles:ie,offset:null}}function A(ie,he,ge){return{type:0,name:ie,styles:he,options:ge}}function L(ie,he,ge=null){return{type:1,expr:ie,animation:he,options:ge}}function ae(ie){Promise.resolve(null).then(ie)}class H{constructor(he=0,ge=0){this._onDoneFns=[],this._onStartFns=[],this._onDestroyFns=[],this._started=!1,this._destroyed=!1,this._finished=!1,this._position=0,this.parentPlayer=null,this.totalTime=he+ge}_onFinish(){this._finished||(this._finished=!0,this._onDoneFns.forEach(he=>he()),this._onDoneFns=[])}onStart(he){this._onStartFns.push(he)}onDone(he){this._onDoneFns.push(he)}onDestroy(he){this._onDestroyFns.push(he)}hasStarted(){return this._started}init(){}play(){this.hasStarted()||(this._onStart(),this.triggerMicrotask()),this._started=!0}triggerMicrotask(){ae(()=>this._onFinish())}_onStart(){this._onStartFns.forEach(he=>he()),this._onStartFns=[]}pause(){}restart(){}finish(){this._onFinish()}destroy(){this._destroyed||(this._destroyed=!0,this.hasStarted()||this._onStart(),this.finish(),this._onDestroyFns.forEach(he=>he()),this._onDestroyFns=[])}reset(){this._started=!1}setPosition(he){this._position=this.totalTime?he*this.totalTime:1}getPosition(){return this.totalTime?this._position/this.totalTime:1}triggerCallback(he){const ge="start"==he?this._onStartFns:this._onDoneFns;ge.forEach(De=>De()),ge.length=0}}class se{constructor(he){this._onDoneFns=[],this._onStartFns=[],this._finished=!1,this._started=!1,this._destroyed=!1,this._onDestroyFns=[],this.parentPlayer=null,this.totalTime=0,this.players=he;let ge=0,De=0,ce=0;const lt=this.players.length;0==lt?ae(()=>this._onFinish()):this.players.forEach(Ve=>{Ve.onDone(()=>{++ge==lt&&this._onFinish()}),Ve.onDestroy(()=>{++De==lt&&this._onDestroy()}),Ve.onStart(()=>{++ce==lt&&this._onStart()})}),this.totalTime=this.players.reduce((Ve,ze)=>Math.max(Ve,ze.totalTime),0)}_onFinish(){this._finished||(this._finished=!0,this._onDoneFns.forEach(he=>he()),this._onDoneFns=[])}init(){this.players.forEach(he=>he.init())}onStart(he){this._onStartFns.push(he)}_onStart(){this.hasStarted()||(this._started=!0,this._onStartFns.forEach(he=>he()),this._onStartFns=[])}onDone(he){this._onDoneFns.push(he)}onDestroy(he){this._onDestroyFns.push(he)}hasStarted(){return this._started}play(){this.parentPlayer||this.init(),this._onStart(),this.players.forEach(he=>he.play())}pause(){this.players.forEach(he=>he.pause())}restart(){this.players.forEach(he=>he.restart())}finish(){this._onFinish(),this.players.forEach(he=>he.finish())}destroy(){this._onDestroy()}_onDestroy(){this._destroyed||(this._destroyed=!0,this._onFinish(),this.players.forEach(he=>he.destroy()),this._onDestroyFns.forEach(he=>he()),this._onDestroyFns=[])}reset(){this.players.forEach(he=>he.reset()),this._destroyed=!1,this._finished=!1,this._started=!1}setPosition(he){const ge=he*this.totalTime;this.players.forEach(De=>{const ce=De.totalTime?Math.min(1,ge/De.totalTime):1;De.setPosition(ce)})}getPosition(){const he=this.players.reduce((ge,De)=>null===ge||De.totalTime>ge.totalTime?De:ge,null);return null!=he?he.getPosition():0}beforeDestroy(){this.players.forEach(he=>{he.beforeDestroy&&he.beforeDestroy()})}triggerCallback(he){const ge="start"==he?this._onStartFns:this._onDoneFns;ge.forEach(De=>De()),ge.length=0}}const Ee="!"},12057:(v,T,i)=>{"use strict";i.d(T,{mr:()=>se,Ov:()=>Ws,ez:()=>Eo,K0:()=>y,uU:()=>So,JJ:()=>an,x:()=>Be,Do:()=>ie,Gx:()=>to,Ts:()=>sn,Nd:()=>$e,V_:()=>N,Ye:()=>he,S$:()=>ae,i8:()=>Po,mk:()=>si,sg:()=>co,O5:()=>ls,PC:()=>Il,RF:()=>us,n9:()=>ya,ED:()=>el,tP:()=>fo,b0:()=>Ee,Zx:()=>ti,lw:()=>S,rS:()=>Ls,Tn:()=>Pe,gd:()=>ps,EM:()=>Fs,JF:()=>Zl,p6:()=>_i,Mn:()=>It,ol:()=>tn,UT:()=>Zt,NF:()=>bc,w_:()=>_,bD:()=>ba,q:()=>p,Mx:()=>on,HT:()=>e});var r=i(74788);let u=null;function p(){return u}function e(rt){u||(u=rt)}class _{}const y=new r.OlP("DocumentToken");let S=(()=>{class rt{historyGo(dt){throw new Error("Not implemented")}}return rt.\u0275fac=function(dt){return new(dt||rt)},rt.\u0275prov=(0,r.Yz7)({factory:A,token:rt,providedIn:"platform"}),rt})();function A(){return(0,r.LFG)(L)}const N=new r.OlP("Location Initialized");let L=(()=>{class rt extends S{constructor(dt){super(),this._doc=dt,this._init()}_init(){this.location=window.location,this._history=window.history}getBaseHrefFromDOM(){return p().getBaseHref(this._doc)}onPopState(dt){const Te=p().getGlobalEventTarget(this._doc,"window");return Te.addEventListener("popstate",dt,!1),()=>Te.removeEventListener("popstate",dt)}onHashChange(dt){const Te=p().getGlobalEventTarget(this._doc,"window");return Te.addEventListener("hashchange",dt,!1),()=>Te.removeEventListener("hashchange",dt)}get href(){return this.location.href}get protocol(){return this.location.protocol}get hostname(){return this.location.hostname}get port(){return this.location.port}get pathname(){return this.location.pathname}get search(){return this.location.search}get hash(){return this.location.hash}set pathname(dt){this.location.pathname=dt}pushState(dt,Te,Me){Z()?this._history.pushState(dt,Te,Me):this.location.hash=Me}replaceState(dt,Te,Me){Z()?this._history.replaceState(dt,Te,Me):this.location.hash=Me}forward(){this._history.forward()}back(){this._history.back()}historyGo(dt=0){this._history.go(dt)}getState(){return this._history.state}}return rt.\u0275fac=function(dt){return new(dt||rt)(r.LFG(y))},rt.\u0275prov=(0,r.Yz7)({factory:J,token:rt,providedIn:"platform"}),rt})();function Z(){return!!window.history.pushState}function J(){return new L((0,r.LFG)(y))}function K(rt,Pt){if(0==rt.length)return Pt;if(0==Pt.length)return rt;let dt=0;return rt.endsWith("/")&&dt++,Pt.startsWith("/")&&dt++,2==dt?rt+Pt.substring(1):1==dt?rt+Pt:rt+"/"+Pt}function ee(rt){const Pt=rt.match(/#|\?|$/),dt=Pt&&Pt.index||rt.length;return rt.slice(0,dt-("/"===rt[dt-1]?1:0))+rt.slice(dt)}function ue(rt){return rt&&"?"!==rt[0]?"?"+rt:rt}let ae=(()=>{class rt{historyGo(dt){throw new Error("Not implemented")}}return rt.\u0275fac=function(dt){return new(dt||rt)},rt.\u0275prov=(0,r.Yz7)({factory:H,token:rt,providedIn:"root"}),rt})();function H(rt){const Pt=(0,r.LFG)(y).location;return new Ee((0,r.LFG)(S),Pt&&Pt.origin||"")}const se=new r.OlP("appBaseHref");let Ee=(()=>{class rt extends ae{constructor(dt,Te){if(super(),this._platformLocation=dt,this._removeListenerFns=[],null==Te&&(Te=this._platformLocation.getBaseHrefFromDOM()),null==Te)throw new Error("No base href set. Please provide a value for the APP_BASE_HREF token or add a base element to the document.");this._baseHref=Te}ngOnDestroy(){for(;this._removeListenerFns.length;)this._removeListenerFns.pop()()}onPopState(dt){this._removeListenerFns.push(this._platformLocation.onPopState(dt),this._platformLocation.onHashChange(dt))}getBaseHref(){return this._baseHref}prepareExternalUrl(dt){return K(this._baseHref,dt)}path(dt=!1){const Te=this._platformLocation.pathname+ue(this._platformLocation.search),Me=this._platformLocation.hash;return Me&&dt?`${Te}${Me}`:Te}pushState(dt,Te,Me,xe){const Ct=this.prepareExternalUrl(Me+ue(xe));this._platformLocation.pushState(dt,Te,Ct)}replaceState(dt,Te,Me,xe){const Ct=this.prepareExternalUrl(Me+ue(xe));this._platformLocation.replaceState(dt,Te,Ct)}forward(){this._platformLocation.forward()}back(){this._platformLocation.back()}historyGo(dt=0){var Te,Me;null===(Me=(Te=this._platformLocation).historyGo)||void 0===Me||Me.call(Te,dt)}}return rt.\u0275fac=function(dt){return new(dt||rt)(r.LFG(S),r.LFG(se,8))},rt.\u0275prov=r.Yz7({token:rt,factory:rt.\u0275fac}),rt})(),ie=(()=>{class rt extends ae{constructor(dt,Te){super(),this._platformLocation=dt,this._baseHref="",this._removeListenerFns=[],null!=Te&&(this._baseHref=Te)}ngOnDestroy(){for(;this._removeListenerFns.length;)this._removeListenerFns.pop()()}onPopState(dt){this._removeListenerFns.push(this._platformLocation.onPopState(dt),this._platformLocation.onHashChange(dt))}getBaseHref(){return this._baseHref}path(dt=!1){let Te=this._platformLocation.hash;return null==Te&&(Te="#"),Te.length>0?Te.substring(1):Te}prepareExternalUrl(dt){const Te=K(this._baseHref,dt);return Te.length>0?"#"+Te:Te}pushState(dt,Te,Me,xe){let Ct=this.prepareExternalUrl(Me+ue(xe));0==Ct.length&&(Ct=this._platformLocation.pathname),this._platformLocation.pushState(dt,Te,Ct)}replaceState(dt,Te,Me,xe){let Ct=this.prepareExternalUrl(Me+ue(xe));0==Ct.length&&(Ct=this._platformLocation.pathname),this._platformLocation.replaceState(dt,Te,Ct)}forward(){this._platformLocation.forward()}back(){this._platformLocation.back()}historyGo(dt=0){var Te,Me;null===(Me=(Te=this._platformLocation).historyGo)||void 0===Me||Me.call(Te,dt)}}return rt.\u0275fac=function(dt){return new(dt||rt)(r.LFG(S),r.LFG(se,8))},rt.\u0275prov=r.Yz7({token:rt,factory:rt.\u0275fac}),rt})(),he=(()=>{class rt{constructor(dt,Te){this._subject=new r.vpe,this._urlChangeListeners=[],this._platformStrategy=dt;const Me=this._platformStrategy.getBaseHref();this._platformLocation=Te,this._baseHref=ee(ce(Me)),this._platformStrategy.onPopState(xe=>{this._subject.emit({url:this.path(!0),pop:!0,state:xe.state,type:xe.type})})}path(dt=!1){return this.normalize(this._platformStrategy.path(dt))}getState(){return this._platformLocation.getState()}isCurrentPathEqualTo(dt,Te=""){return this.path()==this.normalize(dt+ue(Te))}normalize(dt){return rt.stripTrailingSlash(function(rt,Pt){return rt&&Pt.startsWith(rt)?Pt.substring(rt.length):Pt}(this._baseHref,ce(dt)))}prepareExternalUrl(dt){return dt&&"/"!==dt[0]&&(dt="/"+dt),this._platformStrategy.prepareExternalUrl(dt)}go(dt,Te="",Me=null){this._platformStrategy.pushState(Me,"",dt,Te),this._notifyUrlChangeListeners(this.prepareExternalUrl(dt+ue(Te)),Me)}replaceState(dt,Te="",Me=null){this._platformStrategy.replaceState(Me,"",dt,Te),this._notifyUrlChangeListeners(this.prepareExternalUrl(dt+ue(Te)),Me)}forward(){this._platformStrategy.forward()}back(){this._platformStrategy.back()}historyGo(dt=0){var Te,Me;null===(Me=(Te=this._platformStrategy).historyGo)||void 0===Me||Me.call(Te,dt)}onUrlChange(dt){this._urlChangeListeners.push(dt),this._urlChangeSubscription||(this._urlChangeSubscription=this.subscribe(Te=>{this._notifyUrlChangeListeners(Te.url,Te.state)}))}_notifyUrlChangeListeners(dt="",Te){this._urlChangeListeners.forEach(Me=>Me(dt,Te))}subscribe(dt,Te,Me){return this._subject.subscribe({next:dt,error:Te,complete:Me})}}return rt.\u0275fac=function(dt){return new(dt||rt)(r.LFG(ae),r.LFG(S))},rt.normalizeQueryParams=ue,rt.joinWithSlash=K,rt.stripTrailingSlash=ee,rt.\u0275prov=(0,r.Yz7)({factory:ge,token:rt,providedIn:"root"}),rt})();function ge(){return new he((0,r.LFG)(ae),(0,r.LFG)(S))}function ce(rt){return rt.replace(/\/index.html$/,"")}var Ve=(()=>((Ve=Ve||{})[Ve.Decimal=0]="Decimal",Ve[Ve.Percent=1]="Percent",Ve[Ve.Currency=2]="Currency",Ve[Ve.Scientific=3]="Scientific",Ve))(),ze=(()=>((ze=ze||{})[ze.Zero=0]="Zero",ze[ze.One=1]="One",ze[ze.Two=2]="Two",ze[ze.Few=3]="Few",ze[ze.Many=4]="Many",ze[ze.Other=5]="Other",ze))(),Be=(()=>((Be=Be||{})[Be.Format=0]="Format",Be[Be.Standalone=1]="Standalone",Be))(),Pe=(()=>((Pe=Pe||{})[Pe.Narrow=0]="Narrow",Pe[Pe.Abbreviated=1]="Abbreviated",Pe[Pe.Wide=2]="Wide",Pe[Pe.Short=3]="Short",Pe))(),je=(()=>((je=je||{})[je.Short=0]="Short",je[je.Medium=1]="Medium",je[je.Long=2]="Long",je[je.Full=3]="Full",je))(),He=(()=>((He=He||{})[He.Decimal=0]="Decimal",He[He.Group=1]="Group",He[He.List=2]="List",He[He.PercentSign=3]="PercentSign",He[He.PlusSign=4]="PlusSign",He[He.MinusSign=5]="MinusSign",He[He.Exponential=6]="Exponential",He[He.SuperscriptingExponent=7]="SuperscriptingExponent",He[He.PerMille=8]="PerMille",He[He.Infinity=9]="Infinity",He[He.NaN=10]="NaN",He[He.TimeSeparator=11]="TimeSeparator",He[He.CurrencyDecimal=12]="CurrencyDecimal",He[He.CurrencyGroup=13]="CurrencyGroup",He))();function tn(rt,Pt,dt){const Te=(0,r.cg1)(rt),xe=fi([Te[r.wAp.DayPeriodsFormat],Te[r.wAp.DayPeriodsStandalone]],Pt);return fi(xe,dt)}function It(rt,Pt,dt){const Te=(0,r.cg1)(rt),xe=fi([Te[r.wAp.DaysFormat],Te[r.wAp.DaysStandalone]],Pt);return fi(xe,dt)}function Zt(rt,Pt,dt){const Te=(0,r.cg1)(rt),xe=fi([Te[r.wAp.MonthsFormat],Te[r.wAp.MonthsStandalone]],Pt);return fi(xe,dt)}function Gt(rt,Pt){return fi((0,r.cg1)(rt)[r.wAp.DateFormat],Pt)}function xt(rt,Pt){return fi((0,r.cg1)(rt)[r.wAp.TimeFormat],Pt)}function Xt(rt,Pt){return fi((0,r.cg1)(rt)[r.wAp.DateTimeFormat],Pt)}function Zn(rt,Pt){const dt=(0,r.cg1)(rt),Te=dt[r.wAp.NumberSymbols][Pt];if(void 0===Te){if(Pt===He.CurrencyDecimal)return dt[r.wAp.NumberSymbols][He.Decimal];if(Pt===He.CurrencyGroup)return dt[r.wAp.NumberSymbols][He.Group]}return Te}function Ur(rt,Pt){return(0,r.cg1)(rt)[r.wAp.NumberFormats][Pt]}const ei=r.kL8;function Nn(rt){if(!rt[r.wAp.ExtraData])throw new Error(`Missing extra locale data for the locale "${rt[r.wAp.LocaleId]}". Use "registerLocaleData" to load new data. See the "I18n guide" on angular.io to know more.`)}function fi(rt,Pt){for(let dt=Pt;dt>-1;dt--)if(void 0!==rt[dt])return rt[dt];throw new Error("Locale data API: locale data undefined")}function ki(rt){const[Pt,dt]=rt.split(":");return{hours:+Pt,minutes:+dt}}const Wt=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/,zn={},rr=/((?:[^BEGHLMOSWYZabcdhmswyz']+)|(?:'(?:[^']|'')*')|(?:G{1,5}|y{1,4}|Y{1,4}|M{1,5}|L{1,5}|w{1,2}|W{1}|d{1,2}|E{1,6}|c{1,6}|a{1,5}|b{1,5}|B{1,5}|h{1,2}|H{1,2}|m{1,2}|s{1,2}|S{1,3}|z{1,4}|Z{1,5}|O{1,4}))([\s\S]*)/;var Fr=(()=>((Fr=Fr||{})[Fr.Short=0]="Short",Fr[Fr.ShortGMT=1]="ShortGMT",Fr[Fr.Long=2]="Long",Fr[Fr.Extended=3]="Extended",Fr))(),Gn=(()=>((Gn=Gn||{})[Gn.FullYear=0]="FullYear",Gn[Gn.Month=1]="Month",Gn[Gn.Date=2]="Date",Gn[Gn.Hours=3]="Hours",Gn[Gn.Minutes=4]="Minutes",Gn[Gn.Seconds=5]="Seconds",Gn[Gn.FractionalSeconds=6]="FractionalSeconds",Gn[Gn.Day=7]="Day",Gn))(),Jr=(()=>((Jr=Jr||{})[Jr.DayPeriods=0]="DayPeriods",Jr[Jr.Days=1]="Days",Jr[Jr.Months=2]="Months",Jr[Jr.Eras=3]="Eras",Jr))();function _i(rt,Pt,dt,Te){let Me=function(rt){if(lo(rt))return rt;if("number"==typeof rt&&!isNaN(rt))return new Date(rt);if("string"==typeof rt){if(rt=rt.trim(),/^(\d{4}(-\d{1,2}(-\d{1,2})?)?)$/.test(rt)){const[Me,xe=1,Ct=1]=rt.split("-").map(ur=>+ur);return wi(Me,xe-1,Ct)}const dt=parseFloat(rt);if(!isNaN(rt-dt))return new Date(dt);let Te;if(Te=rt.match(Wt))return function(rt){const Pt=new Date(0);let dt=0,Te=0;const Me=rt[8]?Pt.setUTCFullYear:Pt.setFullYear,xe=rt[8]?Pt.setUTCHours:Pt.setHours;rt[9]&&(dt=Number(rt[9]+rt[10]),Te=Number(rt[9]+rt[11])),Me.call(Pt,Number(rt[1]),Number(rt[2])-1,Number(rt[3]));const Ct=Number(rt[4]||0)-dt,ur=Number(rt[5]||0)-Te,Qi=Number(rt[6]||0),Go=Math.floor(1e3*parseFloat("0."+(rt[7]||0)));return xe.call(Pt,Ct,ur,Qi,Go),Pt}(Te)}const Pt=new Date(rt);if(!lo(Pt))throw new Error(`Unable to convert "${rt}" into a date`);return Pt}(rt);Pt=br(dt,Pt)||Pt;let ur,Ct=[];for(;Pt;){if(ur=rr.exec(Pt),!ur){Ct.push(Pt);break}{Ct=Ct.concat(ur.slice(1));const ms=Ct.pop();if(!ms)break;Pt=ms}}let Qi=Me.getTimezoneOffset();Te&&(Qi=Wr(Te,Qi),Me=function(rt,Pt,dt){const Me=rt.getTimezoneOffset();return function(rt,Pt){return(rt=new Date(rt.getTime())).setMinutes(rt.getMinutes()+Pt),rt}(rt,-1*(Wr(Pt,Me)-Me))}(Me,Te));let Go="";return Ct.forEach(ms=>{const hs=function(rt){if(An[rt])return An[rt];let Pt;switch(rt){case"G":case"GG":case"GGG":Pt=Vn(Jr.Eras,Pe.Abbreviated);break;case"GGGG":Pt=Vn(Jr.Eras,Pe.Wide);break;case"GGGGG":Pt=Vn(Jr.Eras,Pe.Narrow);break;case"y":Pt=gr(Gn.FullYear,1,0,!1,!0);break;case"yy":Pt=gr(Gn.FullYear,2,0,!0,!0);break;case"yyy":Pt=gr(Gn.FullYear,3,0,!1,!0);break;case"yyyy":Pt=gr(Gn.FullYear,4,0,!1,!0);break;case"Y":Pt=mi(1);break;case"YY":Pt=mi(2,!0);break;case"YYY":Pt=mi(3);break;case"YYYY":Pt=mi(4);break;case"M":case"L":Pt=gr(Gn.Month,1,1);break;case"MM":case"LL":Pt=gr(Gn.Month,2,1);break;case"MMM":Pt=Vn(Jr.Months,Pe.Abbreviated);break;case"MMMM":Pt=Vn(Jr.Months,Pe.Wide);break;case"MMMMM":Pt=Vn(Jr.Months,Pe.Narrow);break;case"LLL":Pt=Vn(Jr.Months,Pe.Abbreviated,Be.Standalone);break;case"LLLL":Pt=Vn(Jr.Months,Pe.Wide,Be.Standalone);break;case"LLLLL":Pt=Vn(Jr.Months,Pe.Narrow,Be.Standalone);break;case"w":Pt=kr(1);break;case"ww":Pt=kr(2);break;case"W":Pt=kr(1,!0);break;case"d":Pt=gr(Gn.Date,1);break;case"dd":Pt=gr(Gn.Date,2);break;case"c":case"cc":Pt=gr(Gn.Day,1);break;case"ccc":Pt=Vn(Jr.Days,Pe.Abbreviated,Be.Standalone);break;case"cccc":Pt=Vn(Jr.Days,Pe.Wide,Be.Standalone);break;case"ccccc":Pt=Vn(Jr.Days,Pe.Narrow,Be.Standalone);break;case"cccccc":Pt=Vn(Jr.Days,Pe.Short,Be.Standalone);break;case"E":case"EE":case"EEE":Pt=Vn(Jr.Days,Pe.Abbreviated);break;case"EEEE":Pt=Vn(Jr.Days,Pe.Wide);break;case"EEEEE":Pt=Vn(Jr.Days,Pe.Narrow);break;case"EEEEEE":Pt=Vn(Jr.Days,Pe.Short);break;case"a":case"aa":case"aaa":Pt=Vn(Jr.DayPeriods,Pe.Abbreviated);break;case"aaaa":Pt=Vn(Jr.DayPeriods,Pe.Wide);break;case"aaaaa":Pt=Vn(Jr.DayPeriods,Pe.Narrow);break;case"b":case"bb":case"bbb":Pt=Vn(Jr.DayPeriods,Pe.Abbreviated,Be.Standalone,!0);break;case"bbbb":Pt=Vn(Jr.DayPeriods,Pe.Wide,Be.Standalone,!0);break;case"bbbbb":Pt=Vn(Jr.DayPeriods,Pe.Narrow,Be.Standalone,!0);break;case"B":case"BB":case"BBB":Pt=Vn(Jr.DayPeriods,Pe.Abbreviated,Be.Format,!0);break;case"BBBB":Pt=Vn(Jr.DayPeriods,Pe.Wide,Be.Format,!0);break;case"BBBBB":Pt=Vn(Jr.DayPeriods,Pe.Narrow,Be.Format,!0);break;case"h":Pt=gr(Gn.Hours,1,-12);break;case"hh":Pt=gr(Gn.Hours,2,-12);break;case"H":Pt=gr(Gn.Hours,1);break;case"HH":Pt=gr(Gn.Hours,2);break;case"m":Pt=gr(Gn.Minutes,1);break;case"mm":Pt=gr(Gn.Minutes,2);break;case"s":Pt=gr(Gn.Seconds,1);break;case"ss":Pt=gr(Gn.Seconds,2);break;case"S":Pt=gr(Gn.FractionalSeconds,1);break;case"SS":Pt=gr(Gn.FractionalSeconds,2);break;case"SSS":Pt=gr(Gn.FractionalSeconds,3);break;case"Z":case"ZZ":case"ZZZ":Pt=Dn(Fr.Short);break;case"ZZZZZ":Pt=Dn(Fr.Extended);break;case"O":case"OO":case"OOO":case"z":case"zz":case"zzz":Pt=Dn(Fr.ShortGMT);break;case"OOOO":case"ZZZZ":case"zzzz":Pt=Dn(Fr.Long);break;default:return null}return An[rt]=Pt,Pt}(ms);Go+=hs?hs(Me,dt,Qi):"''"===ms?"'":ms.replace(/(^'|'$)/g,"").replace(/''/g,"'")}),Go}function wi(rt,Pt,dt){const Te=new Date(0);return Te.setFullYear(rt,Pt,dt),Te.setHours(0,0,0),Te}function br(rt,Pt){const dt=function(rt){return(0,r.cg1)(rt)[r.wAp.LocaleId]}(rt);if(zn[dt]=zn[dt]||{},zn[dt][Pt])return zn[dt][Pt];let Te="";switch(Pt){case"shortDate":Te=Gt(rt,je.Short);break;case"mediumDate":Te=Gt(rt,je.Medium);break;case"longDate":Te=Gt(rt,je.Long);break;case"fullDate":Te=Gt(rt,je.Full);break;case"shortTime":Te=xt(rt,je.Short);break;case"mediumTime":Te=xt(rt,je.Medium);break;case"longTime":Te=xt(rt,je.Long);break;case"fullTime":Te=xt(rt,je.Full);break;case"short":const Me=br(rt,"shortTime"),xe=br(rt,"shortDate");Te=Dr(Xt(rt,je.Short),[Me,xe]);break;case"medium":const Ct=br(rt,"mediumTime"),ur=br(rt,"mediumDate");Te=Dr(Xt(rt,je.Medium),[Ct,ur]);break;case"long":const Qi=br(rt,"longTime"),Go=br(rt,"longDate");Te=Dr(Xt(rt,je.Long),[Qi,Go]);break;case"full":const ms=br(rt,"fullTime"),hs=br(rt,"fullDate");Te=Dr(Xt(rt,je.Full),[ms,hs])}return Te&&(zn[dt][Pt]=Te),Te}function Dr(rt,Pt){return Pt&&(rt=rt.replace(/\{([^}]+)}/g,function(dt,Te){return null!=Pt&&Te in Pt?Pt[Te]:dt})),rt}function gn(rt,Pt,dt="-",Te,Me){let xe="";(rt<0||Me&&rt<=0)&&(Me?rt=1-rt:(rt=-rt,xe=dt));let Ct=String(rt);for(;Ct.length0||ur>-dt)&&(ur+=dt),rt===Gn.Hours)0===ur&&-12===dt&&(ur=12);else if(rt===Gn.FractionalSeconds)return function(rt,Pt){return gn(rt,3).substr(0,Pt)}(ur,Pt);const Qi=Zn(Ct,He.MinusSign);return gn(ur,Pt,Qi,Te,Me)}}function Vn(rt,Pt,dt=Be.Format,Te=!1){return function(Me,xe){return function(rt,Pt,dt,Te,Me,xe){switch(dt){case Jr.Months:return Zt(Pt,Me,Te)[rt.getMonth()];case Jr.Days:return It(Pt,Me,Te)[rt.getDay()];case Jr.DayPeriods:const Ct=rt.getHours(),ur=rt.getMinutes();if(xe){const Go=function(rt){const Pt=(0,r.cg1)(rt);return Nn(Pt),(Pt[r.wAp.ExtraData][2]||[]).map(Te=>"string"==typeof Te?ki(Te):[ki(Te[0]),ki(Te[1])])}(Pt),ms=function(rt,Pt,dt){const Te=(0,r.cg1)(rt);Nn(Te);const xe=fi([Te[r.wAp.ExtraData][0],Te[r.wAp.ExtraData][1]],Pt)||[];return fi(xe,dt)||[]}(Pt,Me,Te),hs=Go.findIndex(Ts=>{if(Array.isArray(Ts)){const[Ma,au]=Ts,Wa=Ct>=Ma.hours&&ur>=Ma.minutes,Rl=Ct0?Math.floor(Me/60):Math.ceil(Me/60);switch(rt){case Fr.Short:return(Me>=0?"+":"")+gn(Ct,2,xe)+gn(Math.abs(Me%60),2,xe);case Fr.ShortGMT:return"GMT"+(Me>=0?"+":"")+gn(Ct,1,xe);case Fr.Long:return"GMT"+(Me>=0?"+":"")+gn(Ct,2,xe)+":"+gn(Math.abs(Me%60),2,xe);case Fr.Extended:return 0===Te?"Z":(Me>=0?"+":"")+gn(Ct,2,xe)+":"+gn(Math.abs(Me%60),2,xe);default:throw new Error(`Unknown zone width "${rt}"`)}}}function Ge(rt){return wi(rt.getFullYear(),rt.getMonth(),rt.getDate()+(4-rt.getDay()))}function kr(rt,Pt=!1){return function(dt,Te){let Me;if(Pt){const xe=new Date(dt.getFullYear(),dt.getMonth(),1).getDay()-1,Ct=dt.getDate();Me=1+Math.floor((Ct+xe)/7)}else{const xe=Ge(dt),Ct=function(rt){const Pt=wi(rt,0,1).getDay();return wi(rt,0,1+(Pt<=4?4:11)-Pt)}(xe.getFullYear()),ur=xe.getTime()-Ct.getTime();Me=1+Math.round(ur/6048e5)}return gn(Me,rt,Zn(Te,He.MinusSign))}}function mi(rt,Pt=!1){return function(dt,Te){return gn(Ge(dt).getFullYear(),rt,Zn(Te,He.MinusSign),Pt)}}const An={};function Wr(rt,Pt){rt=rt.replace(/:/g,"");const dt=Date.parse("Jan 01, 1970 00:00:00 "+rt)/6e4;return isNaN(dt)?Pt:dt}function lo(rt){return rt instanceof Date&&!isNaN(rt.valueOf())}const vo=/^(\d+)?\.((\d+)(-(\d+))?)?$/;function lr(rt,Pt,dt,Te,Me,xe,Ct=!1){let ur="",Qi=!1;if(isFinite(rt)){let Go=function(rt){let Te,Me,xe,Ct,ur,Pt=Math.abs(rt)+"",dt=0;for((Me=Pt.indexOf("."))>-1&&(Pt=Pt.replace(".","")),(xe=Pt.search(/e/i))>0?(Me<0&&(Me=xe),Me+=+Pt.slice(xe+1),Pt=Pt.substring(0,xe)):Me<0&&(Me=Pt.length),xe=0;"0"===Pt.charAt(xe);xe++);if(xe===(ur=Pt.length))Te=[0],Me=1;else{for(ur--;"0"===Pt.charAt(ur);)ur--;for(Me-=xe,Te=[],Ct=0;xe<=ur;xe++,Ct++)Te[Ct]=Number(Pt.charAt(xe))}return Me>22&&(Te=Te.splice(0,21),dt=Me-1,Me=1),{digits:Te,exponent:dt,integerLen:Me}}(rt);Ct&&(Go=function(rt){if(0===rt.digits[0])return rt;const Pt=rt.digits.length-rt.integerLen;return rt.exponent?rt.exponent+=2:(0===Pt?rt.digits.push(0,0):1===Pt&&rt.digits.push(0),rt.integerLen+=2),rt}(Go));let ms=Pt.minInt,hs=Pt.minFrac,Ts=Pt.maxFrac;if(xe){const Al=xe.match(vo);if(null===Al)throw new Error(`${xe} is not a valid digit info`);const vs=Al[1],Lc=Al[3],Tc=Al[5];null!=vs&&(ms=Ar(vs)),null!=Lc&&(hs=Ar(Lc)),null!=Tc?Ts=Ar(Tc):null!=Lc&&hs>Ts&&(Ts=hs)}!function(rt,Pt,dt){if(Pt>dt)throw new Error(`The minimum number of digits after fraction (${Pt}) is higher than the maximum (${dt}).`);let Te=rt.digits,Me=Te.length-rt.integerLen;const xe=Math.min(Math.max(Pt,Me),dt);let Ct=xe+rt.integerLen,ur=Te[Ct];if(Ct>0){Te.splice(Math.max(rt.integerLen,Ct));for(let hs=Ct;hs=5)if(Ct-1<0){for(let hs=0;hs>Ct;hs--)Te.unshift(0),rt.integerLen++;Te.unshift(1),rt.integerLen++}else Te[Ct-1]++;for(;Me=Go?au.pop():Qi=!1),Ts>=10?1:0},0);ms&&(Te.unshift(ms),rt.integerLen++)}(Go,hs,Ts);let Ma=Go.digits,au=Go.integerLen;const Wa=Go.exponent;let Rl=[];for(Qi=Ma.every(Al=>!Al);au0?Rl=Ma.splice(au,Ma.length):(Rl=Ma,Ma=[0]);const nc=[];for(Ma.length>=Pt.lgSize&&nc.unshift(Ma.splice(-Pt.lgSize,Ma.length).join(""));Ma.length>Pt.gSize;)nc.unshift(Ma.splice(-Pt.gSize,Ma.length).join(""));Ma.length&&nc.unshift(Ma.join("")),ur=nc.join(Zn(dt,Te)),Rl.length&&(ur+=Zn(dt,Me)+Rl.join("")),Wa&&(ur+=Zn(dt,He.Exponential)+"+"+Wa)}else ur=Zn(dt,He.Infinity);return ur=rt<0&&!Qi?Pt.negPre+ur+Pt.negSuf:Pt.posPre+ur+Pt.posSuf,ur}function uo(rt,Pt="-"){const dt={minInt:1,minFrac:0,maxFrac:0,posPre:"",posSuf:"",negPre:"",negSuf:"",gSize:0,lgSize:0},Te=rt.split(";"),Me=Te[0],xe=Te[1],Ct=-1!==Me.indexOf(".")?Me.split("."):[Me.substring(0,Me.lastIndexOf("0")+1),Me.substring(Me.lastIndexOf("0")+1)],ur=Ct[0],Qi=Ct[1]||"";dt.posPre=ur.substr(0,ur.indexOf("#"));for(let ms=0;ms{class rt extends Rt{constructor(dt){super(),this.locale=dt}getPluralCategory(dt,Te){switch(ei(Te||this.locale)(dt)){case ze.Zero:return"zero";case ze.One:return"one";case ze.Two:return"two";case ze.Few:return"few";case ze.Many:return"many";default:return"other"}}}return rt.\u0275fac=function(dt){return new(dt||rt)(r.LFG(r.soG))},rt.\u0275prov=r.Yz7({token:rt,factory:rt.\u0275fac}),rt})();function on(rt,Pt){Pt=encodeURIComponent(Pt);for(const dt of rt.split(";")){const Te=dt.indexOf("="),[Me,xe]=-1==Te?[dt,""]:[dt.slice(0,Te),dt.slice(Te+1)];if(Me.trim()===Pt)return decodeURIComponent(xe)}return null}let si=(()=>{class rt{constructor(dt,Te,Me,xe){this._iterableDiffers=dt,this._keyValueDiffers=Te,this._ngEl=Me,this._renderer=xe,this._iterableDiffer=null,this._keyValueDiffer=null,this._initialClasses=[],this._rawClass=null}set klass(dt){this._removeClasses(this._initialClasses),this._initialClasses="string"==typeof dt?dt.split(/\s+/):[],this._applyClasses(this._initialClasses),this._applyClasses(this._rawClass)}set ngClass(dt){this._removeClasses(this._rawClass),this._applyClasses(this._initialClasses),this._iterableDiffer=null,this._keyValueDiffer=null,this._rawClass="string"==typeof dt?dt.split(/\s+/):dt,this._rawClass&&((0,r.sIi)(this._rawClass)?this._iterableDiffer=this._iterableDiffers.find(this._rawClass).create():this._keyValueDiffer=this._keyValueDiffers.find(this._rawClass).create())}ngDoCheck(){if(this._iterableDiffer){const dt=this._iterableDiffer.diff(this._rawClass);dt&&this._applyIterableChanges(dt)}else if(this._keyValueDiffer){const dt=this._keyValueDiffer.diff(this._rawClass);dt&&this._applyKeyValueChanges(dt)}}_applyKeyValueChanges(dt){dt.forEachAddedItem(Te=>this._toggleClass(Te.key,Te.currentValue)),dt.forEachChangedItem(Te=>this._toggleClass(Te.key,Te.currentValue)),dt.forEachRemovedItem(Te=>{Te.previousValue&&this._toggleClass(Te.key,!1)})}_applyIterableChanges(dt){dt.forEachAddedItem(Te=>{if("string"!=typeof Te.item)throw new Error(`NgClass can only toggle CSS classes expressed as strings, got ${(0,r.AaK)(Te.item)}`);this._toggleClass(Te.item,!0)}),dt.forEachRemovedItem(Te=>this._toggleClass(Te.item,!1))}_applyClasses(dt){dt&&(Array.isArray(dt)||dt instanceof Set?dt.forEach(Te=>this._toggleClass(Te,!0)):Object.keys(dt).forEach(Te=>this._toggleClass(Te,!!dt[Te])))}_removeClasses(dt){dt&&(Array.isArray(dt)||dt instanceof Set?dt.forEach(Te=>this._toggleClass(Te,!1)):Object.keys(dt).forEach(Te=>this._toggleClass(Te,!1)))}_toggleClass(dt,Te){(dt=dt.trim())&&dt.split(/\s+/g).forEach(Me=>{Te?this._renderer.addClass(this._ngEl.nativeElement,Me):this._renderer.removeClass(this._ngEl.nativeElement,Me)})}}return rt.\u0275fac=function(dt){return new(dt||rt)(r.Y36(r.ZZ4),r.Y36(r.aQg),r.Y36(r.SBq),r.Y36(r.Qsj))},rt.\u0275dir=r.lG2({type:rt,selectors:[["","ngClass",""]],inputs:{klass:["class","klass"],ngClass:"ngClass"}}),rt})();class _o{constructor(Pt,dt,Te,Me){this.$implicit=Pt,this.ngForOf=dt,this.index=Te,this.count=Me}get first(){return 0===this.index}get last(){return this.index===this.count-1}get even(){return this.index%2==0}get odd(){return!this.even}}let co=(()=>{class rt{constructor(dt,Te,Me){this._viewContainer=dt,this._template=Te,this._differs=Me,this._ngForOf=null,this._ngForOfDirty=!0,this._differ=null}set ngForOf(dt){this._ngForOf=dt,this._ngForOfDirty=!0}set ngForTrackBy(dt){this._trackByFn=dt}get ngForTrackBy(){return this._trackByFn}set ngForTemplate(dt){dt&&(this._template=dt)}ngDoCheck(){if(this._ngForOfDirty){this._ngForOfDirty=!1;const dt=this._ngForOf;if(!this._differ&&dt)try{this._differ=this._differs.find(dt).create(this.ngForTrackBy)}catch(Te){throw new Error(`Cannot find a differ supporting object '${dt}' of type '${function(rt){return rt.name||typeof rt}(dt)}'. NgFor only supports binding to Iterables such as Arrays.`)}}if(this._differ){const dt=this._differ.diff(this._ngForOf);dt&&this._applyChanges(dt)}}_applyChanges(dt){const Te=[];dt.forEachOperation((Me,xe,Ct)=>{if(null==Me.previousIndex){const ur=this._viewContainer.createEmbeddedView(this._template,new _o(null,this._ngForOf,-1,-1),null===Ct?void 0:Ct),Qi=new Es(Me,ur);Te.push(Qi)}else if(null==Ct)this._viewContainer.remove(null===xe?void 0:xe);else if(null!==xe){const ur=this._viewContainer.get(xe);this._viewContainer.move(ur,Ct);const Qi=new Es(Me,ur);Te.push(Qi)}});for(let Me=0;Me{this._viewContainer.get(Me.currentIndex).context.$implicit=Me.item})}_perViewChange(dt,Te){dt.context.$implicit=Te.item}static ngTemplateContextGuard(dt,Te){return!0}}return rt.\u0275fac=function(dt){return new(dt||rt)(r.Y36(r.s_b),r.Y36(r.Rgc),r.Y36(r.ZZ4))},rt.\u0275dir=r.lG2({type:rt,selectors:[["","ngFor","","ngForOf",""]],inputs:{ngForOf:"ngForOf",ngForTrackBy:"ngForTrackBy",ngForTemplate:"ngForTemplate"}}),rt})();class Es{constructor(Pt,dt){this.record=Pt,this.view=dt}}let ls=(()=>{class rt{constructor(dt,Te){this._viewContainer=dt,this._context=new La,this._thenTemplateRef=null,this._elseTemplateRef=null,this._thenViewRef=null,this._elseViewRef=null,this._thenTemplateRef=Te}set ngIf(dt){this._context.$implicit=this._context.ngIf=dt,this._updateView()}set ngIfThen(dt){ta("ngIfThen",dt),this._thenTemplateRef=dt,this._thenViewRef=null,this._updateView()}set ngIfElse(dt){ta("ngIfElse",dt),this._elseTemplateRef=dt,this._elseViewRef=null,this._updateView()}_updateView(){this._context.$implicit?this._thenViewRef||(this._viewContainer.clear(),this._elseViewRef=null,this._thenTemplateRef&&(this._thenViewRef=this._viewContainer.createEmbeddedView(this._thenTemplateRef,this._context))):this._elseViewRef||(this._viewContainer.clear(),this._thenViewRef=null,this._elseTemplateRef&&(this._elseViewRef=this._viewContainer.createEmbeddedView(this._elseTemplateRef,this._context)))}static ngTemplateContextGuard(dt,Te){return!0}}return rt.\u0275fac=function(dt){return new(dt||rt)(r.Y36(r.s_b),r.Y36(r.Rgc))},rt.\u0275dir=r.lG2({type:rt,selectors:[["","ngIf",""]],inputs:{ngIf:"ngIf",ngIfThen:"ngIfThen",ngIfElse:"ngIfElse"}}),rt})();class La{constructor(){this.$implicit=null,this.ngIf=null}}function ta(rt,Pt){if(Pt&&!Pt.createEmbeddedView)throw new Error(`${rt} must be a TemplateRef, but received '${(0,r.AaK)(Pt)}'.`)}class Is{constructor(Pt,dt){this._viewContainerRef=Pt,this._templateRef=dt,this._created=!1}create(){this._created=!0,this._viewContainerRef.createEmbeddedView(this._templateRef)}destroy(){this._created=!1,this._viewContainerRef.clear()}enforceState(Pt){Pt&&!this._created?this.create():!Pt&&this._created&&this.destroy()}}let us=(()=>{class rt{constructor(){this._defaultUsed=!1,this._caseCount=0,this._lastCaseCheckIndex=0,this._lastCasesMatched=!1}set ngSwitch(dt){this._ngSwitch=dt,0===this._caseCount&&this._updateDefaultCases(!0)}_addCase(){return this._caseCount++}_addDefault(dt){this._defaultViews||(this._defaultViews=[]),this._defaultViews.push(dt)}_matchCase(dt){const Te=dt==this._ngSwitch;return this._lastCasesMatched=this._lastCasesMatched||Te,this._lastCaseCheckIndex++,this._lastCaseCheckIndex===this._caseCount&&(this._updateDefaultCases(!this._lastCasesMatched),this._lastCaseCheckIndex=0,this._lastCasesMatched=!1),Te}_updateDefaultCases(dt){if(this._defaultViews&&dt!==this._defaultUsed){this._defaultUsed=dt;for(let Te=0;Te{class rt{constructor(dt,Te,Me){this.ngSwitch=Me,Me._addCase(),this._view=new Is(dt,Te)}ngDoCheck(){this._view.enforceState(this.ngSwitch._matchCase(this.ngSwitchCase))}}return rt.\u0275fac=function(dt){return new(dt||rt)(r.Y36(r.s_b),r.Y36(r.Rgc),r.Y36(us,9))},rt.\u0275dir=r.lG2({type:rt,selectors:[["","ngSwitchCase",""]],inputs:{ngSwitchCase:"ngSwitchCase"}}),rt})(),el=(()=>{class rt{constructor(dt,Te,Me){Me._addDefault(new Is(dt,Te))}}return rt.\u0275fac=function(dt){return new(dt||rt)(r.Y36(r.s_b),r.Y36(r.Rgc),r.Y36(us,9))},rt.\u0275dir=r.lG2({type:rt,selectors:[["","ngSwitchDefault",""]]}),rt})(),Il=(()=>{class rt{constructor(dt,Te,Me){this._ngEl=dt,this._differs=Te,this._renderer=Me,this._ngStyle=null,this._differ=null}set ngStyle(dt){this._ngStyle=dt,!this._differ&&dt&&(this._differ=this._differs.find(dt).create())}ngDoCheck(){if(this._differ){const dt=this._differ.diff(this._ngStyle);dt&&this._applyChanges(dt)}}_setStyle(dt,Te){const[Me,xe]=dt.split(".");null!=(Te=null!=Te&&xe?`${Te}${xe}`:Te)?this._renderer.setStyle(this._ngEl.nativeElement,Me,Te):this._renderer.removeStyle(this._ngEl.nativeElement,Me)}_applyChanges(dt){dt.forEachRemovedItem(Te=>this._setStyle(Te.key,null)),dt.forEachAddedItem(Te=>this._setStyle(Te.key,Te.currentValue)),dt.forEachChangedItem(Te=>this._setStyle(Te.key,Te.currentValue))}}return rt.\u0275fac=function(dt){return new(dt||rt)(r.Y36(r.SBq),r.Y36(r.aQg),r.Y36(r.Qsj))},rt.\u0275dir=r.lG2({type:rt,selectors:[["","ngStyle",""]],inputs:{ngStyle:"ngStyle"}}),rt})(),fo=(()=>{class rt{constructor(dt){this._viewContainerRef=dt,this._viewRef=null,this.ngTemplateOutletContext=null,this.ngTemplateOutlet=null}ngOnChanges(dt){if(dt.ngTemplateOutlet){const Te=this._viewContainerRef;this._viewRef&&Te.remove(Te.indexOf(this._viewRef)),this._viewRef=this.ngTemplateOutlet?Te.createEmbeddedView(this.ngTemplateOutlet,this.ngTemplateOutletContext):null}else this._viewRef&&dt.ngTemplateOutletContext&&this.ngTemplateOutletContext&&(this._viewRef.context=this.ngTemplateOutletContext)}}return rt.\u0275fac=function(dt){return new(dt||rt)(r.Y36(r.s_b))},rt.\u0275dir=r.lG2({type:rt,selectors:[["","ngTemplateOutlet",""]],inputs:{ngTemplateOutletContext:"ngTemplateOutletContext",ngTemplateOutlet:"ngTemplateOutlet"},features:[r.TTD]}),rt})();function Ao(rt,Pt){return Error(`InvalidPipeArgument: '${Pt}' for pipe '${(0,r.AaK)(rt)}'`)}class fs{createSubscription(Pt,dt){return Pt.subscribe({next:dt,error:Te=>{throw Te}})}dispose(Pt){Pt.unsubscribe()}onDestroy(Pt){Pt.unsubscribe()}}class Ca{createSubscription(Pt,dt){return Pt.then(dt,Te=>{throw Te})}dispose(Pt){}onDestroy(Pt){}}const Ra=new Ca,pl=new fs;let Ws=(()=>{class rt{constructor(dt){this._ref=dt,this._latestValue=null,this._subscription=null,this._obj=null,this._strategy=null}ngOnDestroy(){this._subscription&&this._dispose()}transform(dt){return this._obj?dt!==this._obj?(this._dispose(),this.transform(dt)):this._latestValue:(dt&&this._subscribe(dt),this._latestValue)}_subscribe(dt){this._obj=dt,this._strategy=this._selectStrategy(dt),this._subscription=this._strategy.createSubscription(dt,Te=>this._updateLatestValue(dt,Te))}_selectStrategy(dt){if((0,r.QGY)(dt))return Ra;if((0,r.F4k)(dt))return pl;throw Ao(rt,dt)}_dispose(){this._strategy.dispose(this._subscription),this._latestValue=null,this._subscription=null,this._obj=null}_updateLatestValue(dt,Te){dt===this._obj&&(this._latestValue=Te,this._ref.markForCheck())}}return rt.\u0275fac=function(dt){return new(dt||rt)(r.Y36(r.sBO,16))},rt.\u0275pipe=r.Yjl({name:"async",type:rt,pure:!1}),rt})(),Po=(()=>{class rt{transform(dt){if(null==dt)return null;if("string"!=typeof dt)throw Ao(rt,dt);return dt.toLowerCase()}}return rt.\u0275fac=function(dt){return new(dt||rt)},rt.\u0275pipe=r.Yjl({name:"lowercase",type:rt,pure:!0}),rt})();const bo=/(?:[0-9A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u0870-\u0887\u0889-\u088E\u08A0-\u08C9\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C5D\u0C60\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u1711\u171F-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4C\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BF\u31F0-\u31FF\u3400-\u4DBF\u4E00-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA7CA\uA7D0\uA7D1\uA7D3\uA7D5-\uA7D9\uA7F2-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF2D-\uDF40\uDF42-\uDF49\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF]|\uD801[\uDC00-\uDC9D\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDD70-\uDD7A\uDD7C-\uDD8A\uDD8C-\uDD92\uDD94\uDD95\uDD97-\uDDA1\uDDA3-\uDDB1\uDDB3-\uDDB9\uDDBB\uDDBC\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67\uDF80-\uDF85\uDF87-\uDFB0\uDFB2-\uDFBA]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDD00-\uDD23\uDE80-\uDEA9\uDEB0\uDEB1\uDF00-\uDF1C\uDF27\uDF30-\uDF45\uDF70-\uDF81\uDFB0-\uDFC4\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC71\uDC72\uDC75\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE80-\uDEAA\uDEB8\uDF00-\uDF1A\uDF40-\uDF46]|\uD806[\uDC00-\uDC2B\uDCA0-\uDCDF\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEB0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDEE0-\uDEF2\uDFB0]|\uD808[\uDC00-\uDF99]|\uD809[\uDC80-\uDD43]|\uD80B[\uDF90-\uDFF0]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE70-\uDEBE\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDE40-\uDE7F\uDF00-\uDF4A\uDF50\uDF93-\uDF9F\uDFE0\uDFE1\uDFE3]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82B[\uDFF0-\uDFF3\uDFF5-\uDFFB\uDFFD\uDFFE]|\uD82C[\uDC00-\uDD22\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD837[\uDF00-\uDF1E]|\uD838[\uDD00-\uDD2C\uDD37-\uDD3D\uDD4E\uDE90-\uDEAD\uDEC0-\uDEEB]|\uD839[\uDFE0-\uDFE6\uDFE8-\uDFEB\uDFED\uDFEE\uDFF0-\uDFFE]|\uD83A[\uDC00-\uDCC4\uDD00-\uDD43\uDD4B]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDEDF\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF38\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A])\S*/g;let Ls=(()=>{class rt{transform(dt){if(null==dt)return null;if("string"!=typeof dt)throw Ao(rt,dt);return dt.replace(bo,Te=>Te[0].toUpperCase()+Te.substr(1).toLowerCase())}}return rt.\u0275fac=function(dt){return new(dt||rt)},rt.\u0275pipe=r.Yjl({name:"titlecase",type:rt,pure:!0}),rt})(),ps=(()=>{class rt{transform(dt){if(null==dt)return null;if("string"!=typeof dt)throw Ao(rt,dt);return dt.toUpperCase()}}return rt.\u0275fac=function(dt){return new(dt||rt)},rt.\u0275pipe=r.Yjl({name:"uppercase",type:rt,pure:!0}),rt})(),So=(()=>{class rt{constructor(dt){this.locale=dt}transform(dt,Te="mediumDate",Me,xe){if(null==dt||""===dt||dt!=dt)return null;try{return _i(dt,Te,xe||this.locale,Me)}catch(Ct){throw Ao(rt,Ct.message)}}}return rt.\u0275fac=function(dt){return new(dt||rt)(r.Y36(r.soG,16))},rt.\u0275pipe=r.Yjl({name:"date",type:rt,pure:!0}),rt})();const $r=/#/g;let to=(()=>{class rt{constructor(dt){this._localization=dt}transform(dt,Te,Me){if(null==dt)return"";if("object"!=typeof Te||null===Te)throw Ao(rt,Te);return Te[function(rt,Pt,dt,Te){let Me=`=${rt}`;if(Pt.indexOf(Me)>-1||(Me=dt.getPluralCategory(rt,Te),Pt.indexOf(Me)>-1))return Me;if(Pt.indexOf("other")>-1)return"other";throw new Error(`No plural message found for value "${rt}"`)}(dt,Object.keys(Te),this._localization,Me)].replace($r,dt.toString())}}return rt.\u0275fac=function(dt){return new(dt||rt)(r.Y36(Rt,16))},rt.\u0275pipe=r.Yjl({name:"i18nPlural",type:rt,pure:!0}),rt})(),sn=(()=>{class rt{transform(dt){return JSON.stringify(dt,null,2)}}return rt.\u0275fac=function(dt){return new(dt||rt)},rt.\u0275pipe=r.Yjl({name:"json",type:rt,pure:!1}),rt})(),$e=(()=>{class rt{constructor(dt){this.differs=dt,this.keyValues=[],this.compareFn=Lt}transform(dt,Te=Lt){if(!dt||!(dt instanceof Map)&&"object"!=typeof dt)return null;this.differ||(this.differ=this.differs.find(dt).create());const Me=this.differ.diff(dt),xe=Te!==this.compareFn;return Me&&(this.keyValues=[],Me.forEachItem(Ct=>{this.keyValues.push(function(rt,Pt){return{key:rt,value:Pt}}(Ct.key,Ct.currentValue))})),(Me||xe)&&(this.keyValues.sort(Te),this.compareFn=Te),this.keyValues}}return rt.\u0275fac=function(dt){return new(dt||rt)(r.Y36(r.aQg,16))},rt.\u0275pipe=r.Yjl({name:"keyvalue",type:rt,pure:!1}),rt})();function Lt(rt,Pt){const dt=rt.key,Te=Pt.key;if(dt===Te)return 0;if(void 0===dt)return 1;if(void 0===Te)return-1;if(null===dt)return 1;if(null===Te)return-1;if("string"==typeof dt&&"string"==typeof Te)return dt{class rt{constructor(dt){this._locale=dt}transform(dt,Te,Me){if(!xi(dt))return null;Me=Me||this._locale;try{return function(rt,Pt,dt){return lr(rt,uo(Ur(Pt,Ve.Decimal),Zn(Pt,He.MinusSign)),Pt,He.Group,He.Decimal,dt)}(ts(dt),Me,Te)}catch(xe){throw Ao(rt,xe.message)}}}return rt.\u0275fac=function(dt){return new(dt||rt)(r.Y36(r.soG,16))},rt.\u0275pipe=r.Yjl({name:"number",type:rt,pure:!0}),rt})(),ti=(()=>{class rt{constructor(dt){this._locale=dt}transform(dt,Te,Me){if(!xi(dt))return null;Me=Me||this._locale;try{return function(rt,Pt,dt){return lr(rt,uo(Ur(Pt,Ve.Percent),Zn(Pt,He.MinusSign)),Pt,He.Group,He.Decimal,dt,!0).replace(new RegExp("%","g"),Zn(Pt,He.PercentSign))}(ts(dt),Me,Te)}catch(xe){throw Ao(rt,xe.message)}}}return rt.\u0275fac=function(dt){return new(dt||rt)(r.Y36(r.soG,16))},rt.\u0275pipe=r.Yjl({name:"percent",type:rt,pure:!0}),rt})();function xi(rt){return!(null==rt||""===rt||rt!=rt)}function ts(rt){if("string"==typeof rt&&!isNaN(Number(rt)-parseFloat(rt)))return Number(rt);if("number"!=typeof rt)throw new Error(`${rt} is not a number`);return rt}let Eo=(()=>{class rt{}return rt.\u0275fac=function(dt){return new(dt||rt)},rt.\u0275mod=r.oAB({type:rt}),rt.\u0275inj=r.cJS({providers:[{provide:Rt,useClass:mt}]}),rt})();const ba="browser";function bc(rt){return rt===ba}let Fs=(()=>{class rt{}return rt.\u0275prov=(0,r.Yz7)({token:rt,providedIn:"root",factory:()=>new dc((0,r.LFG)(y),window)}),rt})();class dc{constructor(Pt,dt){this.document=Pt,this.window=dt,this.offset=()=>[0,0]}setOffset(Pt){this.offset=Array.isArray(Pt)?()=>Pt:Pt}getScrollPosition(){return this.supportsScrolling()?[this.window.pageXOffset,this.window.pageYOffset]:[0,0]}scrollToPosition(Pt){this.supportsScrolling()&&this.window.scrollTo(Pt[0],Pt[1])}scrollToAnchor(Pt){if(!this.supportsScrolling())return;const dt=function(rt,Pt){const dt=rt.getElementById(Pt)||rt.getElementsByName(Pt)[0];if(dt)return dt;if("function"==typeof rt.createTreeWalker&&rt.body&&(rt.body.createShadowRoot||rt.body.attachShadow)){const Te=rt.createTreeWalker(rt.body,NodeFilter.SHOW_ELEMENT);let Me=Te.currentNode;for(;Me;){const xe=Me.shadowRoot;if(xe){const Ct=xe.getElementById(Pt)||xe.querySelector(`[name="${Pt}"]`);if(Ct)return Ct}Me=Te.nextNode()}}return null}(this.document,Pt);dt&&(this.scrollToElement(dt),this.attemptFocus(dt))}setHistoryScrollRestoration(Pt){if(this.supportScrollRestoration()){const dt=this.window.history;dt&&dt.scrollRestoration&&(dt.scrollRestoration=Pt)}}scrollToElement(Pt){const dt=Pt.getBoundingClientRect(),Te=dt.left+this.window.pageXOffset,Me=dt.top+this.window.pageYOffset,xe=this.offset();this.window.scrollTo(Te-xe[0],Me-xe[1])}attemptFocus(Pt){return Pt.focus(),this.document.activeElement===Pt}supportScrollRestoration(){try{if(!this.supportsScrolling())return!1;const Pt=iu(this.window.history)||iu(Object.getPrototypeOf(this.window.history));return!(!Pt||!Pt.writable&&!Pt.set)}catch(Pt){return!1}}supportsScrolling(){try{return!!this.window&&!!this.window.scrollTo&&"pageXOffset"in this.window}catch(Pt){return!1}}}function iu(rt){return Object.getOwnPropertyDescriptor(rt,"scrollRestoration")}class Zl{}},58497:(v,T,i)=>{"use strict";i.d(T,{TP:()=>it,eN:()=>He,JF:()=>fi,UA:()=>Pe,LE:()=>ae});var r=i(12057),u=i(74788),p=i(25917),d=i(18891),e=i(94612),_=i(45435),y=i(88002);class S{}class A{}class N{constructor(Cn){this.normalizedNames=new Map,this.lazyUpdate=null,Cn?this.lazyInit="string"==typeof Cn?()=>{this.headers=new Map,Cn.split("\n").forEach(Wt=>{const zn=Wt.indexOf(":");if(zn>0){const rr=Wt.slice(0,zn),Fr=rr.toLowerCase(),Gn=Wt.slice(zn+1).trim();this.maybeSetNormalizedName(rr,Fr),this.headers.has(Fr)?this.headers.get(Fr).push(Gn):this.headers.set(Fr,[Gn])}})}:()=>{this.headers=new Map,Object.keys(Cn).forEach(Wt=>{let zn=Cn[Wt];const rr=Wt.toLowerCase();"string"==typeof zn&&(zn=[zn]),zn.length>0&&(this.headers.set(rr,zn),this.maybeSetNormalizedName(Wt,rr))})}:this.headers=new Map}has(Cn){return this.init(),this.headers.has(Cn.toLowerCase())}get(Cn){this.init();const Wt=this.headers.get(Cn.toLowerCase());return Wt&&Wt.length>0?Wt[0]:null}keys(){return this.init(),Array.from(this.normalizedNames.values())}getAll(Cn){return this.init(),this.headers.get(Cn.toLowerCase())||null}append(Cn,Wt){return this.clone({name:Cn,value:Wt,op:"a"})}set(Cn,Wt){return this.clone({name:Cn,value:Wt,op:"s"})}delete(Cn,Wt){return this.clone({name:Cn,value:Wt,op:"d"})}maybeSetNormalizedName(Cn,Wt){this.normalizedNames.has(Wt)||this.normalizedNames.set(Wt,Cn)}init(){this.lazyInit&&(this.lazyInit instanceof N?this.copyFrom(this.lazyInit):this.lazyInit(),this.lazyInit=null,this.lazyUpdate&&(this.lazyUpdate.forEach(Cn=>this.applyUpdate(Cn)),this.lazyUpdate=null))}copyFrom(Cn){Cn.init(),Array.from(Cn.headers.keys()).forEach(Wt=>{this.headers.set(Wt,Cn.headers.get(Wt)),this.normalizedNames.set(Wt,Cn.normalizedNames.get(Wt))})}clone(Cn){const Wt=new N;return Wt.lazyInit=this.lazyInit&&this.lazyInit instanceof N?this.lazyInit:this,Wt.lazyUpdate=(this.lazyUpdate||[]).concat([Cn]),Wt}applyUpdate(Cn){const Wt=Cn.name.toLowerCase();switch(Cn.op){case"a":case"s":let zn=Cn.value;if("string"==typeof zn&&(zn=[zn]),0===zn.length)return;this.maybeSetNormalizedName(Cn.name,Wt);const rr=("a"===Cn.op?this.headers.get(Wt):void 0)||[];rr.push(...zn),this.headers.set(Wt,rr);break;case"d":const Fr=Cn.value;if(Fr){let Gn=this.headers.get(Wt);if(!Gn)return;Gn=Gn.filter(Jr=>-1===Fr.indexOf(Jr)),0===Gn.length?(this.headers.delete(Wt),this.normalizedNames.delete(Wt)):this.headers.set(Wt,Gn)}else this.headers.delete(Wt),this.normalizedNames.delete(Wt)}}forEach(Cn){this.init(),Array.from(this.normalizedNames.keys()).forEach(Wt=>Cn(this.normalizedNames.get(Wt),this.headers.get(Wt)))}}class L{encodeKey(Cn){return ee(Cn)}encodeValue(Cn){return ee(Cn)}decodeKey(Cn){return decodeURIComponent(Cn)}decodeValue(Cn){return decodeURIComponent(Cn)}}const J=/%(\d[a-f0-9])/gi,K={40:"@","3A":":",24:"$","2C":",","3B":";","2B":"+","3D":"=","3F":"?","2F":"/"};function ee(Zr){return encodeURIComponent(Zr).replace(J,(Cn,Wt)=>{var zn;return null!==(zn=K[Wt])&&void 0!==zn?zn:Cn})}function ue(Zr){return`${Zr}`}class ae{constructor(Cn={}){if(this.updates=null,this.cloneFrom=null,this.encoder=Cn.encoder||new L,Cn.fromString){if(Cn.fromObject)throw new Error("Cannot specify both fromString and fromObject.");this.map=function(Zr,Cn){const Wt=new Map;return Zr.length>0&&Zr.replace(/^\?/,"").split("&").forEach(rr=>{const Fr=rr.indexOf("="),[Gn,Jr]=-1==Fr?[Cn.decodeKey(rr),""]:[Cn.decodeKey(rr.slice(0,Fr)),Cn.decodeValue(rr.slice(Fr+1))],_i=Wt.get(Gn)||[];_i.push(Jr),Wt.set(Gn,_i)}),Wt}(Cn.fromString,this.encoder)}else Cn.fromObject?(this.map=new Map,Object.keys(Cn.fromObject).forEach(Wt=>{const zn=Cn.fromObject[Wt];this.map.set(Wt,Array.isArray(zn)?zn:[zn])})):this.map=null}has(Cn){return this.init(),this.map.has(Cn)}get(Cn){this.init();const Wt=this.map.get(Cn);return Wt?Wt[0]:null}getAll(Cn){return this.init(),this.map.get(Cn)||null}keys(){return this.init(),Array.from(this.map.keys())}append(Cn,Wt){return this.clone({param:Cn,value:Wt,op:"a"})}appendAll(Cn){const Wt=[];return Object.keys(Cn).forEach(zn=>{const rr=Cn[zn];Array.isArray(rr)?rr.forEach(Fr=>{Wt.push({param:zn,value:Fr,op:"a"})}):Wt.push({param:zn,value:rr,op:"a"})}),this.clone(Wt)}set(Cn,Wt){return this.clone({param:Cn,value:Wt,op:"s"})}delete(Cn,Wt){return this.clone({param:Cn,value:Wt,op:"d"})}toString(){return this.init(),this.keys().map(Cn=>{const Wt=this.encoder.encodeKey(Cn);return this.map.get(Cn).map(zn=>Wt+"="+this.encoder.encodeValue(zn)).join("&")}).filter(Cn=>""!==Cn).join("&")}clone(Cn){const Wt=new ae({encoder:this.encoder});return Wt.cloneFrom=this.cloneFrom||this,Wt.updates=(this.updates||[]).concat(Cn),Wt}init(){null===this.map&&(this.map=new Map),null!==this.cloneFrom&&(this.cloneFrom.init(),this.cloneFrom.keys().forEach(Cn=>this.map.set(Cn,this.cloneFrom.map.get(Cn))),this.updates.forEach(Cn=>{switch(Cn.op){case"a":case"s":const Wt=("a"===Cn.op?this.map.get(Cn.param):void 0)||[];Wt.push(ue(Cn.value)),this.map.set(Cn.param,Wt);break;case"d":if(void 0===Cn.value){this.map.delete(Cn.param);break}{let zn=this.map.get(Cn.param)||[];const rr=zn.indexOf(ue(Cn.value));-1!==rr&&zn.splice(rr,1),zn.length>0?this.map.set(Cn.param,zn):this.map.delete(Cn.param)}}}),this.cloneFrom=this.updates=null)}}class se{constructor(){this.map=new Map}set(Cn,Wt){return this.map.set(Cn,Wt),this}get(Cn){return this.map.has(Cn)||this.map.set(Cn,Cn.defaultValue()),this.map.get(Cn)}delete(Cn){return this.map.delete(Cn),this}keys(){return this.map.keys()}}function ie(Zr){return"undefined"!=typeof ArrayBuffer&&Zr instanceof ArrayBuffer}function he(Zr){return"undefined"!=typeof Blob&&Zr instanceof Blob}function ge(Zr){return"undefined"!=typeof FormData&&Zr instanceof FormData}class ce{constructor(Cn,Wt,zn,rr){let Fr;if(this.url=Wt,this.body=null,this.reportProgress=!1,this.withCredentials=!1,this.responseType="json",this.method=Cn.toUpperCase(),function(Zr){switch(Zr){case"DELETE":case"GET":case"HEAD":case"OPTIONS":case"JSONP":return!1;default:return!0}}(this.method)||rr?(this.body=void 0!==zn?zn:null,Fr=rr):Fr=zn,Fr&&(this.reportProgress=!!Fr.reportProgress,this.withCredentials=!!Fr.withCredentials,Fr.responseType&&(this.responseType=Fr.responseType),Fr.headers&&(this.headers=Fr.headers),Fr.context&&(this.context=Fr.context),Fr.params&&(this.params=Fr.params)),this.headers||(this.headers=new N),this.context||(this.context=new se),this.params){const Gn=this.params.toString();if(0===Gn.length)this.urlWithParams=Wt;else{const Jr=Wt.indexOf("?");this.urlWithParams=Wt+(-1===Jr?"?":Jrgn.set(yn,Cn.setHeaders[yn]),wi)),Cn.setParams&&(br=Object.keys(Cn.setParams).reduce((gn,yn)=>gn.set(yn,Cn.setParams[yn]),br)),new ce(zn,rr,Gn,{params:br,headers:wi,context:Dr,reportProgress:_i,responseType:Fr,withCredentials:Jr})}}var lt=(()=>((lt=lt||{})[lt.Sent=0]="Sent",lt[lt.UploadProgress=1]="UploadProgress",lt[lt.ResponseHeader=2]="ResponseHeader",lt[lt.DownloadProgress=3]="DownloadProgress",lt[lt.Response=4]="Response",lt[lt.User=5]="User",lt))();class Ve{constructor(Cn,Wt=200,zn="OK"){this.headers=Cn.headers||new N,this.status=void 0!==Cn.status?Cn.status:Wt,this.statusText=Cn.statusText||zn,this.url=Cn.url||null,this.ok=this.status>=200&&this.status<300}}class ze extends Ve{constructor(Cn={}){super(Cn),this.type=lt.ResponseHeader}clone(Cn={}){return new ze({headers:Cn.headers||this.headers,status:void 0!==Cn.status?Cn.status:this.status,statusText:Cn.statusText||this.statusText,url:Cn.url||this.url||void 0})}}class Be extends Ve{constructor(Cn={}){super(Cn),this.type=lt.Response,this.body=void 0!==Cn.body?Cn.body:null}clone(Cn={}){return new Be({body:void 0!==Cn.body?Cn.body:this.body,headers:Cn.headers||this.headers,status:void 0!==Cn.status?Cn.status:this.status,statusText:Cn.statusText||this.statusText,url:Cn.url||this.url||void 0})}}class Pe extends Ve{constructor(Cn){super(Cn,0,"Unknown Error"),this.name="HttpErrorResponse",this.ok=!1,this.message=this.status>=200&&this.status<300?`Http failure during parsing for ${Cn.url||"(unknown url)"}`:`Http failure response for ${Cn.url||"(unknown url)"}: ${Cn.status} ${Cn.statusText}`,this.error=Cn.error||null}}function je(Zr,Cn){return{body:Cn,headers:Zr.headers,context:Zr.context,observe:Zr.observe,params:Zr.params,reportProgress:Zr.reportProgress,responseType:Zr.responseType,withCredentials:Zr.withCredentials}}let He=(()=>{class Zr{constructor(Wt){this.handler=Wt}request(Wt,zn,rr={}){let Fr;if(Wt instanceof ce)Fr=Wt;else{let _i,wi;_i=rr.headers instanceof N?rr.headers:new N(rr.headers),rr.params&&(wi=rr.params instanceof ae?rr.params:new ae({fromObject:rr.params})),Fr=new ce(Wt,zn,void 0!==rr.body?rr.body:null,{headers:_i,context:rr.context,params:wi,reportProgress:rr.reportProgress,responseType:rr.responseType||"json",withCredentials:rr.withCredentials})}const Gn=(0,p.of)(Fr).pipe((0,e.b)(_i=>this.handler.handle(_i)));if(Wt instanceof ce||"events"===rr.observe)return Gn;const Jr=Gn.pipe((0,_.h)(_i=>_i instanceof Be));switch(rr.observe||"body"){case"body":switch(Fr.responseType){case"arraybuffer":return Jr.pipe((0,y.U)(_i=>{if(null!==_i.body&&!(_i.body instanceof ArrayBuffer))throw new Error("Response is not an ArrayBuffer.");return _i.body}));case"blob":return Jr.pipe((0,y.U)(_i=>{if(null!==_i.body&&!(_i.body instanceof Blob))throw new Error("Response is not a Blob.");return _i.body}));case"text":return Jr.pipe((0,y.U)(_i=>{if(null!==_i.body&&"string"!=typeof _i.body)throw new Error("Response is not a string.");return _i.body}));case"json":default:return Jr.pipe((0,y.U)(_i=>_i.body))}case"response":return Jr;default:throw new Error(`Unreachable: unhandled observe type ${rr.observe}}`)}}delete(Wt,zn={}){return this.request("DELETE",Wt,zn)}get(Wt,zn={}){return this.request("GET",Wt,zn)}head(Wt,zn={}){return this.request("HEAD",Wt,zn)}jsonp(Wt,zn){return this.request("JSONP",Wt,{params:(new ae).append(zn,"JSONP_CALLBACK"),observe:"body",responseType:"json"})}options(Wt,zn={}){return this.request("OPTIONS",Wt,zn)}patch(Wt,zn,rr={}){return this.request("PATCH",Wt,je(rr,zn))}post(Wt,zn,rr={}){return this.request("POST",Wt,je(rr,zn))}put(Wt,zn,rr={}){return this.request("PUT",Wt,je(rr,zn))}}return Zr.\u0275fac=function(Wt){return new(Wt||Zr)(u.LFG(S))},Zr.\u0275prov=u.Yz7({token:Zr,factory:Zr.\u0275fac}),Zr})();class Vt{constructor(Cn,Wt){this.next=Cn,this.interceptor=Wt}handle(Cn){return this.interceptor.intercept(Cn,this.next)}}const it=new u.OlP("HTTP_INTERCEPTORS");let tn=(()=>{class Zr{intercept(Wt,zn){return zn.handle(Wt)}}return Zr.\u0275fac=function(Wt){return new(Wt||Zr)},Zr.\u0275prov=u.Yz7({token:Zr,factory:Zr.\u0275fac}),Zr})();const Xt=/^\)\]\}',?\n/;let Ur=(()=>{class Zr{constructor(Wt){this.xhrFactory=Wt}handle(Wt){if("JSONP"===Wt.method)throw new Error("Attempted to construct Jsonp request without HttpClientJsonpModule installed.");return new d.y(zn=>{const rr=this.xhrFactory.build();if(rr.open(Wt.method,Wt.urlWithParams),Wt.withCredentials&&(rr.withCredentials=!0),Wt.headers.forEach((yn,gr)=>rr.setRequestHeader(yn,gr.join(","))),Wt.headers.has("Accept")||rr.setRequestHeader("Accept","application/json, text/plain, */*"),!Wt.headers.has("Content-Type")){const yn=Wt.detectContentTypeHeader();null!==yn&&rr.setRequestHeader("Content-Type",yn)}if(Wt.responseType){const yn=Wt.responseType.toLowerCase();rr.responseType="json"!==yn?yn:"text"}const Fr=Wt.serializeBody();let Gn=null;const Jr=()=>{if(null!==Gn)return Gn;const yn=1223===rr.status?204:rr.status,gr=rr.statusText||"OK",Jt=new N(rr.getAllResponseHeaders()),Vn=function(Zr){return"responseURL"in Zr&&Zr.responseURL?Zr.responseURL:/^X-Request-URL:/m.test(Zr.getAllResponseHeaders())?Zr.getResponseHeader("X-Request-URL"):null}(rr)||Wt.url;return Gn=new ze({headers:Jt,status:yn,statusText:gr,url:Vn}),Gn},_i=()=>{let{headers:yn,status:gr,statusText:Jt,url:Vn}=Jr(),mr=null;204!==gr&&(mr=void 0===rr.response?rr.responseText:rr.response),0===gr&&(gr=mr?200:0);let Dn=gr>=200&&gr<300;if("json"===Wt.responseType&&"string"==typeof mr){const Pr=mr;mr=mr.replace(Xt,"");try{mr=""!==mr?JSON.parse(mr):null}catch(Yt){mr=Pr,Dn&&(Dn=!1,mr={error:Yt,text:mr})}}Dn?(zn.next(new Be({body:mr,headers:yn,status:gr,statusText:Jt,url:Vn||void 0})),zn.complete()):zn.error(new Pe({error:mr,headers:yn,status:gr,statusText:Jt,url:Vn||void 0}))},wi=yn=>{const{url:gr}=Jr(),Jt=new Pe({error:yn,status:rr.status||0,statusText:rr.statusText||"Unknown Error",url:gr||void 0});zn.error(Jt)};let br=!1;const Dr=yn=>{br||(zn.next(Jr()),br=!0);let gr={type:lt.DownloadProgress,loaded:yn.loaded};yn.lengthComputable&&(gr.total=yn.total),"text"===Wt.responseType&&!!rr.responseText&&(gr.partialText=rr.responseText),zn.next(gr)},gn=yn=>{let gr={type:lt.UploadProgress,loaded:yn.loaded};yn.lengthComputable&&(gr.total=yn.total),zn.next(gr)};return rr.addEventListener("load",_i),rr.addEventListener("error",wi),rr.addEventListener("timeout",wi),rr.addEventListener("abort",wi),Wt.reportProgress&&(rr.addEventListener("progress",Dr),null!==Fr&&rr.upload&&rr.upload.addEventListener("progress",gn)),rr.send(Fr),zn.next({type:lt.Sent}),()=>{rr.removeEventListener("error",wi),rr.removeEventListener("abort",wi),rr.removeEventListener("load",_i),rr.removeEventListener("timeout",wi),Wt.reportProgress&&(rr.removeEventListener("progress",Dr),null!==Fr&&rr.upload&&rr.upload.removeEventListener("progress",gn)),rr.readyState!==rr.DONE&&rr.abort()}})}}return Zr.\u0275fac=function(Wt){return new(Wt||Zr)(u.LFG(r.JF))},Zr.\u0275prov=u.Yz7({token:Zr,factory:Zr.\u0275fac}),Zr})();const di=new u.OlP("XSRF_COOKIE_NAME"),Lr=new u.OlP("XSRF_HEADER_NAME");class Mr{}let Kr=(()=>{class Zr{constructor(Wt,zn,rr){this.doc=Wt,this.platform=zn,this.cookieName=rr,this.lastCookieString="",this.lastToken=null,this.parseCount=0}getToken(){if("server"===this.platform)return null;const Wt=this.doc.cookie||"";return Wt!==this.lastCookieString&&(this.parseCount++,this.lastToken=(0,r.Mx)(Wt,this.cookieName),this.lastCookieString=Wt),this.lastToken}}return Zr.\u0275fac=function(Wt){return new(Wt||Zr)(u.LFG(r.K0),u.LFG(u.Lbi),u.LFG(di))},Zr.\u0275prov=u.Yz7({token:Zr,factory:Zr.\u0275fac}),Zr})(),ei=(()=>{class Zr{constructor(Wt,zn){this.tokenService=Wt,this.headerName=zn}intercept(Wt,zn){const rr=Wt.url.toLowerCase();if("GET"===Wt.method||"HEAD"===Wt.method||rr.startsWith("http://")||rr.startsWith("https://"))return zn.handle(Wt);const Fr=this.tokenService.getToken();return null!==Fr&&!Wt.headers.has(this.headerName)&&(Wt=Wt.clone({headers:Wt.headers.set(this.headerName,Fr)})),zn.handle(Wt)}}return Zr.\u0275fac=function(Wt){return new(Wt||Zr)(u.LFG(Mr),u.LFG(Lr))},Zr.\u0275prov=u.Yz7({token:Zr,factory:Zr.\u0275fac}),Zr})(),Nn=(()=>{class Zr{constructor(Wt,zn){this.backend=Wt,this.injector=zn,this.chain=null}handle(Wt){if(null===this.chain){const zn=this.injector.get(it,[]);this.chain=zn.reduceRight((rr,Fr)=>new Vt(rr,Fr),this.backend)}return this.chain.handle(Wt)}}return Zr.\u0275fac=function(Wt){return new(Wt||Zr)(u.LFG(A),u.LFG(u.zs3))},Zr.\u0275prov=u.Yz7({token:Zr,factory:Zr.\u0275fac}),Zr})(),Yr=(()=>{class Zr{static disable(){return{ngModule:Zr,providers:[{provide:ei,useClass:tn}]}}static withOptions(Wt={}){return{ngModule:Zr,providers:[Wt.cookieName?{provide:di,useValue:Wt.cookieName}:[],Wt.headerName?{provide:Lr,useValue:Wt.headerName}:[]]}}}return Zr.\u0275fac=function(Wt){return new(Wt||Zr)},Zr.\u0275mod=u.oAB({type:Zr}),Zr.\u0275inj=u.cJS({providers:[ei,{provide:it,useExisting:ei,multi:!0},{provide:Mr,useClass:Kr},{provide:di,useValue:"XSRF-TOKEN"},{provide:Lr,useValue:"X-XSRF-TOKEN"}]}),Zr})(),fi=(()=>{class Zr{}return Zr.\u0275fac=function(Wt){return new(Wt||Zr)},Zr.\u0275mod=u.oAB({type:Zr}),Zr.\u0275inj=u.cJS({providers:[He,{provide:S,useClass:Nn},Ur,{provide:A,useExisting:Ur}],imports:[[Yr.withOptions({cookieName:"XSRF-TOKEN",headerName:"X-XSRF-TOKEN"})]]}),Zr})()},74788:(v,T,i)=>{"use strict";i.d(T,{deG:()=>vd,tb:()=>Pp,AFp:()=>N2,ip1:()=>yT,CZH:()=>Zv,hGG:()=>$T,z2F:()=>r0,sBO:()=>Kw,Sil:()=>n0,_Vd:()=>Pv,UuU:()=>nM,EJc:()=>F2,SBq:()=>og,qLn:()=>Gf,vpe:()=>Wp,gxx:()=>Jg,tBr:()=>ha,XFs:()=>Yr,OlP:()=>Jl,zs3:()=>Ac,IIB:()=>w2,ZZ4:()=>Ay,aQg:()=>Oy,soG:()=>Gy,YKP:()=>DE,v3s:()=>UD,h0i:()=>zm,PXZ:()=>FD,R0b:()=>Eh,FiY:()=>ia,r_U:()=>gD,Lbi:()=>cm,g9A:()=>D_,Qsj:()=>kw,FYo:()=>Cy,JOm:()=>Bd,Tiy:()=>xE,q3G:()=>Vs,tp0:()=>il,EAV:()=>ZD,Rgc:()=>F1,dDg:()=>AT,DyG:()=>Hf,GfV:()=>aM,i9L:()=>fp,s_b:()=>Dy,ifc:()=>Gn,eFA:()=>DT,G48:()=>RD,Gpc:()=>L,X6Q:()=>xT,_c5:()=>JD,VLi:()=>DD,c2e:()=>L2,zSh:()=>Im,wAp:()=>Ua,vHH:()=>ee,EiD:()=>Xo,mCW:()=>vf,qzn:()=>Yf,JVY:()=>bd,pB0:()=>Nh,eBb:()=>th,L6k:()=>Rh,LAX:()=>nh,cg1:()=>fE,Tjo:()=>WD,kL8:()=>wC,yhl:()=>eh,dqk:()=>Dr,sIi:()=>Hm,CqO:()=>qb,QGY:()=>Zm,F4k:()=>kS,RDi:()=>tl,AaK:()=>S,z3N:()=>kd,qOj:()=>T1,TTD:()=>Eo,_Bn:()=>Pw,xp6:()=>Fa,uIk:()=>A1,Tol:()=>rC,Gre:()=>gC,MT6:()=>mC,DjV:()=>vC,ekj:()=>oE,Suo:()=>a2,Xpm:()=>mi,lG2:()=>lo,Yz7:()=>xt,cJS:()=>Zn,oAB:()=>Fn,Yjl:()=>vo,Y36:()=>D1,_UZ:()=>LS,GkF:()=>FS,BQk:()=>Kb,ynx:()=>Xb,qZA:()=>Qb,TgZ:()=>Jb,EpF:()=>fy,n5z:()=>$f,Ikx:()=>cE,SDv:()=>QC,QtT:()=>KC,DtL:()=>XC,N_p:()=>EE,pQV:()=>TE,Zx4:()=>qC,tHW:()=>Ty,LFG:()=>xn,$8M:()=>Sl,NdJ:()=>eE,CRH:()=>l2,oxw:()=>BS,ALo:()=>e2,lcZ:()=>t2,xi3:()=>bh,Dn7:()=>n2,Hsn:()=>ZS,F$t:()=>YS,Q6J:()=>zb,s9C:()=>nE,MGl:()=>py,hYB:()=>xg,DdM:()=>WE,VKq:()=>JE,WLB:()=>z1,kEZ:()=>WM,l5B:()=>e_,qbA:()=>JM,iGM:()=>o2,MAs:()=>Ke,pYS:()=>Vf,Jf7:()=>Uh,CHM:()=>Aa,oJD:()=>_c,uOi:()=>Hd,LSH:()=>wu,kYT:()=>ar,Udp:()=>iE,d8E:()=>dE,YNc:()=>$,W1O:()=>of,_uU:()=>uC,Oqu:()=>aE,hij:()=>gy,AsE:()=>lE,lnq:()=>uE,Gf:()=>s2});var r=i(79765),u=i(75319),p=i(18891),d=i(66682),e=i(18819);function _(a){for(let l in a)if(a[l]===_)return l;throw Error("Could not find renamed property on target object.")}function y(a,l){for(const f in l)l.hasOwnProperty(f)&&!a.hasOwnProperty(f)&&(a[f]=l[f])}function S(a){if("string"==typeof a)return a;if(Array.isArray(a))return"["+a.map(S).join(", ")+"]";if(null==a)return""+a;if(a.overriddenName)return`${a.overriddenName}`;if(a.name)return`${a.name}`;const l=a.toString();if(null==l)return""+l;const f=l.indexOf("\n");return-1===f?l:l.substring(0,f)}function A(a,l){return null==a||""===a?null===l?"":l:null==l||""===l?a:a+" "+l}const N=_({__forward_ref__:_});function L(a){return a.__forward_ref__=L,a.toString=function(){return S(this())},a}function Z(a){return J(a)?a():a}function J(a){return"function"==typeof a&&a.hasOwnProperty(N)&&a.__forward_ref__===L}class ee extends Error{constructor(l,f){super(function(a,l){return`${a?`NG0${a}: `:""}${l}`}(l,f)),this.code=l}}function H(a){return"string"==typeof a?a:null==a?"":String(a)}function se(a){return"function"==typeof a?a.name||a.toString():"object"==typeof a&&null!=a&&"function"==typeof a.type?a.type.name||a.type.toString():H(a)}function ge(a,l){const f=l?` in ${l}`:"";throw new ee("201",`No provider for ${se(a)} found${f}`)}function Ut(a,l,f,m){throw new Error(`ASSERTION ERROR: ${a}`+(null==m?"":` [Expected=> ${f} ${m} ${l} <=Actual]`))}function xt(a){return{token:a.token,providedIn:a.providedIn||null,factory:a.factory,value:void 0}}function Zn(a){return{providers:a.providers||[],imports:a.imports||[]}}function Ur(a){return di(a,ei)||di(a,$n)}function di(a,l){return a.hasOwnProperty(l)?a[l]:null}function Kr(a){return a&&(a.hasOwnProperty(Nn)||a.hasOwnProperty(Br))?a[Nn]:null}const ei=_({\u0275prov:_}),Nn=_({\u0275inj:_}),$n=_({ngInjectableDef:_}),Br=_({ngInjectorDef:_});var Yr=(()=>((Yr=Yr||{})[Yr.Default=0]="Default",Yr[Yr.Host=1]="Host",Yr[Yr.Self=2]="Self",Yr[Yr.SkipSelf=4]="SkipSelf",Yr[Yr.Optional=8]="Optional",Yr))();let fi;function Hi(a){const l=fi;return fi=a,l}function Zr(a,l,f){const m=Ur(a);return m&&"root"==m.providedIn?void 0===m.value?m.value=m.factory():m.value:f&Yr.Optional?null:void 0!==l?l:void ge(S(a),"Injector")}function Wt(a){return{toString:a}.toString()}var zn=(()=>((zn=zn||{})[zn.OnPush=0]="OnPush",zn[zn.Default=1]="Default",zn))(),Gn=(()=>((Gn=Gn||{})[Gn.Emulated=0]="Emulated",Gn[Gn.None=2]="None",Gn[Gn.ShadowDom=3]="ShadowDom",Gn))();const Jr="undefined"!=typeof globalThis&&globalThis,_i="undefined"!=typeof window&&window,wi="undefined"!=typeof self&&"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope&&self,Dr=Jr||"undefined"!=typeof global&&global||_i||wi,gr={},Jt=[],Vn=_({\u0275cmp:_}),mr=_({\u0275dir:_}),Dn=_({\u0275pipe:_}),Pr=_({\u0275mod:_}),Yt=_({\u0275loc:_}),_n=_({\u0275fac:_}),Ge=_({__NG_ELEMENT_ID__:_});let kr=0;function mi(a){return Wt(()=>{const f={},m={type:a.type,providersResolver:null,decls:a.decls,vars:a.vars,factory:null,template:a.template||null,consts:a.consts||null,ngContentSelectors:a.ngContentSelectors,hostBindings:a.hostBindings||null,hostVars:a.hostVars||0,hostAttrs:a.hostAttrs||null,contentQueries:a.contentQueries||null,declaredInputs:f,inputs:null,outputs:null,exportAs:a.exportAs||null,onPush:a.changeDetection===zn.OnPush,directiveDefs:null,pipeDefs:null,selectors:a.selectors||Jt,viewQuery:a.viewQuery||null,features:a.features||null,data:a.data||{},encapsulation:a.encapsulation||Gn.Emulated,id:"c",styles:a.styles||Jt,_:null,setInput:null,schemas:a.schemas||null,tView:null},M=a.directives,k=a.features,te=a.pipes;return m.id+=kr++,m.inputs=Wi(a.inputs,f),m.outputs=Wi(a.outputs),k&&k.forEach(le=>le(m)),m.directiveDefs=M?()=>("function"==typeof M?M():M).map(Er):null,m.pipeDefs=te?()=>("function"==typeof te?te():te).map(Wr):null,m})}function Er(a){return Co(a)||function(a){return a[mr]||null}(a)}function Wr(a){return function(a){return a[Dn]||null}(a)}const dr={};function Fn(a){return Wt(()=>{const l={type:a.type,bootstrap:a.bootstrap||Jt,declarations:a.declarations||Jt,imports:a.imports||Jt,exports:a.exports||Jt,transitiveCompileScopes:null,schemas:a.schemas||null,id:a.id||null};return null!=a.id&&(dr[a.id]=a.type),l})}function ar(a,l){return Wt(()=>{const f=jo(a,!0);f.declarations=l.declarations||Jt,f.imports=l.imports||Jt,f.exports=l.exports||Jt})}function Wi(a,l){if(null==a)return gr;const f={};for(const m in a)if(a.hasOwnProperty(m)){let M=a[m],k=M;Array.isArray(M)&&(k=M[1],M=M[0]),f[M]=m,l&&(l[M]=k)}return f}const lo=mi;function vo(a){return{type:a.type,name:a.name,factory:null,pure:!1!==a.pure,onDestroy:a.type.prototype.ngOnDestroy||null}}function Co(a){return a[Vn]||null}function jo(a,l){const f=a[Pr]||null;if(!f&&!0===l)throw new Error(`Type ${S(a)} does not have '\u0275mod' property.`);return f}function ca(a){return Array.isArray(a)&&"object"==typeof a[1]}function $s(a){return Array.isArray(a)&&!0===a[1]}function da(a){return 0!=(8&a.flags)}function Il(a){return 2==(2&a.flags)}function fo(a){return 1==(1&a.flags)}function Ya(a){return null!==a.template}function Ao(a){return 0!=(512&a[2])}function wo(a,l){return a.hasOwnProperty(_n)?a[_n]:null}class ko{constructor(l,f,m){this.previousValue=l,this.currentValue=f,this.firstChange=m}isFirstChange(){return this.firstChange}}function Eo(){return ba}function ba(a){return a.type.prototype.ngOnChanges&&(a.setInput=tc),sl}function sl(){const a=bc(this),l=null==a?void 0:a.current;if(l){const f=a.previous;if(f===gr)a.previous=l;else for(let m in l)f[m]=l[m];a.current=null,this.ngOnChanges(l)}}function tc(a,l,f,m){const M=bc(a)||function(a,l){return a[Nu]=l}(a,{previous:gr,current:null}),k=M.current||(M.current={}),te=M.previous,le=this.declaredInputs[f],Ne=te[le];k[le]=new ko(Ne&&Ne.currentValue,l,te===gr),a[m]=l}Eo.ngInherit=!0;const Nu="__ngSimpleChanges__";function bc(a){return a[Nu]||null}let iu;function tl(a){iu=a}function Su(){return void 0!==iu?iu:"undefined"!=typeof document?document:void 0}function rt(a){return!!a.listen}const dt={createRenderer:(a,l)=>Su()};function Me(a){for(;Array.isArray(a);)a=a[0];return a}function ur(a,l){return Me(l[a])}function Qi(a,l){return Me(l[a.index])}function ms(a,l){return a.data[l]}function hs(a,l){return a[l]}function Ts(a,l){const f=l[a];return ca(f)?f:f[0]}function Ma(a){return 4==(4&a[2])}function au(a){return 128==(128&a[2])}function Rl(a,l){return null==l?null:a[l]}function nc(a){a[18]=0}function Al(a,l){a[5]+=l;let f=a,m=a[3];for(;null!==m&&(1===l&&1===f[5]||-1===l&&0===f[5]);)m[5]+=l,f=m,m=m[3]}const vs={lFrame:Or(null),bindingsEnabled:!0,isInCheckNoChangesMode:!1};function kc(){return vs.bindingsEnabled}function Li(){return vs.lFrame.lView}function sa(){return vs.lFrame.tView}function Aa(a){return vs.lFrame.contextLView=a,a[8]}function Ss(){let a=$u();for(;null!==a&&64===a.type;)a=a.parent;return a}function $u(){return vs.lFrame.currentTNode}function yl(){const a=vs.lFrame,l=a.currentTNode;return a.isParent?l:l.parent}function Mu(a,l){const f=vs.lFrame;f.currentTNode=a,f.isParent=l}function yu(){return vs.lFrame.isParent}function lu(){vs.lFrame.isParent=!1}function Uu(){return vs.isInCheckNoChangesMode}function Bo(a){vs.isInCheckNoChangesMode=a}function uu(){const a=vs.lFrame;let l=a.bindingRootIndex;return-1===l&&(l=a.bindingRootIndex=a.tView.bindingStartIndex),l}function wr(){return vs.lFrame.bindingIndex}function pu(){return vs.lFrame.bindingIndex++}function aa(a){const l=vs.lFrame,f=l.bindingIndex;return l.bindingIndex=l.bindingIndex+a,f}function bl(a){vs.lFrame.inI18n=a}function El(a,l){const f=vs.lFrame;f.bindingIndex=f.bindingRootIndex=a,ks(l)}function ks(a){vs.lFrame.currentDirectiveIndex=a}function V(a){const l=vs.lFrame.currentDirectiveIndex;return-1===l?null:a[l]}function Ae(){return vs.lFrame.currentQueryIndex}function st(a){vs.lFrame.currentQueryIndex=a}function vt(a){const l=a[1];return 2===l.type?l.declTNode:1===l.type?a[6]:null}function ut(a,l,f){if(f&Yr.SkipSelf){let M=l,k=a;for(;!(M=M.parent,null!==M||f&Yr.Host||(M=vt(k),null===M||(k=k[15],10&M.type))););if(null===M)return!1;l=M,a=k}const m=vs.lFrame=mn();return m.currentTNode=l,m.lView=a,!0}function un(a){const l=mn(),f=a[1];vs.lFrame=l,l.currentTNode=f.firstChild,l.lView=a,l.tView=f,l.contextLView=a,l.bindingIndex=f.bindingStartIndex,l.inI18n=!1}function mn(){const a=vs.lFrame,l=null===a?null:a.child;return null===l?Or(a):l}function Or(a){const l={currentTNode:null,isParent:!0,lView:null,tView:null,selectedIndex:-1,contextLView:null,elementDepthCount:0,currentNamespace:null,currentDirectiveIndex:-1,bindingRootIndex:-1,bindingIndex:-1,currentQueryIndex:0,parent:a,child:null,inI18n:!1};return null!==a&&(a.child=l),l}function zr(){const a=vs.lFrame;return vs.lFrame=a.parent,a.currentTNode=null,a.lView=null,a}const gi=zr;function ai(){const a=zr();a.isParent=!0,a.tView=null,a.selectedIndex=-1,a.contextLView=null,a.elementDepthCount=0,a.currentDirectiveIndex=-1,a.currentNamespace=null,a.bindingRootIndex=-1,a.bindingIndex=-1,a.currentQueryIndex=0}function In(){return vs.lFrame.selectedIndex}function Cr(a){vs.lFrame.selectedIndex=a}function hr(){const a=vs.lFrame;return ms(a.tView,a.selectedIndex)}function $c(a,l){for(let f=l.directiveStart,m=l.directiveEnd;f=m)break}else l[Ne]<0&&(a[18]+=65536),(le>11>16&&(3&a[2])===l){a[2]+=2048;try{k.call(le)}finally{}}}else try{k.call(le)}finally{}}class Js{constructor(l,f,m){this.factory=l,this.resolving=!1,this.canSeeViewProviders=f,this.injectImpl=m}}function ic(a,l,f){const m=rt(a);let M=0;for(;Ml){te=k-1;break}}}for(;k>16}(a),m=l;for(;f>0;)m=m[15],f--;return m}let Et=!0;function qt(a){const l=Et;return Et=a,l}let Yo=0;function ns(a,l){const f=Hs(a,l);if(-1!==f)return f;const m=l[1];m.firstCreatePass&&(a.injectorIndex=l.length,Oa(m.data,a),Oa(l,null),Oa(m.blueprint,null));const M=fc(a,l),k=a.injectorIndex;if(Ye(M)){const te=Ie(M),le=ot(M,l),Ne=le[1].data;for(let qe=0;qe<8;qe++)l[k+qe]=le[te+qe]|Ne[te+qe]}return l[k+8]=M,k}function Oa(a,l){a.push(0,0,0,0,0,0,0,0,l)}function Hs(a,l){return-1===a.injectorIndex||a.parent&&a.parent.injectorIndex===a.injectorIndex||null===l[a.injectorIndex+8]?-1:a.injectorIndex}function fc(a,l){if(a.parent&&-1!==a.parent.injectorIndex)return a.parent.injectorIndex;let f=0,m=null,M=l;for(;null!==M;){const k=M[1],te=k.type;if(m=2===te?k.declTNode:1===te?M[6]:null,null===m)return-1;if(f++,M=M[15],-1!==m.injectorIndex)return m.injectorIndex|f<<16}return-1}function ga(a,l,f){!function(a,l,f){let m;"string"==typeof f?m=f.charCodeAt(0)||0:f.hasOwnProperty(Ge)&&(m=f[Ge]),null==m&&(m=f[Ge]=Yo++);const M=255&m;l.data[a+(M>>5)]|=1<=0?255&l:Of:l}(f);if("function"==typeof k){if(!ut(l,a,m))return m&Yr.Host?ll(M,f,m):Za(l,f,m,M);try{const te=k(m);if(null!=te||m&Yr.Optional)return te;ge(f)}finally{gi()}}else if("number"==typeof k){let te=null,le=Hs(a,l),Ne=-1,qe=m&Yr.Host?l[16][6]:null;for((-1===le||m&Yr.SkipSelf)&&(Ne=-1===le?fc(a,l):l[le+8],-1!==Ne&&Xd(m,!1)?(te=l[1],le=Ie(Ne),l=ot(Ne,l)):le=-1);-1!==le;){const wt=l[1];if(Cc(k,le,wt.data)){const ln=Rd(le,l,f,te,m,qe);if(ln!==ul)return ln}Ne=l[le+8],-1!==Ne&&Xd(m,l[1].data[le+8]===qe)&&Cc(k,le,l)?(te=wt,le=Ie(Ne),l=ot(Ne,l)):le=-1}}}return Za(l,f,m,M)}const ul={};function Of(){return new jc(Ss(),Li())}function Rd(a,l,f,m,M,k){const te=l[1],le=te.data[a+8],wt=Qd(le,te,f,null==m?Il(le)&&Et:m!=te&&0!=(3&le.type),M&Yr.Host&&k===le);return null!==wt?Sc(l,te,wt,le):ul}function Qd(a,l,f,m,M){const k=a.providerIndexes,te=l.data,le=1048575&k,Ne=a.directiveStart,wt=k>>20,dn=M?le+wt:a.directiveEnd;for(let nr=m?le:le+wt;nr=Ne&&Rr.type===f)return nr}if(M){const nr=te[Ne];if(nr&&Ya(nr)&&nr.type===f)return Ne}return null}function Sc(a,l,f,m){let M=a[f];const k=l.data;if(function(a){return a instanceof Js}(M)){const te=M;te.resolving&&function(a,l){throw new ee("200",`Circular dependency in DI detected for ${a}`)}(se(k[f]));const le=qt(te.canSeeViewProviders);te.resolving=!0;const Ne=te.injectImpl?Hi(te.injectImpl):null;ut(a,m,Yr.Default);try{M=a[f]=te.factory(void 0,k,a,m),l.firstCreatePass&&f>=m.directiveStart&&function(a,l,f){const{ngOnChanges:m,ngOnInit:M,ngDoCheck:k}=l.type.prototype;if(m){const te=ba(l);(f.preOrderHooks||(f.preOrderHooks=[])).push(a,te),(f.preOrderCheckHooks||(f.preOrderCheckHooks=[])).push(a,te)}M&&(f.preOrderHooks||(f.preOrderHooks=[])).push(0-a,M),k&&((f.preOrderHooks||(f.preOrderHooks=[])).push(a,k),(f.preOrderCheckHooks||(f.preOrderCheckHooks=[])).push(a,k))}(f,k[f],l)}finally{null!==Ne&&Hi(Ne),qt(le),te.resolving=!1,gi()}}return M}function Cc(a,l,f){return!!(f[l+(a>>5)]&1<{const l=a.prototype.constructor,f=l[_n]||Ju(l),m=Object.prototype;let M=Object.getPrototypeOf(a.prototype).constructor;for(;M&&M!==m;){const k=M[_n]||Ju(M);if(k&&k!==f)return k;M=Object.getPrototypeOf(M)}return k=>new k})}function Ju(a){return J(a)?()=>{const l=Ju(Z(a));return l&&l()}:wo(a)}function Sl(a){return function(a,l){if("class"===l)return a.classes;if("style"===l)return a.styles;const f=a.attrs;if(f){const m=f.length;let M=0;for(;M{const m=Qu(l);function M(...k){if(this instanceof M)return m.apply(this,k),this;const te=new M(...k);return le.annotation=te,le;function le(Ne,qe,wt){const ln=Ne.hasOwnProperty(kl)?Ne[kl]:Object.defineProperty(Ne,kl,{value:[]})[kl];for(;ln.length<=wt;)ln.push(null);return(ln[wt]=ln[wt]||[]).push(te),Ne}}return f&&(M.prototype=Object.create(f.prototype)),M.prototype.ngMetadataName=a,M.annotationCls=M,M})}function Oc(a,l,f,m){return Wt(()=>{const M=Qu(l);function k(...te){if(this instanceof k)return M.apply(this,te),this;const le=new k(...te);return function(qe,wt){const ln=qe.constructor,dn=ln.hasOwnProperty(Pu)?ln[Pu]:Object.defineProperty(ln,Pu,{value:{}})[Pu];dn[wt]=dn.hasOwnProperty(wt)&&dn[wt]||[],dn[wt].unshift(le),m&&m(qe,wt,...te)}}return f&&(k.prototype=Object.create(f.prototype)),k.prototype.ngMetadataName=a,k.annotationCls=k,k})}class Jl{constructor(l,f){this._desc=l,this.ngMetadataName="InjectionToken",this.\u0275prov=void 0,"number"==typeof f?this.__NG_ELEMENT_ID__=f:void 0!==f&&(this.\u0275prov=xt({token:this,providedIn:f.providedIn||"root",factory:f.factory}))}toString(){return`InjectionToken ${this._desc}`}}const vd=new Jl("AnalyzeForEntryComponents");class Gc{}const fp=Oc("ViewChild",(a,l)=>Object.assign({selector:a,first:!0,isViewQuery:!0,descendants:!0},l),Gc),Hf=Function;function Dc(a,l){void 0===l&&(l=a);for(let f=0;fArray.isArray(f)?Xl(f,l):l(f))}function Ji(a,l,f){l>=a.length?a.push(f):a.splice(l,0,f)}function cl(a,l){return l>=a.length-1?a.pop():a.splice(l,1)[0]}function Ll(a,l){const f=[];for(let m=0;m=0?a[1|m]=f:(m=~m,function(a,l,f,m){let M=a.length;if(M==l)a.push(f,m);else if(1===M)a.push(m,a[0]),a[0]=f;else{for(M--,a.push(a[M-1],a[M]);M>l;)a[M]=a[M-2],M--;a[l]=f,a[l+1]=m}}(a,m,l,f)),m}function Id(a,l){const f=oc(a,l);if(f>=0)return a[1|f]}function oc(a,l){return function(a,l,f){let m=0,M=a.length>>f;for(;M!==m;){const k=m+(M-m>>1),te=a[k<l?M=k:m=k+1}return~(M< ");else if("object"==typeof l){let k=[];for(let te in l)if(l.hasOwnProperty(te)){let le=l[te];k.push(te+":"+("string"==typeof le?JSON.stringify(le):S(le)))}M=`{${k.join(", ")}}`}return`${f}${m?"("+m+")":""}[${M}]: ${a.replace(ye,"\n ")}`}("\n"+a.message,M,f,m),a.ngTokenPath=M,a[q]=null,a}const ha=Zo(en("Inject",a=>({token:a})),-1),ia=Zo(en("Optional"),8),il=Zo(en("SkipSelf"),4);let na,Hp;function zl(a){var l;return(null===(l=function(){if(void 0===na&&(na=null,Dr.trustedTypes))try{na=Dr.trustedTypes.createPolicy("angular",{createHTML:a=>a,createScript:a=>a,createScriptURL:a=>a})}catch(a){}return na}())||void 0===l?void 0:l.createHTML(a))||a}function Up(){if(void 0===Hp&&(Hp=null,Dr.trustedTypes))try{Hp=Dr.trustedTypes.createPolicy("angular#unsafe-bypass",{createHTML:a=>a,createScript:a=>a,createScriptURL:a=>a})}catch(a){}return Hp}function mf(a){var l;return(null===(l=Up())||void 0===l?void 0:l.createHTML(a))||a}function dd(a){var l;return(null===(l=Up())||void 0===l?void 0:l.createScriptURL(a))||a}class Fd{constructor(l){this.changingThisBreaksApplicationSecurity=l}toString(){return`SafeValue must use [property]=binding: ${this.changingThisBreaksApplicationSecurity} (see https://g.co/ng/security#xss)`}}class pp extends Fd{getTypeName(){return"HTML"}}class fl extends Fd{getTypeName(){return"Style"}}class i_ extends Fd{getTypeName(){return"Script"}}class F_ extends Fd{getTypeName(){return"URL"}}class Bf extends Fd{getTypeName(){return"ResourceURL"}}function kd(a){return a instanceof Fd?a.changingThisBreaksApplicationSecurity:a}function Yf(a,l){const f=eh(a);if(null!=f&&f!==l){if("ResourceURL"===f&&"URL"===l)return!0;throw new Error(`Required a safe ${l}, got a ${f} (see https://g.co/ng/security#xss)`)}return f===l}function eh(a){return a instanceof Fd&&a.getTypeName()||null}function bd(a){return new pp(a)}function Rh(a){return new fl(a)}function th(a){return new i_(a)}function nh(a){return new F_(a)}function Nh(a){return new Bf(a)}function rh(a){const l=new xf(a);return function(){try{return!!(new window.DOMParser).parseFromString(zl(""),"text/html")}catch(a){return!1}}()?new Ed(l):l}class Ed{constructor(l){this.inertDocumentHelper=l}getInertBodyElement(l){l=""+l;try{const f=(new window.DOMParser).parseFromString(zl(l),"text/html").body;return null===f?this.inertDocumentHelper.getInertBodyElement(l):(f.removeChild(f.firstChild),f)}catch(f){return null}}}class xf{constructor(l){if(this.defaultDoc=l,this.inertDocument=this.defaultDoc.implementation.createHTMLDocument("sanitization-inert"),null==this.inertDocument.body){const f=this.inertDocument.createElement("html");this.inertDocument.appendChild(f);const m=this.inertDocument.createElement("body");f.appendChild(m)}}getInertBodyElement(l){const f=this.inertDocument.createElement("template");if("content"in f)return f.innerHTML=zl(l),f;const m=this.inertDocument.createElement("body");return m.innerHTML=zl(l),this.defaultDoc.documentMode&&this.stripCustomNsAttrs(m),m}stripCustomNsAttrs(l){const f=l.attributes;for(let M=f.length-1;0vf(l.trim())).join(", ")}function $d(a){const l={};for(const f of a.split(","))l[f]=!0;return l}function hp(...a){const l={};for(const f of a)for(const m in f)f.hasOwnProperty(m)&&(l[m]=!0);return l}const Zf=$d("area,br,col,hr,img,wbr"),qd=$d("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),ac=$d("rp,rt"),Y=hp(Zf,hp(qd,$d("address,article,aside,blockquote,caption,center,del,details,dialog,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,main,map,menu,nav,ol,pre,section,summary,table,ul")),hp(ac,$d("a,abbr,acronym,audio,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,picture,q,ruby,rp,rt,s,samp,small,source,span,strike,strong,sub,sup,time,track,tt,u,var,video")),hp(ac,qd)),fe=$d("background,cite,href,itemtype,longdesc,poster,src,xlink:href"),w=$d("srcset"),ct=hp(fe,w,$d("abbr,accesskey,align,alt,autoplay,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,controls,coords,datetime,default,dir,download,face,headers,height,hidden,hreflang,hspace,ismap,itemscope,itemprop,kind,label,lang,language,loop,media,muted,nohref,nowrap,open,preload,rel,rev,role,rows,rowspan,rules,scope,scrolling,shape,size,sizes,span,srclang,start,summary,tabindex,target,title,translate,type,usemap,valign,value,vspace,width"),$d("aria-activedescendant,aria-atomic,aria-autocomplete,aria-busy,aria-checked,aria-colcount,aria-colindex,aria-colspan,aria-controls,aria-current,aria-describedby,aria-details,aria-disabled,aria-dropeffect,aria-errormessage,aria-expanded,aria-flowto,aria-grabbed,aria-haspopup,aria-hidden,aria-invalid,aria-keyshortcuts,aria-label,aria-labelledby,aria-level,aria-live,aria-modal,aria-multiline,aria-multiselectable,aria-orientation,aria-owns,aria-placeholder,aria-posinset,aria-pressed,aria-readonly,aria-relevant,aria-required,aria-roledescription,aria-rowcount,aria-rowindex,aria-rowspan,aria-selected,aria-setsize,aria-sort,aria-valuemax,aria-valuemin,aria-valuenow,aria-valuetext")),Jn=$d("script,style,template");class Ir{constructor(){this.sanitizedSomething=!1,this.buf=[]}sanitizeChildren(l){let f=l.firstChild,m=!0;for(;f;)if(f.nodeType===Node.ELEMENT_NODE?m=this.startElement(f):f.nodeType===Node.TEXT_NODE?this.chars(f.nodeValue):this.sanitizedSomething=!0,m&&f.firstChild)f=f.firstChild;else for(;f;){f.nodeType===Node.ELEMENT_NODE&&this.endElement(f);let M=this.checkClobberedElement(f,f.nextSibling);if(M){f=M;break}f=this.checkClobberedElement(f,f.parentNode)}return this.buf.join("")}startElement(l){const f=l.nodeName.toLowerCase();if(!Y.hasOwnProperty(f))return this.sanitizedSomething=!0,!Jn.hasOwnProperty(f);this.buf.push("<"),this.buf.push(f);const m=l.attributes;for(let M=0;M"),!0}endElement(l){const f=l.nodeName.toLowerCase();Y.hasOwnProperty(f)&&!Zf.hasOwnProperty(f)&&(this.buf.push(""))}chars(l){this.buf.push(Do(l))}checkClobberedElement(l,f){if(f&&(l.compareDocumentPosition(f)&Node.DOCUMENT_POSITION_CONTAINED_BY)===Node.DOCUMENT_POSITION_CONTAINED_BY)throw new Error(`Failed to sanitize html because the element is clobbered: ${l.outerHTML}`);return f}}const vi=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,zi=/([^\#-~ |!])/g;function Do(a){return a.replace(/&/g,"&").replace(vi,function(l){return"&#"+(1024*(l.charCodeAt(0)-55296)+(l.charCodeAt(1)-56320)+65536)+";"}).replace(zi,function(l){return"&#"+l.charCodeAt(0)+";"}).replace(//g,">")}let xs;function Xo(a,l){let f=null;try{xs=xs||rh(a);let m=l?String(l):"";f=xs.getInertBodyElement(m);let M=5,k=m;do{if(0===M)throw new Error("Failed to sanitize html because the input is unstable");M--,m=k,k=f.innerHTML,f=xs.getInertBodyElement(m)}while(m!==k);return zl((new Ir).sanitizeChildren(Rs(f)||f))}finally{if(f){const m=Rs(f)||f;for(;m.firstChild;)m.removeChild(m.firstChild)}}}function Rs(a){return"content"in a&&function(a){return a.nodeType===Node.ELEMENT_NODE&&"TEMPLATE"===a.nodeName}(a)?a.content:null}var Vs=(()=>((Vs=Vs||{})[Vs.NONE=0]="NONE",Vs[Vs.HTML=1]="HTML",Vs[Vs.STYLE=2]="STYLE",Vs[Vs.SCRIPT=3]="SCRIPT",Vs[Vs.URL=4]="URL",Vs[Vs.RESOURCE_URL=5]="RESOURCE_URL",Vs))();function _c(a){const l=gp();return l?mf(l.sanitize(Vs.HTML,a)||""):Yf(a,"HTML")?mf(kd(a)):Xo(Su(),H(a))}function wu(a){const l=gp();return l?l.sanitize(Vs.URL,a)||"":Yf(a,"URL")?kd(a):vf(H(a))}function Hd(a){const l=gp();if(l)return dd(l.sanitize(Vs.RESOURCE_URL,a)||"");if(Yf(a,"ResourceURL"))return dd(kd(a));throw new Error("unsafe value used in a resource URL context (see https://g.co/ng/security#xss)")}function gp(){const a=Li();return a&&a[12]}const B_="__ngContext__";function Wc(a,l){a[B_]=l}function a_(a){const l=function(a){return a[B_]||null}(a);return l?Array.isArray(l)?l:l.lView:null}function Zu(a){return a.ngOriginalError}function Yp(a,...l){a.error(...l)}class Gf{constructor(){this._console=console}handleError(l){const f=this._findOriginalError(l),m=this._findContext(l),M=(a=l)&&a.ngErrorLogger||Yp;var a;M(this._console,"ERROR",l),f&&M(this._console,"ORIGINAL ERROR",f),m&&M(this._console,"ERROR CONTEXT",m)}_findContext(l){return l?l.ngDebugContext||this._findContext(Zu(l)):null}_findOriginalError(l){let f=l&&Zu(l);for(;f&&Zu(f);)f=Zu(f);return f||null}}const Z_=/^>|^->||--!>|)/;const Hh=(()=>("undefined"!=typeof requestAnimationFrame&&requestAnimationFrame||setTimeout).bind(Dr))();function Uh(a){return a.ownerDocument.defaultView}function Vf(a){return a.ownerDocument.body}function yf(a){return a instanceof Function?a():a}var Bd=(()=>((Bd=Bd||{})[Bd.Important=1]="Important",Bd[Bd.DashCase=2]="DashCase",Bd))();let fd;function bf(a,l){return fd(a,l)}function od(a){const l=a[3];return $s(l)?l[3]:l}function np(a){return fh(a[13])}function V_(a){return fh(a[4])}function fh(a){for(;null!==a&&!$s(a);)a=a[4];return a}function Bc(a,l,f,m,M){if(null!=m){let k,te=!1;$s(m)?k=m:ca(m)&&(te=!0,m=m[0]);const le=Me(m);0===a&&null!==f?null==M?Bg(l,f,le):_h(l,f,le,M||null,!0):1===a&&null!==f?_h(l,f,le,M||null,!0):2===a?y_(l,le,te):3===a&&l.destroyNode(le),null!=k&&function(a,l,f,m,M){const k=f[7];k!==Me(f)&&Bc(l,a,m,k,M);for(let le=10;lel.replace(j_,"\u200b$1\u200b"))}(l))}function Bh(a,l,f){return rt(a)?a.createElement(l,f):null===f?a.createElement(l):a.createElementNS(f,l)}function Hg(a,l){const f=a[9],m=f.indexOf(l),M=l[3];1024&l[2]&&(l[2]&=-1025,Al(M,-1)),f.splice(m,1)}function gg(a,l){if(a.length<=10)return;const f=10+l,m=a[f];if(m){const M=m[17];null!==M&&M!==a&&Hg(M,m),l>0&&(a[f-1][4]=m[4]);const k=cl(a,10+l);!function(a,l){jh(a,l,l[11],2,null,null),l[0]=null,l[6]=null}(m[1],m);const te=k[19];null!==te&&te.detachView(k[1]),m[3]=null,m[4]=null,m[2]&=-129}return m}function Ep(a,l){if(!(256&l[2])){const f=l[11];rt(f)&&f.destroyNode&&jh(a,l,f,3,null,null),function(a){let l=a[13];if(!l)return mg(a[1],a);for(;l;){let f=null;if(ca(l))f=l[13];else{const m=l[10];m&&(f=m)}if(!f){for(;l&&!l[4]&&l!==a;)ca(l)&&mg(l[1],l),l=l[3];null===l&&(l=a),ca(l)&&mg(l[1],l),f=l&&l[4]}l=f}}(l)}}function mg(a,l){if(!(256&l[2])){l[2]&=-129,l[2]|=256,function(a,l){let f;if(null!=a&&null!=(f=a.destroyHooks))for(let m=0;m=0?m[M=qe]():m[M=-qe].unsubscribe(),k+=2}else{const te=m[M=f[k+1]];f[k].call(te)}if(null!==m){for(let k=M+1;kk?"":M[ln+1].toLowerCase();const nr=8&m?dn:null;if(nr&&-1!==Sg(nr,qe,0)||2&m&&qe!==dn){if(G(m))return!1;te=!0}}}}else{if(!te&&!G(m)&&!G(Ne))return!1;if(te&&G(Ne))continue;te=!1,m=Ne|1&m}}return G(m)||te}function G(a){return 0==(1&a)}function Se(a,l,f,m){if(null===l)return-1;let M=0;if(m||!f){let k=!1;for(;M-1)for(f++;f0?'="'+le+'"':"")+"]"}else 8&m?M+="."+te:4&m&&(M+=" "+te);else""!==M&&!G(te)&&(l+=Si(k,M),M=""),m=te,k=k||!G(m);f++}return""!==M&&(l+=Si(k,M)),l}const po={};function Fa(a){Ta(sa(),Li(),In()+a,Uu())}function Ta(a,l,f,m){if(!m)if(3==(3&l[2])){const k=a.preOrderCheckHooks;null!==k&&Gl(l,k,f)}else{const k=a.preOrderHooks;null!==k&&$i(l,k,0,f)}Cr(f)}function ni(a,l){return a<<17|l<<2}function Tr(a){return a>>17&32767}function io(a){return 2|a}function va(a){return(131068&a)>>2}function Cs(a,l){return-131069&a|l<<2}function xl(a){return 1|a}function hv(a,l){const f=a.contentQueries;if(null!==f)for(let m=0;m20&&Ta(a,l,20,Uu()),f(m,M)}finally{Cr(k)}}function c1(a,l,f){if(da(l)){const M=l.directiveEnd;for(let k=l.directiveStart;k0;){const f=a[--l];if("number"==typeof f&&f<0)return f}return 0})(le)!=Ne&&le.push(Ne),le.push(m,M,te)}}function Qc(a,l){null!==a.hostBindings&&a.hostBindings(1,l)}function ju(a,l){l.flags|=2,(a.components||(a.components=[])).push(l.index)}function D0(a,l,f){if(f){if(l.exportAs)for(let m=0;m0&&Gu(f)}}function Gu(a){for(let m=np(a);null!==m;m=V_(m))for(let M=10;M0&&Gu(k)}const f=a[1].components;if(null!==f)for(let m=0;m0&&Gu(M)}}function Nm(a,l){const f=Ts(l,a),m=f[1];(function(a,l){for(let f=l.length;fPromise.resolve(null))();function Vu(a){return a[7]||(a[7]=[])}function yb(a){return a.cleanup||(a.cleanup=[])}function mv(a,l){const f=a[9],m=f?f.get(Gf,null):null;m&&m.handleError(l)}function bb(a,l,f,m,M){for(let k=0;kthis.processProvider(le,l,f)),Xl([l],le=>this.processInjectorType(le,[],k)),this.records.set(Jg,sd(void 0,this));const te=this.records.get(Im);this.scope=null!=te?te.value:null,this.source=M||("object"==typeof l?null:S(l))}get destroyed(){return this._destroyed}destroy(){this.assertNotDestroyed(),this._destroyed=!0;try{this.onDestroy.forEach(l=>l.ngOnDestroy())}finally{this.records.clear(),this.onDestroy.clear(),this.injectorDefTypes.clear()}}get(l,f=R,m=Yr.Default){this.assertNotDestroyed();const M=nn(this),k=Hi(void 0);try{if(!(m&Yr.SkipSelf)){let le=this.records.get(l);if(void 0===le){const Ne=("function"==typeof(a=l)||"object"==typeof a&&a instanceof Jl)&&Ur(l);le=Ne&&this.injectableDefInScope(Ne)?sd(yv(l),Lm):null,this.records.set(l,le)}if(null!=le)return this.hydrate(l,le)}return(m&Yr.Self?Ag():this.parent).get(l,f=m&Yr.Optional&&f===R?null:f)}catch(te){if("NullInjectorError"===te.name){if((te[q]=te[q]||[]).unshift(S(l)),M)throw te;return Gs(te,l,"R3InjectorError",this.source)}throw te}finally{Hi(k),nn(M)}var a}_resolveInjectorDefTypes(){this.injectorDefTypes.forEach(l=>this.get(l))}toString(){const l=[];return this.records.forEach((m,M)=>l.push(S(M))),`R3Injector[${l.join(", ")}]`}assertNotDestroyed(){if(this._destroyed)throw new Error("Injector has already been destroyed.")}processInjectorType(l,f,m){if(!(l=Z(l)))return!1;let M=Kr(l);const k=null==M&&l.ngModule||void 0,te=void 0===k?l:k,le=-1!==m.indexOf(te);if(void 0!==k&&(M=Kr(k)),null==M)return!1;if(null!=M.imports&&!le){let wt;m.push(te);try{Xl(M.imports,ln=>{this.processInjectorType(ln,f,m)&&(void 0===wt&&(wt=[]),wt.push(ln))})}finally{}if(void 0!==wt)for(let ln=0;lnthis.processProvider(Rr,dn,nr||Jt))}}this.injectorDefTypes.add(te);const Ne=wo(te)||(()=>new te);this.records.set(te,sd(Ne,Lm));const qe=M.providers;if(null!=qe&&!le){const wt=l;Xl(qe,ln=>this.processProvider(ln,wt,qe))}return void 0!==k&&void 0!==l.providers}processProvider(l,f,m){let M=Og(l=Z(l))?l:Z(l&&l.provide);const k=g1(a=l)?sd(void 0,a.useValue):sd(Md(a),Lm);var a;if(Og(l)||!0!==l.multi)this.records.get(M);else{let te=this.records.get(M);te||(te=sd(void 0,Lm,!0),te.factory=()=>Pi(te.multi),this.records.set(M,te)),M=l,te.multi.push(l)}this.records.set(M,k)}hydrate(l,f){return f.value===Lm&&(f.value=pd,f.value=f.factory()),"object"==typeof f.value&&f.value&&null!==(a=f.value)&&"object"==typeof a&&"function"==typeof a.ngOnDestroy&&this.onDestroy.add(f.value),f.value;var a}injectableDefInScope(l){if(!l.providedIn)return!1;const f=Z(l.providedIn);return"string"==typeof f?"any"===f||f===this.scope:this.injectorDefTypes.has(f)}}function yv(a){const l=Ur(a),f=null!==l?l.factory:wo(a);if(null!==f)return f;if(a instanceof Jl)throw new Error(`Token ${S(a)} is missing a \u0275prov definition.`);if(a instanceof Function)return function(a){const l=a.length;if(l>0){const m=Ll(l,"?");throw new Error(`Can't resolve all parameters for ${S(a)}: (${m.join(", ")}).`)}const f=function(a){const l=a&&(a[ei]||a[$n]);if(l){const f=function(a){if(a.hasOwnProperty("name"))return a.name;const l=(""+a).match(/^function\s*([^\s(]+)/);return null===l?"":l[1]}(a);return console.warn(`DEPRECATED: DI is instantiating a token "${f}" that inherits its @Injectable decorator but does not provide one itself.\nThis will become an error in a future version of Angular. Please add @Injectable() to the "${f}" class.`),l}return null}(a);return null!==f?()=>f.factory(a):()=>new a}(a);throw new Error("unreachable")}function Md(a,l,f){let m;if(Og(a)){const M=Z(a);return wo(M)||yv(M)}if(g1(a))m=()=>Z(a.useValue);else if(function(a){return!(!a||!a.useFactory)}(a))m=()=>a.useFactory(...Pi(a.deps||[]));else if(function(a){return!(!a||!a.useExisting)}(a))m=()=>xn(Z(a.useExisting));else{const M=Z(a&&(a.useClass||a.provide));if(!function(a){return!!a.deps}(a))return wo(M)||yv(M);m=()=>new M(...Pi(a.deps))}return m}function sd(a,l,f=!1){return{factory:a,value:l,multi:f?[]:void 0}}function g1(a){return null!==a&&"object"==typeof a&> in a}function Og(a){return"function"==typeof a}const U0=function(a,l,f){return function(a,l=null,f=null,m){const M=Sb(a,l,f,m);return M._resolveInjectorDefTypes(),M}({name:f},l,a,f)};let Ac=(()=>{class a{static create(f,m){return Array.isArray(f)?U0(f,m,""):U0(f.providers,f.parent,f.name||"")}}return a.THROW_IF_NOT_FOUND=R,a.NULL=new k0,a.\u0275prov=xt({token:a,providedIn:"any",factory:()=>xn(Jg)}),a.__NG_ELEMENT_ID__=-1,a})();function ny(a,l){$c(a_(a)[1],Ss())}function T1(a){let l=function(a){return Object.getPrototypeOf(a.prototype).constructor}(a.type),f=!0;const m=[a];for(;l;){let M;if(Ya(a))M=l.\u0275cmp||l.\u0275dir;else{if(l.\u0275cmp)throw new Error("Directives cannot inherit Components");M=l.\u0275dir}if(M){if(f){m.push(M);const te=a;te.inputs=Nf(a.inputs),te.declaredInputs=Nf(a.declaredInputs),te.outputs=Nf(a.outputs);const le=M.hostBindings;le&&Bb(a,le);const Ne=M.viewQuery,qe=M.contentQueries;if(Ne&&S1(a,Ne),qe&&Ub(a,qe),y(a.inputs,M.inputs),y(a.declaredInputs,M.declaredInputs),y(a.outputs,M.outputs),Ya(M)&&M.data.animation){const wt=a.data;wt.animation=(wt.animation||[]).concat(M.data.animation)}}const k=M.features;if(k)for(let te=0;te=0;m--){const M=a[m];M.hostVars=l+=M.hostVars,M.hostAttrs=Wd(M.hostAttrs,f=Wd(f,M.hostAttrs))}}(m)}function Nf(a){return a===gr?{}:a===Jt?[]:a}function S1(a,l){const f=a.viewQuery;a.viewQuery=f?(m,M)=>{l(m,M),f(m,M)}:l}function Ub(a,l){const f=a.contentQueries;a.contentQueries=f?(m,M,k)=>{l(m,M,k),f(m,M,k)}:l}function Bb(a,l){const f=a.hostBindings;a.hostBindings=f?(m,M)=>{l(m,M),f(m,M)}:l}let Tv=null;function tg(){if(!Tv){const a=Dr.Symbol;if(a&&a.iterator)Tv=a.iterator;else{const l=Object.getOwnPropertyNames(Map.prototype);for(let f=0;fle(Me(Xa[m.index])):m.index;if(rt(f)){let Xa=null;if(!le&&Ne&&(Xa=function(a,l,f,m){const M=a.cleanup;if(null!=M)for(let k=0;kNe?le[Ne]:null}"string"==typeof te&&(k+=2)}return null}(a,l,M,m.index)),null!==Xa)(Xa.__ngLastListenerFn__||Xa).__ngNextListenerFn__=k,Xa.__ngLastListenerFn__=k,nr=!1;else{k=tE(m,l,ln,k,!1);const du=f.listen(Oo,M,k);dn.push(k,du),wt&&wt.push(M,ua,Xi,Xi+1)}}else k=tE(m,l,ln,k,!0),Oo.addEventListener(M,k,te),dn.push(k),wt&&wt.push(M,ua,Xi,te)}else k=tE(m,l,ln,k,!1);const Rr=m.outputs;let Ti;if(nr&&null!==Rr&&(Ti=Rr[M])){const ii=Ti.length;if(ii)for(let Oo=0;Oo0;)l=l[15],a--;return l}(a,vs.lFrame.contextLView))[8]}(a)}function fO(a,l){let f=null;const m=function(a){const l=a.attrs;if(null!=l){const f=l.indexOf(5);if(0==(1&f))return l[f+1]}return null}(a);for(let M=0;M=0}const Tf={textEnd:0,key:0,keyEnd:0,value:0,valueEnd:0};function XS(a){return a.substring(Tf.key,Tf.keyEnd)}function KS(a,l){const f=Tf.textEnd;return f===l?-1:(l=Tf.keyEnd=function(a,l,f){for(;l32;)l++;return l}(a,Tf.key=l,f),Ov(a,l,f))}function Ov(a,l,f){for(;l=0;f=KS(l,f))Vl(a,XS(l),!0)}function O_(a,l,f,m){const M=Li(),k=sa(),te=aa(2);k.firstUpdatePass&&oC(k,a,te,m),l!==po&&nf(M,te,l)&&wv(k,k.data[In()],M,M[11],a,M[te+1]=function(a,l){return null==a||("string"==typeof l?a+=l:"object"==typeof a&&(a=S(kd(a)))),a}(l,f),m,te)}function w_(a,l,f,m){const M=sa(),k=aa(2);M.firstUpdatePass&&oC(M,null,k,m);const te=Li();if(f!==po&&nf(te,k,f)){const le=M.data[In()];if(_y(le,m)&&!iC(M,k)){let Ne=m?le.classesWithoutHost:le.stylesWithoutHost;null!==Ne&&(f=A(Ne,f||"")),Wb(M,le,te,f,m)}else!function(a,l,f,m,M,k,te,le){M===po&&(M=Jt);let Ne=0,qe=0,wt=0=a.expandoStartIndex}function oC(a,l,f,m){const M=a.data;if(null===M[f+1]){const k=M[In()],te=iC(a,f);_y(k,m)&&null===l&&!te&&(l=!1),l=function(a,l,f,m){const M=V(a);let k=m?l.residualClasses:l.residualStyles;if(null===M)0===(m?l.classBindings:l.styleBindings)&&(f=R1(f=sE(null,a,l,f,m),l.attrs,m),k=null);else{const te=l.directiveStylingLast;if(-1===te||a[te]!==M)if(f=sE(M,a,l,f,m),null===k){let Ne=function(a,l,f){const m=f?l.classBindings:l.styleBindings;if(0!==va(m))return a[Tr(m)]}(a,l,m);void 0!==Ne&&Array.isArray(Ne)&&(Ne=sE(null,a,l,Ne[1],m),Ne=R1(Ne,l.attrs,m),function(a,l,f,m){a[Tr(f?l.classBindings:l.styleBindings)]=m}(a,l,m,Ne))}else k=function(a,l,f){let m;const M=l.directiveEnd;for(let k=1+l.directiveStylingLast;k0)&&(qe=!0)}else wt=f;if(M)if(0!==Ne){const dn=Tr(a[le+1]);a[m+1]=ni(dn,le),0!==dn&&(a[dn+1]=Cs(a[dn+1],m)),a[le+1]=function(a,l){return 131071&a|l<<17}(a[le+1],m)}else a[m+1]=ni(le,0),0!==le&&(a[le+1]=Cs(a[le+1],m)),le=m;else a[m+1]=ni(Ne,0),0===le?le=m:a[Ne+1]=Cs(a[Ne+1],m),Ne=m;qe&&(a[m+1]=io(a[m+1])),QS(a,wt,m,!0),QS(a,wt,m,!1),function(a,l,f,m,M){const k=M?a.residualClasses:a.residualStyles;null!=k&&"string"==typeof l&&oc(k,l)>=0&&(f[m+1]=xl(f[m+1]))}(l,wt,a,m,k),te=ni(le,Ne),k?l.classBindings=te:l.styleBindings=te}(M,k,l,f,te,m)}}function sE(a,l,f,m,M){let k=null;const te=f.directiveEnd;let le=f.directiveStylingLast;for(-1===le?le=f.directiveStart:le++;le0;){const Ne=a[M],qe=Array.isArray(Ne),wt=qe?Ne[1]:Ne,ln=null===wt;let dn=f[M+1];dn===po&&(dn=ln?Jt:void 0);let nr=ln?Id(dn,m):wt===m?dn:void 0;if(qe&&!hy(nr)&&(nr=Id(Ne,m)),hy(nr)&&(le=nr,te))return le;const Rr=a[M+1];M=te?Tr(Rr):va(Rr)}if(null!==l){let Ne=k?l.residualClasses:l.residualStyles;null!=Ne&&(le=Id(Ne,m))}return le}function hy(a){return void 0!==a}function _y(a,l){return 0!=(a.flags&(l?16:32))}function uC(a,l=""){const f=Li(),m=sa(),M=a+20,k=m.firstCreatePass?ip(m,M,1,l,null):m.data[M],te=f[M]=Rf(f[11],l);Yh(m,f,te,k),Mu(k,!1)}function aE(a){return gy("",a,""),aE}function gy(a,l,f){const m=Li(),M=Kg(m,a,l,f);return M!==po&&eg(m,In(),M),gy}function lE(a,l,f,m,M){const k=Li(),te=qg(k,a,l,f,m,M);return te!==po&&eg(k,In(),te),lE}function uE(a,l,f,m,M,k,te){const le=Li(),Ne=function(a,l,f,m,M,k,te,le){const qe=Sv(a,wr(),f,M,te);return aa(3),qe?l+H(f)+m+H(M)+k+H(te)+le:po}(le,a,l,f,m,M,k,te);return Ne!==po&&eg(le,In(),Ne),uE}function gC(a,l,f){w_(Vl,ig,Kg(Li(),a,l,f),!0)}function mC(a,l,f,m,M){w_(Vl,ig,qg(Li(),a,l,f,m,M),!0)}function vC(a,l,f,m,M,k,te,le,Ne){w_(Vl,ig,function(a,l,f,m,M,k,te,le,Ne,qe){const ln=zp(a,wr(),f,M,te,Ne);return aa(4),ln?l+H(f)+m+H(M)+k+H(te)+le+H(Ne)+qe:po}(Li(),a,l,f,m,M,k,te,le,Ne),!0)}function cE(a,l,f){const m=Li();return nf(m,pu(),l)&&Ro(sa(),hr(),m,a,l,m[11],f,!0),cE}function dE(a,l,f){const m=Li();if(nf(m,pu(),l)){const k=sa(),te=hr();Ro(k,te,m,a,l,function(a,l,f){return(null===a||Ya(a))&&(f=function(a){for(;Array.isArray(a);){if("object"==typeof a[1])return a;a=a[0]}return null}(f[l.index])),f[11]}(V(k.data),te,m),f,!0)}return dE}const jm=void 0;var GO=["en",[["a","p"],["AM","PM"],jm],[["AM","PM"],jm,jm],[["S","M","T","W","T","F","S"],["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],["Su","Mo","Tu","We","Th","Fr","Sa"]],jm,[["J","F","M","A","M","J","J","A","S","O","N","D"],["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],["January","February","March","April","May","June","July","August","September","October","November","December"]],jm,[["B","A"],["BC","AD"],["Before Christ","Anno Domini"]],0,[6,0],["M/d/yy","MMM d, y","MMMM d, y","EEEE, MMMM d, y"],["h:mm a","h:mm:ss a","h:mm:ss a z","h:mm:ss a zzzz"],["{1}, {0}",jm,"{1} 'at' {0}",jm],[".",",",";","%","+","-","E","\xd7","\u2030","\u221e","NaN",":"],["#,##0.###","#,##0%","\xa4#,##0.00","#E0"],"USD","$","US Dollar",{},"ltr",function(a){const l=Math.floor(Math.abs(a)),f=a.toString().replace(/^[^.]*\.?/,"").length;return 1===l&&0===f?1:5}];let xv={};function fE(a){const l=function(a){return a.toLowerCase().replace(/_/g,"-")}(a);let f=xC(l);if(f)return f;const m=l.split("-")[0];if(f=xC(m),f)return f;if("en"===m)return GO;throw new Error(`Missing locale data for the locale "${a}".`)}function wC(a){return fE(a)[Ua.PluralCase]}function xC(a){return a in xv||(xv[a]=Dr.ng&&Dr.ng.common&&Dr.ng.common.locales&&Dr.ng.common.locales[a]),xv[a]}var Ua=(()=>((Ua=Ua||{})[Ua.LocaleId=0]="LocaleId",Ua[Ua.DayPeriodsFormat=1]="DayPeriodsFormat",Ua[Ua.DayPeriodsStandalone=2]="DayPeriodsStandalone",Ua[Ua.DaysFormat=3]="DaysFormat",Ua[Ua.DaysStandalone=4]="DaysStandalone",Ua[Ua.MonthsFormat=5]="MonthsFormat",Ua[Ua.MonthsStandalone=6]="MonthsStandalone",Ua[Ua.Eras=7]="Eras",Ua[Ua.FirstDayOfWeek=8]="FirstDayOfWeek",Ua[Ua.WeekendRange=9]="WeekendRange",Ua[Ua.DateFormat=10]="DateFormat",Ua[Ua.TimeFormat=11]="TimeFormat",Ua[Ua.DateTimeFormat=12]="DateTimeFormat",Ua[Ua.NumberSymbols=13]="NumberSymbols",Ua[Ua.NumberFormats=14]="NumberFormats",Ua[Ua.CurrencyCode=15]="CurrencyCode",Ua[Ua.CurrencySymbol=16]="CurrencySymbol",Ua[Ua.CurrencyName=17]="CurrencyName",Ua[Ua.Currencies=18]="Currencies",Ua[Ua.Directionality=19]="Directionality",Ua[Ua.PluralCase=20]="PluralCase",Ua[Ua.ExtraData=21]="ExtraData",Ua))();const zO=["zero","one","two","few","many"],my="en-US",vy={marker:"element"},yy={marker:"ICU"};var Ad=(()=>((Ad=Ad||{})[Ad.SHIFT=2]="SHIFT",Ad[Ad.APPEND_EAGERLY=1]="APPEND_EAGERLY",Ad[Ad.COMMENT=2]="COMMENT",Ad))();let DC=my;function pE(a){(function(a,l){null==a&&Ut(l,a,null,"!=")})(a,"Expected localeId to be defined"),"string"==typeof a&&(DC=a.toLowerCase().replace(/_/g,"-"))}function RC(a,l,f){const m=l.insertBeforeIndex,M=Array.isArray(m)?m[0]:m;return null===M?Sp(a,0,f):Me(f[M])}function hE(a,l,f,m,M){const k=l.insertBeforeIndex;if(Array.isArray(k)){let te=m,le=null;if(3&l.type||(le=te,te=M),null!==te&&0==(2&l.flags))for(let Ne=1;Ne1)for(let f=a.length-2;f>=0;f--){const m=a[f];NC(m)||XO(m,l)&&null===KO(m)&&qO(m,l.index)}}function NC(a){return!(64&a.type)}function XO(a,l){return NC(l)||a.index>l.index}function KO(a){const l=a.insertBeforeIndex;return Array.isArray(l)?l[0]:l}function qO(a,l){const f=a.insertBeforeIndex;Array.isArray(f)?f[0]=l:(J_(RC,hE),a.insertBeforeIndex=l)}function N1(a,l){const f=a.data[l];return null===f||"string"==typeof f?null:f.hasOwnProperty("currentCaseLViewIndex")?f:f.value}function nw(a,l,f){const m=Jh(a,f,64,null,null);return _E(l,m),m}function by(a,l){const f=l[a.currentCaseLViewIndex];return null===f?f:f<0?~f:f}function PC(a){return a>>>17}function IC(a){return(131070&a)>>>1}let Dv=0,Gm=0;function FC(a,l,f,m){const M=f[11];let te,k=null;for(let le=0;le>>1,f),null,null,nr,Rr,null);break;default:throw new Error(`Unable to determine the type of mutate operation for "${Ne}"`)}else switch(Ne){case yy:const qe=l[++le],wt=l[++le];null===f[wt]&&Wc(f[wt]=_g(M,qe),f);break;case vy:const ln=l[++le],dn=l[++le];null===f[dn]&&Wc(f[dn]=Bh(M,ln,null),f)}}}function kC(a,l,f,m,M){for(let k=0;k>>2;switch(3&wt){case 1:const dn=f[++qe],nr=f[++qe],Rr=a.data[ln];"string"==typeof Rr?d1(l[11],l[ln],null,Rr,dn,Ne,nr):Ro(a,Rr,l,dn,Ne,l[11],nr,!1);break;case 0:const Ti=l[ln];null!==Ti&&ph(l[11],Ti,Ne);break;case 2:sw(a,N1(a,ln),l,Ne);break;case 3:$C(a,N1(a,ln),m,l)}}}}else{const Ne=f[k+1];if(Ne>0&&3==(3&Ne)){const wt=N1(a,Ne>>>2);l[wt.currentCaseLViewIndex]<0&&$C(a,wt,m,l)}}k+=le}}function $C(a,l,f,m){let M=m[l.currentCaseLViewIndex];if(null!==M){let k=Dv;M<0&&(M=m[l.currentCaseLViewIndex]=~M,k=-1),kC(a,m,l.update[M],f,k)}}function sw(a,l,f,m){const M=function(a,l){let f=a.cases.indexOf(l);if(-1===f)switch(a.type){case 1:{const m=function(a,l){const f=wC(l)(parseInt(a,10)),m=zO[f];return void 0!==m?m:"other"}(l,DC);f=a.cases.indexOf(m),-1===f&&"other"!==m&&(f=a.cases.indexOf("other"));break}case 0:f=a.cases.indexOf("other")}return-1===f?null:f}(l,m);if(by(l,f)!==M&&(HC(a,l,f),f[l.currentCaseLViewIndex]=null===M?null:~M,null!==M)){const te=f[l.anchorIdx];te&&FC(a,l.create[M],f,te)}}function HC(a,l,f){let m=by(l,f);if(null!==m){const M=l.remove[m];for(let k=0;k0){const le=ur(te,f);null!==le&&y_(f[11],le)}else HC(a,N1(a,~te),f)}}}function aw(){const a=[];let f,m,l=-1;function k(le,Ne){l=0;const qe=by(le,Ne);m=null!==qe?le.remove[qe]:Jt}function te(){if(l0?f[le]:(a.push(l,m),k(f[1].data[~le],f),te())}return 0===a.length?null:(m=a.pop(),l=a.pop(),te())}return function(le,Ne){for(f=Ne;a.length;)a.pop();return k(le.value,Ne),te}}const Ey=/\ufffd(\d+):?\d*\ufffd/gi,lw=/({\s*\ufffd\d+:?\d*\ufffd\s*,\s*\S{6}\s*,[\s\S]*})/gi,uw=/\ufffd(\d+)\ufffd/,YC=/^\s*(\ufffd\d+:?\d*\ufffd)\s*,\s*(select|plural)\s*,/,cw=/\ufffd\/?\*(\d+:\d+)\ufffd/gi,dw=/\ufffd(\/?[#*]\d+):?\d*\ufffd/gi,ZC=/\uE500/g;function jC(a,l,f,m,M,k,te){const le=Vp(a,m,1,null);let Ne=le<f.length&&f.push(Ne)}return{type:m,mainBinding:M,cases:l,values:f}}function Vm(a){if(!a)return[];let l=0;const f=[],m=[],M=/[{}]/g;let k;for(M.lastIndex=0;k=M.exec(a);){const le=k.index;if("}"==k[0]){if(f.pop(),0==f.length){const Ne=a.substring(l,le);YC.test(Ne)?m.push(yw(Ne)):m.push(Ne),l=le+1}}else{if(0==f.length){const Ne=a.substring(l,le);m.push(Ne),l=le+1}f.push("{")}}const te=a.substring(l);return m.push(te),m}function bw(a,l,f,m,M,k,te,le){const Ne=[],qe=[],wt=[];l.cases.push(k),l.create.push(Ne),l.remove.push(qe),l.update.push(wt);const dn=rh(Su()).getInertBodyElement(te),nr=Rs(dn)||dn;return nr?vE(a,l,f,m,Ne,qe,wt,nr,M,le,0):0}function vE(a,l,f,m,M,k,te,le,Ne,qe,wt){let ln=0,dn=le.firstChild;for(;dn;){const nr=Vp(a,f,1,null);switch(dn.nodeType){case Node.ELEMENT_NODE:const Rr=dn,Ti=Rr.tagName.toLowerCase();if(Y.hasOwnProperty(Ti)){yE(M,vy,Ti,Ne,nr),a.data[nr]=Ti;const ua=Rr.attributes;for(let Xa=0;Xa>>Ad.SHIFT;let ln=a[wt];null===ln&&(ln=a[wt]=Ne?M.createComment(le):Rf(M,le)),qe&&null!==f&&_h(M,f,ln,m,!1)}})(M,Ne.create,wt,le&&8&le.type?M[le.index]:null),bl(!0)}function EE(){bl(!1)}function QC(a,l,f){Ty(a,l,f),EE()}function XC(a,l){const f=sa();!function(a,l,f){const M=Ss().index,k=[];if(a.firstCreatePass&&null===a.data[l]){for(let te=0;te0){const m=a.data[f];kC(a,l,Array.isArray(m)?m:m.update,wr()-Gm-1,Dv)}Dv=0,Gm=0}(sa(),Li(),a+20)}function qC(a,l={}){return function(a,l={}){let f=a;if(Cw.test(a)){const m={},M=[0];f=f.replace(WC,(k,te,le)=>{const Ne=te||le,qe=m[Ne]||[];if(qe.length||(Ne.split("|").forEach(Ti=>{const ii=Ti.match(ww),Oo=ii?parseInt(ii[1],10):0,Xi=Ow.test(Ti);qe.push([Oo,Xi,Ti])}),m[Ne]=qe),!qe.length)throw new Error(`i18n postprocess: unmatched placeholder - ${Ne}`);const wt=M[M.length-1];let ln=0;for(let Ti=0;Til.hasOwnProperty(k)?`${M}${l[k]}${Ne}`:m),f=f.replace(Aw,(m,M)=>l.hasOwnProperty(M)?l[M]:m),f=f.replace(JC,(m,M)=>{if(l.hasOwnProperty(M)){const k=l[M];if(!k.length)throw new Error(`i18n postprocess: unmatched ICU - ${m} with key: ${M}`);return k.shift()}return m})),f}(a,l)}function SE(a,l,f,m,M){if(a=Z(a),Array.isArray(a))for(let k=0;k>20;if(Og(a)||!a.multi){const nr=new Js(Ne,M,D1),Rr=ME(le,l,M?wt:wt+dn,ln);-1===Rr?(ga(ns(qe,te),k,le),CE(k,a,l.length),l.push(le),qe.directiveStart++,qe.directiveEnd++,M&&(qe.providerIndexes+=1048576),f.push(nr),te.push(nr)):(f[Rr]=nr,te[Rr]=nr)}else{const nr=ME(le,l,wt+dn,ln),Rr=ME(le,l,wt,wt+dn),Ti=nr>=0&&f[nr],ii=Rr>=0&&f[Rr];if(M&&!ii||!M&&!Ti){ga(ns(qe,te),k,le);const Oo=function(a,l,f,m,M){const k=new Js(a,f,D1);return k.multi=[],k.index=l,k.componentProviders=0,eM(k,M,m&&!f),k}(M?Nw:Rw,f.length,M,m,Ne);!M&&ii&&(f[Rr].providerFactory=Oo),CE(k,a,l.length,0),l.push(le),qe.directiveStart++,qe.directiveEnd++,M&&(qe.providerIndexes+=1048576),f.push(Oo),te.push(Oo)}else CE(k,a,nr>-1?nr:Rr,eM(f[M?Rr:nr],Ne,!M&&m));!M&&m&&ii&&f[Rr].componentProviders++}}}function CE(a,l,f,m){const M=Og(l);if(M||function(a){return!!a.useClass}(l)){const te=(l.useClass||l).prototype.ngOnDestroy;if(te){const le=a.destroyHooks||(a.destroyHooks=[]);if(!M&&l.multi){const Ne=le.indexOf(f);-1===Ne?le.push(f,[m,te]):le[Ne+1].push(m,te)}else le.push(f,te)}}}function eM(a,l,f){return f&&a.componentProviders++,a.multi.push(l)-1}function ME(a,l,f,m){for(let M=f;M{f.providersResolver=(m,M)=>function(a,l,f){const m=sa();if(m.firstCreatePass){const M=Ya(a);SE(f,m.data,m.blueprint,M,!0),SE(l,m.data,m.blueprint,M,!1)}}(m,M?M(a):a,l)}}class nM{}class rM{}const Sy="ngComponent";class Iw{resolveComponentFactory(l){throw function(a){const l=Error(`No component factory found for ${S(a)}. Did you add it to @NgModule.entryComponents?`);return l[Sy]=a,l}(l)}}let Pv=(()=>{class a{}return a.NULL=new Iw,a})();function I1(...a){}function Iv(a,l){return new og(Qi(a,l))}const Fw=function(){return Iv(Ss(),Li())};let og=(()=>{class a{constructor(f){this.nativeElement=f}}return a.__NG_ELEMENT_ID__=Fw,a})();function sM(a){return a instanceof og?a.nativeElement:a}class Cy{}let kw=(()=>{class a{}return a.__NG_ELEMENT_ID__=()=>Hw(),a})();const Hw=function(){const a=Li(),f=Ts(Ss().index,a);return function(a){return a[11]}(ca(f)?f:a)};let xE=(()=>{class a{}return a.\u0275prov=xt({token:a,providedIn:"root",factory:()=>null}),a})();class aM{constructor(l){this.full=l,this.major=l.split(".")[0],this.minor=l.split(".")[1],this.patch=l.split(".").slice(2).join(".")}}const lM=new aM("12.2.13");class uM{constructor(){}supports(l){return Hm(l)}create(l){return new Zw(l)}}const Yw=(a,l)=>l;class Zw{constructor(l){this.length=0,this._linkedRecords=null,this._unlinkedRecords=null,this._previousItHead=null,this._itHead=null,this._itTail=null,this._additionsHead=null,this._additionsTail=null,this._movesHead=null,this._movesTail=null,this._removalsHead=null,this._removalsTail=null,this._identityChangesHead=null,this._identityChangesTail=null,this._trackByFn=l||Yw}forEachItem(l){let f;for(f=this._itHead;null!==f;f=f._next)l(f)}forEachOperation(l){let f=this._itHead,m=this._removalsHead,M=0,k=null;for(;f||m;){const te=!m||f&&f.currentIndex{te=this._trackByFn(M,le),null!==f&&Object.is(f.trackById,te)?(m&&(f=this._verifyReinsertion(f,le,te,M)),Object.is(f.item,le)||this._addIdentityChange(f,le)):(f=this._mismatch(f,le,te,M),m=!0),f=f._next,M++}),this.length=M;return this._truncate(f),this.collection=l,this.isDirty}get isDirty(){return null!==this._additionsHead||null!==this._movesHead||null!==this._removalsHead||null!==this._identityChangesHead}_reset(){if(this.isDirty){let l;for(l=this._previousItHead=this._itHead;null!==l;l=l._next)l._nextPrevious=l._next;for(l=this._additionsHead;null!==l;l=l._nextAdded)l.previousIndex=l.currentIndex;for(this._additionsHead=this._additionsTail=null,l=this._movesHead;null!==l;l=l._nextMoved)l.previousIndex=l.currentIndex;this._movesHead=this._movesTail=null,this._removalsHead=this._removalsTail=null,this._identityChangesHead=this._identityChangesTail=null}}_mismatch(l,f,m,M){let k;return null===l?k=this._itTail:(k=l._prev,this._remove(l)),null!==(l=null===this._unlinkedRecords?null:this._unlinkedRecords.get(m,null))?(Object.is(l.item,f)||this._addIdentityChange(l,f),this._reinsertAfter(l,k,M)):null!==(l=null===this._linkedRecords?null:this._linkedRecords.get(m,M))?(Object.is(l.item,f)||this._addIdentityChange(l,f),this._moveAfter(l,k,M)):l=this._addAfter(new jw(f,m),k,M),l}_verifyReinsertion(l,f,m,M){let k=null===this._unlinkedRecords?null:this._unlinkedRecords.get(m,null);return null!==k?l=this._reinsertAfter(k,l._prev,M):l.currentIndex!=M&&(l.currentIndex=M,this._addToMoves(l,M)),l}_truncate(l){for(;null!==l;){const f=l._next;this._addToRemovals(this._unlink(l)),l=f}null!==this._unlinkedRecords&&this._unlinkedRecords.clear(),null!==this._additionsTail&&(this._additionsTail._nextAdded=null),null!==this._movesTail&&(this._movesTail._nextMoved=null),null!==this._itTail&&(this._itTail._next=null),null!==this._removalsTail&&(this._removalsTail._nextRemoved=null),null!==this._identityChangesTail&&(this._identityChangesTail._nextIdentityChange=null)}_reinsertAfter(l,f,m){null!==this._unlinkedRecords&&this._unlinkedRecords.remove(l);const M=l._prevRemoved,k=l._nextRemoved;return null===M?this._removalsHead=k:M._nextRemoved=k,null===k?this._removalsTail=M:k._prevRemoved=M,this._insertAfter(l,f,m),this._addToMoves(l,m),l}_moveAfter(l,f,m){return this._unlink(l),this._insertAfter(l,f,m),this._addToMoves(l,m),l}_addAfter(l,f,m){return this._insertAfter(l,f,m),this._additionsTail=null===this._additionsTail?this._additionsHead=l:this._additionsTail._nextAdded=l,l}_insertAfter(l,f,m){const M=null===f?this._itHead:f._next;return l._next=M,l._prev=f,null===M?this._itTail=l:M._prev=l,null===f?this._itHead=l:f._next=l,null===this._linkedRecords&&(this._linkedRecords=new My),this._linkedRecords.put(l),l.currentIndex=m,l}_remove(l){return this._addToRemovals(this._unlink(l))}_unlink(l){null!==this._linkedRecords&&this._linkedRecords.remove(l);const f=l._prev,m=l._next;return null===f?this._itHead=m:f._next=m,null===m?this._itTail=f:m._prev=f,l}_addToMoves(l,f){return l.previousIndex===f||(this._movesTail=null===this._movesTail?this._movesHead=l:this._movesTail._nextMoved=l),l}_addToRemovals(l){return null===this._unlinkedRecords&&(this._unlinkedRecords=new My),this._unlinkedRecords.put(l),l.currentIndex=null,l._nextRemoved=null,null===this._removalsTail?(this._removalsTail=this._removalsHead=l,l._prevRemoved=null):(l._prevRemoved=this._removalsTail,this._removalsTail=this._removalsTail._nextRemoved=l),l}_addIdentityChange(l,f){return l.item=f,this._identityChangesTail=null===this._identityChangesTail?this._identityChangesHead=l:this._identityChangesTail._nextIdentityChange=l,l}}class jw{constructor(l,f){this.item=l,this.trackById=f,this.currentIndex=null,this.previousIndex=null,this._nextPrevious=null,this._prev=null,this._next=null,this._prevDup=null,this._nextDup=null,this._prevRemoved=null,this._nextRemoved=null,this._nextAdded=null,this._nextMoved=null,this._nextIdentityChange=null}}class Gw{constructor(){this._head=null,this._tail=null}add(l){null===this._head?(this._head=this._tail=l,l._nextDup=null,l._prevDup=null):(this._tail._nextDup=l,l._prevDup=this._tail,l._nextDup=null,this._tail=l)}get(l,f){let m;for(m=this._head;null!==m;m=m._nextDup)if((null===f||f<=m.currentIndex)&&Object.is(m.trackById,l))return m;return null}remove(l){const f=l._prevDup,m=l._nextDup;return null===f?this._head=m:f._nextDup=m,null===m?this._tail=f:m._prevDup=f,null===this._head}}class My{constructor(){this.map=new Map}put(l){const f=l.trackById;let m=this.map.get(f);m||(m=new Gw,this.map.set(f,m)),m.add(l)}get(l,f){const M=this.map.get(l);return M?M.get(l,f):null}remove(l){const f=l.trackById;return this.map.get(f).remove(l)&&this.map.delete(f),l}get isEmpty(){return 0===this.map.size}clear(){this.map.clear()}}function cM(a,l,f){const m=a.previousIndex;if(null===m)return m;let M=0;return f&&m{if(f&&f.key===M)this._maybeAddToChanges(f,m),this._appendAfter=f,f=f._next;else{const k=this._getOrCreateRecordForKey(M,m);f=this._insertBeforeOrAppend(f,k)}}),f){f._prev&&(f._prev._next=null),this._removalsHead=f;for(let m=f;null!==m;m=m._nextRemoved)m===this._mapHead&&(this._mapHead=null),this._records.delete(m.key),m._nextRemoved=m._next,m.previousValue=m.currentValue,m.currentValue=null,m._prev=null,m._next=null}return this._changesTail&&(this._changesTail._nextChanged=null),this._additionsTail&&(this._additionsTail._nextAdded=null),this.isDirty}_insertBeforeOrAppend(l,f){if(l){const m=l._prev;return f._next=l,f._prev=m,l._prev=f,m&&(m._next=f),l===this._mapHead&&(this._mapHead=f),this._appendAfter=l,l}return this._appendAfter?(this._appendAfter._next=f,f._prev=this._appendAfter):this._mapHead=f,this._appendAfter=f,null}_getOrCreateRecordForKey(l,f){if(this._records.has(l)){const M=this._records.get(l);this._maybeAddToChanges(M,f);const k=M._prev,te=M._next;return k&&(k._next=te),te&&(te._prev=k),M._next=null,M._prev=null,M}const m=new zw(l);return this._records.set(l,m),m.currentValue=f,this._addToAdditions(m),m}_reset(){if(this.isDirty){let l;for(this._previousMapHead=this._mapHead,l=this._previousMapHead;null!==l;l=l._next)l._nextPrevious=l._next;for(l=this._changesHead;null!==l;l=l._nextChanged)l.previousValue=l.currentValue;for(l=this._additionsHead;null!=l;l=l._nextAdded)l.previousValue=l.currentValue;this._changesHead=this._changesTail=null,this._additionsHead=this._additionsTail=null,this._removalsHead=null}}_maybeAddToChanges(l,f){Object.is(f,l.currentValue)||(l.previousValue=l.currentValue,l.currentValue=f,this._addToChanges(l))}_addToAdditions(l){null===this._additionsHead?this._additionsHead=this._additionsTail=l:(this._additionsTail._nextAdded=l,this._additionsTail=l)}_addToChanges(l){null===this._changesHead?this._changesHead=this._changesTail=l:(this._changesTail._nextChanged=l,this._changesTail=l)}_forEach(l,f){l instanceof Map?l.forEach(f):Object.keys(l).forEach(m=>f(l[m],m))}}class zw{constructor(l){this.key=l,this.previousValue=null,this.currentValue=null,this._nextPrevious=null,this._next=null,this._prev=null,this._nextAdded=null,this._nextRemoved=null,this._nextChanged=null}}function fM(){return new Ay([new uM])}let Ay=(()=>{class a{constructor(f){this.factories=f}static create(f,m){if(null!=m){const M=m.factories.slice();f=f.concat(M)}return new a(f)}static extend(f){return{provide:a,useFactory:m=>a.create(f,m||fM()),deps:[[a,new il,new ia]]}}find(f){const m=this.factories.find(M=>M.supports(f));if(null!=m)return m;throw new Error(`Cannot find a differ supporting object '${f}' of type '${function(a){return a.name||typeof a}(f)}'`)}}return a.\u0275prov=xt({token:a,providedIn:"root",factory:fM}),a})();function pM(){return new Oy([new dM])}let Oy=(()=>{class a{constructor(f){this.factories=f}static create(f,m){if(m){const M=m.factories.slice();f=f.concat(M)}return new a(f)}static extend(f){return{provide:a,useFactory:m=>a.create(f,m||pM()),deps:[[a,new il,new ia]]}}find(f){const m=this.factories.find(M=>M.supports(f));if(m)return m;throw new Error(`Cannot find a differ supporting object '${f}'`)}}return a.\u0275prov=xt({token:a,providedIn:"root",factory:pM}),a})();function wy(a,l,f,m,M=!1){for(;null!==f;){const k=l[f.index];if(null!==k&&m.push(Me(k)),$s(k))for(let le=10;le-1&&(gg(l,m),cl(f,m))}this._attachedToViewContainer=!1}Ep(this._lView[1],this._lView)}onDestroy(l){at(this._lView[1],this._lView,null,l)}markForCheck(){I0(this._cdRefInjectingView||this._lView)}detach(){this._lView[2]&=-129}reattach(){this._lView[2]|=128}detectChanges(){Pm(this._lView[1],this._lView,this.context)}checkNoChanges(){!function(a,l,f){Bo(!0);try{Pm(a,l,f)}finally{Bo(!1)}}(this._lView[1],this._lView,this.context)}attachToViewContainerRef(){if(this._appRef)throw new Error("This view is already attached directly to the ApplicationRef!");this._attachedToViewContainer=!0}detachFromAppRef(){var l;this._appRef=null,jh(this._lView[1],l=this._lView,l[11],2,null,null)}attachToAppRef(l){if(this._attachedToViewContainer)throw new Error("This view is already attached to a ViewContainer!");this._appRef=l}}class Jw extends L1{constructor(l){super(l),this._view=l}detectChanges(){Xh(this._view)}checkNoChanges(){!function(a){Bo(!0);try{Xh(a)}finally{Bo(!1)}}(this._view)}get context(){return null}}const Xw=function(a){return function(a,l,f){if(Il(a)&&!f){const m=Ts(a.index,l);return new L1(m,m)}return 47&a.type?new L1(l[16],l):null}(Ss(),Li(),16==(16&a))};let Kw=(()=>{class a{}return a.__NG_ELEMENT_ID__=Xw,a})();const tx=[new dM],rx=new Ay([new uM]),ix=new Oy(tx),sx=function(){return xy(Ss(),Li())};let F1=(()=>{class a{}return a.__NG_ELEMENT_ID__=sx,a})();const ax=F1,lx=class extends ax{constructor(l,f,m){super(),this._declarationLView=l,this._declarationTContainer=f,this.elementRef=m}createEmbeddedView(l){const f=this._declarationTContainer.tViews,m=Ap(this._declarationLView,f,l,16,null,f.declTNode,null,null,null,null);m[17]=this._declarationLView[this._declarationTContainer.index];const k=this._declarationLView[19];return null!==k&&(m[19]=k.createEmbeddedView(f)),X_(f,m,l),new L1(m)}};function xy(a,l){return 4&a.type?new lx(l,a,Iv(a,l)):null}class zm{}class DE{}const dx=function(){return gM(Ss(),Li())};let Dy=(()=>{class a{}return a.__NG_ELEMENT_ID__=dx,a})();const px=Dy,hM=class extends px{constructor(l,f,m){super(),this._lContainer=l,this._hostTNode=f,this._hostLView=m}get element(){return Iv(this._hostTNode,this._hostLView)}get injector(){return new jc(this._hostTNode,this._hostLView)}get parentInjector(){const l=fc(this._hostTNode,this._hostLView);if(Ye(l)){const f=ot(l,this._hostLView),m=Ie(l);return new jc(f[1].data[m+8],f)}return new jc(null,this._hostLView)}clear(){for(;this.length>0;)this.remove(this.length-1)}get(l){const f=_M(this._lContainer);return null!==f&&f[l]||null}get length(){return this._lContainer.length-10}createEmbeddedView(l,f,m){const M=l.createEmbeddedView(f||{});return this.insert(M,m),M}createComponent(l,f,m,M,k){const te=m||this.parentInjector;if(!k&&null==l.ngModule&&te){const Ne=te.get(zm,null);Ne&&(k=Ne)}const le=l.create(te,M,void 0,k);return this.insert(le.hostView,f),le}insert(l,f){const m=l._lView,M=m[1];if($s(m[3])){const wt=this.indexOf(l);if(-1!==wt)this.detach(wt);else{const ln=m[3],dn=new hM(ln,ln[6],ln[3]);dn.detach(dn.indexOf(l))}}const k=this._adjustIndex(f),te=this._lContainer;!function(a,l,f,m){const M=10+m,k=f.length;m>0&&(f[M-1][4]=l),mHh});class V1 extends rM{constructor(l,f){super(),this.componentDef=l,this.ngModule=f,this.componentType=l.type,this.selector=l.selectors.map(as).join(","),this.ngContentSelectors=l.ngContentSelectors?l.ngContentSelectors:[],this.isBoundToModule=!!f}get inputs(){return GE(this.componentDef.inputs)}get outputs(){return GE(this.componentDef.outputs)}create(l,f,m,M){const k=(M=M||this.ngModule)?function(a,l){return{get:(f,m,M)=>{const k=a.get(f,Uv,M);return k!==Uv||m===Uv?k:l.get(f,m,M)}}}(l,M.injector):l,te=k.get(Cy,dt),le=k.get(xE,null),Ne=te.createRenderer(null,this.componentDef),qe=this.componentDef.selectors[0][0]||"div",wt=m?function(a,l,f){if(rt(a))return a.selectRootElement(l,f===Gn.ShadowDom);let m="string"==typeof l?a.querySelector(l):l;return m.textContent="",m}(Ne,m,this.componentDef.encapsulation):Bh(te.createRenderer(null,this.componentDef),qe,function(a){const l=a.toLowerCase();return"svg"===l?"http://www.w3.org/2000/svg":"math"===l?"http://www.w3.org/1998/MathML/":null}(qe)),ln=this.componentDef.onPush?576:528,dn=function(a,l){return{components:[],scheduler:a||Hh,clean:vS,playerHandler:l||null,flags:0}}(),nr=zg(0,null,null,1,0,null,null,null,null,null),Rr=Ap(null,nr,dn,ln,null,null,te,Ne,le,k);let Ti,ii;un(Rr);try{const Oo=function(a,l,f,m,M,k){const te=f[1];f[20]=a;const Ne=ip(te,20,2,"#host",null),qe=Ne.mergedAttrs=l.hostAttrs;null!==qe&&(vv(Ne,qe,!0),null!==a&&(ic(M,a,qe),null!==Ne.classes&&Gh(M,a,Ne.classes),null!==Ne.styles&&Zg(M,a,Ne.styles)));const wt=m.createRenderer(a,l),ln=Ap(f,_v(l),null,l.onPush?64:16,f[20],Ne,m,wt,k||null,null);return te.firstCreatePass&&(ga(ns(Ne,f),te,l.type),ju(te,Ne),R0(Ne,f.length,1)),h1(f,ln),f[20]=ln}(wt,this.componentDef,Rr,te,Ne);if(wt)if(m)ic(Ne,wt,["ng-version",lM.full]);else{const{attrs:Xi,classes:ua}=function(a){const l=[],f=[];let m=1,M=2;for(;m0&&Gh(Ne,wt,ua.join(" "))}if(ii=ms(nr,20),void 0!==f){const Xi=ii.projection=[];for(let ua=0;uaNe(te,l)),l.contentQueries){const Ne=Ss();l.contentQueries(1,te,Ne.directiveStart)}const le=Ss();return!k.firstCreatePass||null===l.hostBindings&&null===l.hostAttrs||(Cr(le.index),Op(f[1],le,0,le.directiveStart,le.directiveEnd,l),Qc(l,te)),te}(Oo,this.componentDef,Rr,dn,[ny]),X_(nr,Rr,null)}finally{ai()}return new jx(this.componentType,Ti,Iv(ii,Rr),Rr,ii)}}class jx extends nM{constructor(l,f,m,M,k){super(),this.location=m,this._rootLView=M,this._tNode=k,this.instance=f,this.hostView=this.changeDetectorRef=new Jw(M),this.componentType=l}get injector(){return new jc(this._tNode,this._rootLView)}destroy(){this.hostView.destroy()}onDestroy(l){this.hostView.onDestroy(l)}}const Bv=new Map;class zx extends zm{constructor(l,f){super(),this._parent=f,this._bootstrapComponents=[],this.injector=this,this.destroyCbs=[],this.componentFactoryResolver=new Uy(this);const m=jo(l),M=l[Yt]||null;M&&pE(M),this._bootstrapComponents=yf(m.bootstrap),this._r3Injector=Sb(l,f,[{provide:zm,useValue:this},{provide:Pv,useValue:this.componentFactoryResolver}],S(l)),this._r3Injector._resolveInjectorDefTypes(),this.instance=this.get(l)}get(l,f=Ac.THROW_IF_NOT_FOUND,m=Yr.Default){return l===Ac||l===zm||l===Jg?this:this._r3Injector.get(l,f,m)}destroy(){const l=this._r3Injector;!l.destroyed&&l.destroy(),this.destroyCbs.forEach(f=>f()),this.destroyCbs=null}onDestroy(l){this.destroyCbs.push(l)}}class zE extends DE{constructor(l){super(),this.moduleType=l,null!==jo(l)&&function(a){const l=new Set;!function f(m){const M=jo(m,!0),k=M.id;null!==k&&(function(a,l,f){if(l&&l!==f)throw new Error(`Duplicate module registered for ${a} - ${S(l)} vs ${S(l.name)}`)}(k,Bv.get(k),m),Bv.set(k,m));const te=yf(M.imports);for(const le of te)l.has(le)||(l.add(le),f(le))}(a)}(l)}create(l){return new zx(this.moduleType,l)}}function WE(a,l,f){const m=uu()+a,M=Li();return M[m]===po?Kh(M,m,f?l.call(f):l()):Um(M,m)}function JE(a,l,f,m){return QM(Li(),uu(),a,l,f,m)}function z1(a,l,f,m,M){return XM(Li(),uu(),a,l,f,m,M)}function WM(a,l,f,m,M,k){return KM(Li(),uu(),a,l,f,m,M,k)}function e_(a,l,f,m,M,k,te){return function(a,l,f,m,M,k,te,le,Ne){const qe=l+f;return zp(a,qe,M,k,te,le)?Kh(a,qe+4,Ne?m.call(Ne,M,k,te,le):m(M,k,te,le)):W1(a,qe+4)}(Li(),uu(),a,l,f,m,M,k,te)}function JM(a,l,f,m,M,k,te,le){const Ne=uu()+a,qe=Li(),wt=zp(qe,Ne,f,m,M,k);return nf(qe,Ne+4,te)||wt?Kh(qe,Ne+5,le?l.call(le,f,m,M,k,te):l(f,m,M,k,te)):Um(qe,Ne+5)}function W1(a,l){const f=a[l];return f===po?void 0:f}function QM(a,l,f,m,M,k){const te=l+f;return nf(a,te,M)?Kh(a,te+1,k?m.call(k,M):m(M)):W1(a,te+1)}function XM(a,l,f,m,M,k,te){const le=l+f;return A_(a,le,M,k)?Kh(a,le+2,te?m.call(te,M,k):m(M,k)):W1(a,le+2)}function KM(a,l,f,m,M,k,te,le){const Ne=l+f;return Sv(a,Ne,M,k,te)?Kh(a,Ne+3,le?m.call(le,M,k,te):m(M,k,te)):W1(a,Ne+3)}function e2(a,l){const f=sa();let m;const M=a+20;f.firstCreatePass?(m=function(a,l){if(l)for(let f=l.length-1;f>=0;f--){const m=l[f];if(a===m.name)return m}throw new ee("302",`The pipe '${a}' could not be found!`)}(l,f.pipeRegistry),f.data[M]=m,m.onDestroy&&(f.destroyHooks||(f.destroyHooks=[])).push(M,m.onDestroy)):m=f.data[M];const k=m.factory||(m.factory=wo(m.type)),te=Hi(D1);try{const le=qt(!1),Ne=k();return qt(le),function(a,l,f,m){f>=a.data.length&&(a.data[f]=null,a.blueprint[f]=null),l[f]=m}(f,Li(),M,Ne),Ne}finally{Hi(te)}}function t2(a,l,f){const m=a+20,M=Li(),k=hs(M,m);return J1(M,Qm(M,m)?QM(M,uu(),l,k.transform,f,k):k.transform(f))}function bh(a,l,f,m){const M=a+20,k=Li(),te=hs(k,M);return J1(k,Qm(k,M)?XM(k,uu(),l,te.transform,f,m,te):te.transform(f,m))}function n2(a,l,f,m,M){const k=a+20,te=Li(),le=hs(te,k);return J1(te,Qm(te,k)?KM(te,uu(),l,le.transform,f,m,M,le):le.transform(f,m,M))}function Qm(a,l){return a[1].data[l].pure}function J1(a,l){return mh.isWrapped(l)&&(l=mh.unwrap(l),a[wr()]=po),l}function XE(a){return l=>{setTimeout(a,void 0,l)}}const Wp=class extends r.xQ{constructor(l=!1){super(),this.__isAsync=l}emit(l){super.next(l)}subscribe(l,f,m){var M,k,te;let le=l,Ne=f||(()=>null),qe=m;if(l&&"object"==typeof l){const ln=l;le=null===(M=ln.next)||void 0===M?void 0:M.bind(ln),Ne=null===(k=ln.error)||void 0===k?void 0:k.bind(ln),qe=null===(te=ln.complete)||void 0===te?void 0:te.bind(ln)}this.__isAsync&&(Ne=XE(Ne),le&&(le=XE(le)),qe&&(qe=XE(qe)));const wt=super.subscribe({next:le,error:Ne,complete:qe});return l instanceof u.w&&l.add(wt),wt}};function Kx(){return this._results[tg()]()}class Tu{constructor(l=!1){this._emitDistinctChangesOnly=l,this.dirty=!0,this._results=[],this._changesDetected=!1,this._changes=null,this.length=0,this.first=void 0,this.last=void 0;const f=tg(),m=Tu.prototype;m[f]||(m[f]=Kx)}get changes(){return this._changes||(this._changes=new Wp)}get(l){return this._results[l]}map(l){return this._results.map(l)}filter(l){return this._results.filter(l)}find(l){return this._results.find(l)}reduce(l,f){return this._results.reduce(l,f)}forEach(l){this._results.forEach(l)}some(l){return this._results.some(l)}toArray(){return this._results.slice()}toString(){return this._results.toString()}reset(l,f){const m=this;m.dirty=!1;const M=Dc(l);(this._changesDetected=!function(a,l,f){if(a.length!==l.length)return!1;for(let m=0;m0)m.push(te[le/2]);else{const qe=k[le+1],wt=l[-Ne];for(let ln=10;ln({bindingPropertyName:a})),gD=Oc("Output",a=>({bindingPropertyName:a})),yT=new Jl("Application Initializer");let Zv=(()=>{class a{constructor(f){this.appInits=f,this.resolve=I1,this.reject=I1,this.initialized=!1,this.done=!1,this.donePromise=new Promise((m,M)=>{this.resolve=m,this.reject=M})}runInitializers(){if(this.initialized)return;const f=[],m=()=>{this.done=!0,this.resolve()};if(this.appInits)for(let M=0;M{k.subscribe({complete:le,error:Ne})});f.push(te)}}Promise.all(f).then(()=>{m()}).catch(M=>{this.reject(M)}),0===f.length&&m(),this.initialized=!0}}return a.\u0275fac=function(f){return new(f||a)(xn(yT,8))},a.\u0275prov=xt({token:a,factory:a.\u0275fac}),a})();const N2=new Jl("AppId"),I2={provide:N2,useFactory:function(){return`${jy()}${jy()}${jy()}`},deps:[]};function jy(){return String.fromCharCode(97+Math.floor(25*Math.random()))}const D_=new Jl("Platform Initializer"),cm=new Jl("Platform ID"),Pp=new Jl("appBootstrapListener");let L2=(()=>{class a{log(f){console.log(f)}warn(f){console.warn(f)}}return a.\u0275fac=function(f){return new(f||a)},a.\u0275prov=xt({token:a,factory:a.\u0275fac}),a})();const Gy=new Jl("LocaleId"),F2=new Jl("DefaultCurrencyCode");class $2{constructor(l,f){this.ngModuleFactory=l,this.componentFactories=f}}const zy=function(a){return new zE(a)},dm=zy,ET=function(a){return Promise.resolve(zy(a))},t0=function(a){const l=zy(a),m=yf(jo(a).declarations).reduce((M,k)=>{const te=Co(k);return te&&M.push(new V1(te)),M},[]);return new $2(l,m)},TT=t0,MD=function(a){return Promise.resolve(t0(a))};let n0=(()=>{class a{constructor(){this.compileModuleSync=dm,this.compileModuleAsync=ET,this.compileModuleAndAllComponentsSync=TT,this.compileModuleAndAllComponentsAsync=MD}clearCache(){}clearCacheFor(f){}getModuleId(f){}}return a.\u0275fac=function(f){return new(f||a)},a.\u0275prov=xt({token:a,factory:a.\u0275fac}),a})();const OD=(()=>Promise.resolve(0))();function ST(a){"undefined"==typeof Zone?OD.then(()=>{a&&a.apply(null,null)}):Zone.current.scheduleMicroTask("scheduleMicrotask",a)}class Eh{constructor({enableLongStackTrace:l=!1,shouldCoalesceEventChangeDetection:f=!1,shouldCoalesceRunChangeDetection:m=!1}){if(this.hasPendingMacrotasks=!1,this.hasPendingMicrotasks=!1,this.isStable=!0,this.onUnstable=new Wp(!1),this.onMicrotaskEmpty=new Wp(!1),this.onStable=new Wp(!1),this.onError=new Wp(!1),"undefined"==typeof Zone)throw new Error("In this configuration Angular requires Zone.js");Zone.assertZonePatched();const M=this;M._nesting=0,M._outer=M._inner=Zone.current,Zone.TaskTrackingZoneSpec&&(M._inner=M._inner.fork(new Zone.TaskTrackingZoneSpec)),l&&Zone.longStackTraceZoneSpec&&(M._inner=M._inner.fork(Zone.longStackTraceZoneSpec)),M.shouldCoalesceEventChangeDetection=!m&&f,M.shouldCoalesceRunChangeDetection=m,M.lastRequestAnimationFrameId=-1,M.nativeRequestAnimationFrame=function(){let a=Dr.requestAnimationFrame,l=Dr.cancelAnimationFrame;if("undefined"!=typeof Zone&&a&&l){const f=a[Zone.__symbol__("OriginalDelegate")];f&&(a=f);const m=l[Zone.__symbol__("OriginalDelegate")];m&&(l=m)}return{nativeRequestAnimationFrame:a,nativeCancelAnimationFrame:l}}().nativeRequestAnimationFrame,function(a){const l=()=>{!function(a){a.isCheckStableRunning||-1!==a.lastRequestAnimationFrameId||(a.lastRequestAnimationFrameId=a.nativeRequestAnimationFrame.call(Dr,()=>{a.fakeTopEventTask||(a.fakeTopEventTask=Zone.root.scheduleEventTask("fakeTopEventTask",()=>{a.lastRequestAnimationFrameId=-1,MT(a),a.isCheckStableRunning=!0,CT(a),a.isCheckStableRunning=!1},void 0,()=>{},()=>{})),a.fakeTopEventTask.invoke()}),MT(a))}(a)};a._inner=a._inner.fork({name:"angular",properties:{isAngularZone:!0},onInvokeTask:(f,m,M,k,te,le)=>{try{return B2(a),f.invokeTask(M,k,te,le)}finally{(a.shouldCoalesceEventChangeDetection&&"eventTask"===k.type||a.shouldCoalesceRunChangeDetection)&&l(),Y2(a)}},onInvoke:(f,m,M,k,te,le,Ne)=>{try{return B2(a),f.invoke(M,k,te,le,Ne)}finally{a.shouldCoalesceRunChangeDetection&&l(),Y2(a)}},onHasTask:(f,m,M,k)=>{f.hasTask(M,k),m===M&&("microTask"==k.change?(a._hasPendingMicrotasks=k.microTask,MT(a),CT(a)):"macroTask"==k.change&&(a.hasPendingMacrotasks=k.macroTask))},onHandleError:(f,m,M,k)=>(f.handleError(M,k),a.runOutsideAngular(()=>a.onError.emit(k)),!1)})}(M)}static isInAngularZone(){return!0===Zone.current.get("isAngularZone")}static assertInAngularZone(){if(!Eh.isInAngularZone())throw new Error("Expected to be in Angular Zone, but it is not!")}static assertNotInAngularZone(){if(Eh.isInAngularZone())throw new Error("Expected to not be in Angular Zone, but it is!")}run(l,f,m){return this._inner.run(l,f,m)}runTask(l,f,m,M){const k=this._inner,te=k.scheduleEventTask("NgZoneEvent: "+M,l,jv,I1,I1);try{return k.runTask(te,f,m)}finally{k.cancelTask(te)}}runGuarded(l,f,m){return this._inner.runGuarded(l,f,m)}runOutsideAngular(l){return this._outer.run(l)}}const jv={};function CT(a){if(0==a._nesting&&!a.hasPendingMicrotasks&&!a.isStable)try{a._nesting++,a.onMicrotaskEmpty.emit(null)}finally{if(a._nesting--,!a.hasPendingMicrotasks)try{a.runOutsideAngular(()=>a.onStable.emit(null))}finally{a.isStable=!0}}}function MT(a){a.hasPendingMicrotasks=!!(a._hasPendingMicrotasks||(a.shouldCoalesceEventChangeDetection||a.shouldCoalesceRunChangeDetection)&&-1!==a.lastRequestAnimationFrameId)}function B2(a){a._nesting++,a.isStable&&(a.isStable=!1,a.onUnstable.emit(null))}function Y2(a){a._nesting--,CT(a)}class Z2{constructor(){this.hasPendingMicrotasks=!1,this.hasPendingMacrotasks=!1,this.isStable=!0,this.onUnstable=new Wp,this.onMicrotaskEmpty=new Wp,this.onStable=new Wp,this.onError=new Wp}run(l,f,m){return l.apply(f,m)}runGuarded(l,f,m){return l.apply(f,m)}runOutsideAngular(l){return l()}runTask(l,f,m,M){return l.apply(f,m)}}let AT=(()=>{class a{constructor(f){this._ngZone=f,this._pendingCount=0,this._isZoneStable=!0,this._didWork=!1,this._callbacks=[],this.taskTrackingZone=null,this._watchAngularEvents(),f.run(()=>{this.taskTrackingZone="undefined"==typeof Zone?null:Zone.current.get("TaskTrackingZone")})}_watchAngularEvents(){this._ngZone.onUnstable.subscribe({next:()=>{this._didWork=!0,this._isZoneStable=!1}}),this._ngZone.runOutsideAngular(()=>{this._ngZone.onStable.subscribe({next:()=>{Eh.assertNotInAngularZone(),ST(()=>{this._isZoneStable=!0,this._runCallbacksIfReady()})}})})}increasePendingRequestCount(){return this._pendingCount+=1,this._didWork=!0,this._pendingCount}decreasePendingRequestCount(){if(this._pendingCount-=1,this._pendingCount<0)throw new Error("pending async requests below zero");return this._runCallbacksIfReady(),this._pendingCount}isStable(){return this._isZoneStable&&0===this._pendingCount&&!this._ngZone.hasPendingMacrotasks}_runCallbacksIfReady(){if(this.isStable())ST(()=>{for(;0!==this._callbacks.length;){let f=this._callbacks.pop();clearTimeout(f.timeoutId),f.doneCb(this._didWork)}this._didWork=!1});else{let f=this.getPendingTasks();this._callbacks=this._callbacks.filter(m=>!m.updateCb||!m.updateCb(f)||(clearTimeout(m.timeoutId),!1)),this._didWork=!0}}getPendingTasks(){return this.taskTrackingZone?this.taskTrackingZone.macroTasks.map(f=>({source:f.source,creationLocation:f.creationLocation,data:f.data})):[]}addCallback(f,m,M){let k=-1;m&&m>0&&(k=setTimeout(()=>{this._callbacks=this._callbacks.filter(te=>te.timeoutId!==k),f(this._didWork,this.getPendingTasks())},m)),this._callbacks.push({doneCb:f,timeoutId:k,updateCb:M})}whenStable(f,m,M){if(M&&!this.taskTrackingZone)throw new Error('Task tracking zone is required when passing an update callback to whenStable(). Is "zone.js/plugins/task-tracking" loaded?');this.addCallback(f,m,M),this._runCallbacksIfReady()}getPendingRequestCount(){return this._pendingCount}findProviders(f,m,M){return[]}}return a.\u0275fac=function(f){return new(f||a)(xn(Eh))},a.\u0275prov=xt({token:a,factory:a.\u0275fac}),a})(),OT=(()=>{class a{constructor(){this._applications=new Map,Xm.addToWindow(this)}registerApplication(f,m){this._applications.set(f,m)}unregisterApplication(f){this._applications.delete(f)}unregisterAllApplications(){this._applications.clear()}getTestability(f){return this._applications.get(f)||null}getAllTestabilities(){return Array.from(this._applications.values())}getAllRootElements(){return Array.from(this._applications.keys())}findTestabilityInTree(f,m=!0){return Xm.findTestabilityInTree(this,f,m)}}return a.\u0275fac=function(f){return new(f||a)},a.\u0275prov=xt({token:a,factory:a.\u0275fac}),a})();class wT{addToWindow(l){}findTestabilityInTree(l,f,m){return null}}function DD(a){Xm=a}let Xm=new wT,j2=!0,G2=!1;function xT(){return G2=!0,j2}function RD(){if(G2)throw new Error("Cannot enable prod mode after platform setup.");j2=!1}let Th;const z2=new Jl("AllowMultipleToken");class FD{constructor(l,f){this.name=l,this.token=f}}function DT(a,l,f=[]){const m=`Platform: ${l}`,M=new Jl(m);return(k=[])=>{let te=J2();if(!te||te.injector.get(z2,!1))if(a)a(f.concat(k).concat({provide:M,useValue:!0}));else{const le=f.concat(k).concat({provide:M,useValue:!0},{provide:Im,useValue:"platform"});!function(a){if(Th&&!Th.destroyed&&!Th.injector.get(z2,!1))throw new Error("There can be only one platform. Destroy the previous one to create a new one.");Th=a.get(RT);const l=a.get(D_,null);l&&l.forEach(f=>f())}(Ac.create({providers:le,name:m}))}return function(a){const l=J2();if(!l)throw new Error("No platform exists!");if(!l.injector.get(a,null))throw new Error("A platform with a different configuration has been created. Please destroy it first.");return l}(M)}}function J2(){return Th&&!Th.destroyed?Th:null}let RT=(()=>{class a{constructor(f){this._injector=f,this._modules=[],this._destroyListeners=[],this._destroyed=!1}bootstrapModuleFactory(f,m){const le=function(a,l){let f;return f="noop"===a?new Z2:("zone.js"===a?void 0:a)||new Eh({enableLongStackTrace:xT(),shouldCoalesceEventChangeDetection:!!(null==l?void 0:l.ngZoneEventCoalescing),shouldCoalesceRunChangeDetection:!!(null==l?void 0:l.ngZoneRunCoalescing)}),f}(m?m.ngZone:void 0,{ngZoneEventCoalescing:m&&m.ngZoneEventCoalescing||!1,ngZoneRunCoalescing:m&&m.ngZoneRunCoalescing||!1}),Ne=[{provide:Eh,useValue:le}];return le.run(()=>{const qe=Ac.create({providers:Ne,parent:this.injector,name:f.moduleType.name}),wt=f.create(qe),ln=wt.injector.get(Gf,null);if(!ln)throw new Error("No ErrorHandler. Is platform module (BrowserModule) included?");return le.runOutsideAngular(()=>{const dn=le.onError.subscribe({next:nr=>{ln.handleError(nr)}});wt.onDestroy(()=>{NT(this._modules,wt),dn.unsubscribe()})}),function(a,l,f){try{const m=f();return Zm(m)?m.catch(M=>{throw l.runOutsideAngular(()=>a.handleError(M)),M}):m}catch(m){throw l.runOutsideAngular(()=>a.handleError(m)),m}}(ln,le,()=>{const dn=wt.injector.get(Zv);return dn.runInitializers(),dn.donePromise.then(()=>(pE(wt.injector.get(Gy,my)||my),this._moduleDoBootstrap(wt),wt))})})}bootstrapModule(f,m=[]){const M=Q2({},m);return function(a,l,f){const m=new zE(f);return Promise.resolve(m)}(0,0,f).then(k=>this.bootstrapModuleFactory(k,M))}_moduleDoBootstrap(f){const m=f.injector.get(r0);if(f._bootstrapComponents.length>0)f._bootstrapComponents.forEach(M=>m.bootstrap(M));else{if(!f.instance.ngDoBootstrap)throw new Error(`The module ${S(f.instance.constructor)} was bootstrapped, but it does not declare "@NgModule.bootstrap" components nor a "ngDoBootstrap" method. Please define one of these.`);f.instance.ngDoBootstrap(m)}this._modules.push(f)}onDestroy(f){this._destroyListeners.push(f)}get injector(){return this._injector}destroy(){if(this._destroyed)throw new Error("The platform has already been destroyed!");this._modules.slice().forEach(f=>f.destroy()),this._destroyListeners.forEach(f=>f()),this._destroyed=!0}get destroyed(){return this._destroyed}}return a.\u0275fac=function(f){return new(f||a)(xn(Ac))},a.\u0275prov=xt({token:a,factory:a.\u0275fac}),a})();function Q2(a,l){return Array.isArray(l)?l.reduce(Q2,a):Object.assign(Object.assign({},a),l)}let r0=(()=>{class a{constructor(f,m,M,k,te){this._zone=f,this._injector=m,this._exceptionHandler=M,this._componentFactoryResolver=k,this._initStatus=te,this._bootstrapListeners=[],this._views=[],this._runningTick=!1,this._stable=!0,this.componentTypes=[],this.components=[],this._onMicrotaskEmptySubscription=this._zone.onMicrotaskEmpty.subscribe({next:()=>{this._zone.run(()=>{this.tick()})}});const le=new p.y(qe=>{this._stable=this._zone.isStable&&!this._zone.hasPendingMacrotasks&&!this._zone.hasPendingMicrotasks,this._zone.runOutsideAngular(()=>{qe.next(this._stable),qe.complete()})}),Ne=new p.y(qe=>{let wt;this._zone.runOutsideAngular(()=>{wt=this._zone.onStable.subscribe(()=>{Eh.assertNotInAngularZone(),ST(()=>{!this._stable&&!this._zone.hasPendingMacrotasks&&!this._zone.hasPendingMicrotasks&&(this._stable=!0,qe.next(!0))})})});const ln=this._zone.onUnstable.subscribe(()=>{Eh.assertInAngularZone(),this._stable&&(this._stable=!1,this._zone.runOutsideAngular(()=>{qe.next(!1)}))});return()=>{wt.unsubscribe(),ln.unsubscribe()}});this.isStable=(0,d.T)(le,Ne.pipe((0,e.B)()))}bootstrap(f,m){if(!this._initStatus.done)throw new Error("Cannot bootstrap as there are still asynchronous initializers running. Bootstrap components in the `ngDoBootstrap` method of the root module.");let M;M=f instanceof rM?f:this._componentFactoryResolver.resolveComponentFactory(f),this.componentTypes.push(M.componentType);const k=function(a){return a.isBoundToModule}(M)?void 0:this._injector.get(zm),le=M.create(Ac.NULL,[],m||M.selector,k),Ne=le.location.nativeElement,qe=le.injector.get(AT,null),wt=qe&&le.injector.get(OT);return qe&&wt&&wt.registerApplication(Ne,qe),le.onDestroy(()=>{this.detachView(le.hostView),NT(this.components,le),wt&&wt.unregisterApplication(Ne)}),this._loadComponent(le),le}tick(){if(this._runningTick)throw new Error("ApplicationRef.tick is called recursively");try{this._runningTick=!0;for(let f of this._views)f.detectChanges()}catch(f){this._zone.runOutsideAngular(()=>this._exceptionHandler.handleError(f))}finally{this._runningTick=!1}}attachView(f){const m=f;this._views.push(m),m.attachToAppRef(this)}detachView(f){const m=f;NT(this._views,m),m.detachFromAppRef()}_loadComponent(f){this.attachView(f.hostView),this.tick(),this.components.push(f),this._injector.get(Pp,[]).concat(this._bootstrapListeners).forEach(M=>M(f))}ngOnDestroy(){this._views.slice().forEach(f=>f.destroy()),this._onMicrotaskEmptySubscription.unsubscribe()}get viewCount(){return this._views.length}}return a.\u0275fac=function(f){return new(f||a)(xn(Eh),xn(Ac),xn(Gf),xn(Pv),xn(Zv))},a.\u0275prov=xt({token:a,factory:a.\u0275fac}),a})();function NT(a,l){const f=a.indexOf(l);f>-1&&a.splice(f,1)}class UD{}class eA{}const YD={factoryPathPrefix:"",factoryPathSuffix:".ngfactory"};let ZD=(()=>{class a{constructor(f,m){this._compiler=f,this._config=m||YD}load(f){return this.loadAndCompile(f)}loadAndCompile(f){let[m,M]=f.split("#");return void 0===M&&(M="default"),i(98255)(m).then(k=>k[M]).then(k=>Vv(k,m,M)).then(k=>this._compiler.compileModuleAsync(k))}loadFactory(f){let[m,M]=f.split("#"),k="NgFactory";return void 0===M&&(M="default",k=""),i(98255)(this._config.factoryPathPrefix+m+this._config.factoryPathSuffix).then(te=>te[M+k]).then(te=>Vv(te,m,M))}}return a.\u0275fac=function(f){return new(f||a)(xn(n0),xn(eA,8))},a.\u0275prov=xt({token:a,factory:a.\u0275fac}),a})();function Vv(a,l,f){if(!a)throw new Error(`Cannot find '${f}' in '${l}'`);return a}const WD=function(a){return null},JD=DT(null,"core",[{provide:cm,useValue:"unknown"},{provide:RT,deps:[Ac]},{provide:OT,deps:[]},{provide:L2,deps:[]}]),e3=[{provide:r0,useClass:r0,deps:[Eh,Ac,Gf,Pv,Zv]},{provide:Zx,deps:[Eh],useFactory:function(a){let l=[];return a.onStable.subscribe(()=>{for(;l.length;)l.pop()()}),function(f){l.push(f)}}},{provide:Zv,useClass:Zv,deps:[[new ia,yT]]},{provide:n0,useClass:n0,deps:[]},I2,{provide:Ay,useFactory:function(){return rx},deps:[]},{provide:Oy,useFactory:function(){return ix},deps:[]},{provide:Gy,useFactory:function(a){return pE(a=a||"undefined"!=typeof $localize&&$localize.locale||my),a},deps:[[new ha(Gy),new ia,new il]]},{provide:F2,useValue:"USD"}];let $T=(()=>{class a{constructor(f){}}return a.\u0275fac=function(f){return new(f||a)(xn(r0))},a.\u0275mod=Fn({type:a}),a.\u0275inj=Zn({providers:e3}),a})()},24751:(v,T,i)=>{"use strict";i.d(T,{TO:()=>li,Wl:()=>N,gN:()=>Mr,Fj:()=>K,Oe:()=>vr,CE:()=>Ca,qu:()=>Pt,NI:()=>Bn,u:()=>Ws,cw:()=>lr,sg:()=>fo,x0:()=>Ao,u5:()=>Su,Fd:()=>pi,qQ:()=>ts,Cf:()=>ae,JU:()=>S,a5:()=>Kr,JJ:()=>Br,JL:()=>Yr,F:()=>Ci,On:()=>_o,YN:()=>So,wV:()=>Zs,_:()=>us,UX:()=>Zl,Q7:()=>Eo,EJ:()=>ps,kI:()=>Ee,_Y:()=>co,Kr:()=>$e});var r=i(74788),u=i(12057),p=i(94402),d=i(35758),e=i(88002);let _=(()=>{class Te{constructor(xe,Ct){this._renderer=xe,this._elementRef=Ct,this.onChange=ur=>{},this.onTouched=()=>{}}setProperty(xe,Ct){this._renderer.setProperty(this._elementRef.nativeElement,xe,Ct)}registerOnTouched(xe){this.onTouched=xe}registerOnChange(xe){this.onChange=xe}setDisabledState(xe){this.setProperty("disabled",xe)}}return Te.\u0275fac=function(xe){return new(xe||Te)(r.Y36(r.Qsj),r.Y36(r.SBq))},Te.\u0275dir=r.lG2({type:Te}),Te})(),y=(()=>{class Te extends _{}return Te.\u0275fac=function(){let Me;return function(Ct){return(Me||(Me=r.n5z(Te)))(Ct||Te)}}(),Te.\u0275dir=r.lG2({type:Te,features:[r.qOj]}),Te})();const S=new r.OlP("NgValueAccessor"),A={provide:S,useExisting:(0,r.Gpc)(()=>N),multi:!0};let N=(()=>{class Te extends y{writeValue(xe){this.setProperty("checked",xe)}}return Te.\u0275fac=function(){let Me;return function(Ct){return(Me||(Me=r.n5z(Te)))(Ct||Te)}}(),Te.\u0275dir=r.lG2({type:Te,selectors:[["input","type","checkbox","formControlName",""],["input","type","checkbox","formControl",""],["input","type","checkbox","ngModel",""]],hostBindings:function(xe,Ct){1&xe&&r.NdJ("change",function(Qi){return Ct.onChange(Qi.target.checked)})("blur",function(){return Ct.onTouched()})},features:[r._Bn([A]),r.qOj]}),Te})();const L={provide:S,useExisting:(0,r.Gpc)(()=>K),multi:!0},J=new r.OlP("CompositionEventMode");let K=(()=>{class Te extends _{constructor(xe,Ct,ur){super(xe,Ct),this._compositionMode=ur,this._composing=!1,null==this._compositionMode&&(this._compositionMode=!function(){const Te=(0,u.q)()?(0,u.q)().getUserAgent():"";return/android (\d+)/.test(Te.toLowerCase())}())}writeValue(xe){this.setProperty("value",null==xe?"":xe)}_handleInput(xe){(!this._compositionMode||this._compositionMode&&!this._composing)&&this.onChange(xe)}_compositionStart(){this._composing=!0}_compositionEnd(xe){this._composing=!1,this._compositionMode&&this.onChange(xe)}}return Te.\u0275fac=function(xe){return new(xe||Te)(r.Y36(r.Qsj),r.Y36(r.SBq),r.Y36(J,8))},Te.\u0275dir=r.lG2({type:Te,selectors:[["input","formControlName","",3,"type","checkbox"],["textarea","formControlName",""],["input","formControl","",3,"type","checkbox"],["textarea","formControl",""],["input","ngModel","",3,"type","checkbox"],["textarea","ngModel",""],["","ngDefaultControl",""]],hostBindings:function(xe,Ct){1&xe&&r.NdJ("input",function(Qi){return Ct._handleInput(Qi.target.value)})("blur",function(){return Ct.onTouched()})("compositionstart",function(){return Ct._compositionStart()})("compositionend",function(Qi){return Ct._compositionEnd(Qi.target.value)})},features:[r._Bn([L]),r.qOj]}),Te})();function ee(Te){return null==Te||0===Te.length}function ue(Te){return null!=Te&&"number"==typeof Te.length}const ae=new r.OlP("NgValidators"),H=new r.OlP("NgAsyncValidators"),se=/^(?=.{1,254}$)(?=.{1,64}@)[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;class Ee{static min(Me){return ie(Me)}static max(Me){return he(Me)}static required(Me){return ge(Me)}static requiredTrue(Me){return!0===Me.value?null:{required:!0}}static email(Me){return ee((Te=Me).value)||se.test(Te.value)?null:{email:!0};var Te}static minLength(Me){return Te=Me,Me=>ee(Me.value)||!ue(Me.value)?null:Me.value.lengthue(Me.value)&&Me.value.length>Te?{maxlength:{requiredLength:Te,actualLength:Me.value.length}}:null;var Te}static pattern(Me){return function(Te){if(!Te)return Be;let Me,xe;return"string"==typeof Te?(xe="","^"!==Te.charAt(0)&&(xe+="^"),xe+=Te,"$"!==Te.charAt(Te.length-1)&&(xe+="$"),Me=new RegExp(xe)):(xe=Te.toString(),Me=Te),Ct=>{if(ee(Ct.value))return null;const ur=Ct.value;return Me.test(ur)?null:{pattern:{requiredPattern:xe,actualValue:ur}}}}(Me)}static nullValidator(Me){return null}static compose(Me){return It(Me)}static composeAsync(Me){return Ut(Me)}}function ie(Te){return Me=>{if(ee(Me.value)||ee(Te))return null;const xe=parseFloat(Me.value);return!isNaN(xe)&&xe{if(ee(Me.value)||ee(Te))return null;const xe=parseFloat(Me.value);return!isNaN(xe)&&xe>Te?{max:{max:Te,actual:Me.value}}:null}}function ge(Te){return ee(Te.value)?{required:!0}:null}function Be(Te){return null}function Pe(Te){return null!=Te}function je(Te){const Me=(0,r.QGY)(Te)?(0,p.D)(Te):Te;return(0,r.CqO)(Me),Me}function He(Te){let Me={};return Te.forEach(xe=>{Me=null!=xe?Object.assign(Object.assign({},Me),xe):Me}),0===Object.keys(Me).length?null:Me}function Vt(Te,Me){return Me.map(xe=>xe(Te))}function tn(Te){return Te.map(Me=>function(Te){return!Te.validate}(Me)?Me:xe=>Me.validate(xe))}function It(Te){if(!Te)return null;const Me=Te.filter(Pe);return 0==Me.length?null:function(xe){return He(Vt(xe,Me))}}function Zt(Te){return null!=Te?It(tn(Te)):null}function Ut(Te){if(!Te)return null;const Me=Te.filter(Pe);return 0==Me.length?null:function(xe){const Ct=Vt(xe,Me).map(je);return(0,d.D)(Ct).pipe((0,e.U)(He))}}function Bt(Te){return null!=Te?Ut(tn(Te)):null}function bt(Te,Me){return null===Te?[Me]:Array.isArray(Te)?[...Te,Me]:[Te,Me]}function Gt(Te){return Te._rawValidators}function xt(Te){return Te._rawAsyncValidators}function Xt(Te){return Te?Array.isArray(Te)?Te:[Te]:[]}function Zn(Te,Me){return Array.isArray(Te)?Te.includes(Me):Te===Me}function Ur(Te,Me){const xe=Xt(Me);return Xt(Te).forEach(ur=>{Zn(xe,ur)||xe.push(ur)}),xe}function di(Te,Me){return Xt(Me).filter(xe=>!Zn(Te,xe))}let Lr=(()=>{class Te{constructor(){this._rawValidators=[],this._rawAsyncValidators=[],this._onDestroyCallbacks=[]}get value(){return this.control?this.control.value:null}get valid(){return this.control?this.control.valid:null}get invalid(){return this.control?this.control.invalid:null}get pending(){return this.control?this.control.pending:null}get disabled(){return this.control?this.control.disabled:null}get enabled(){return this.control?this.control.enabled:null}get errors(){return this.control?this.control.errors:null}get pristine(){return this.control?this.control.pristine:null}get dirty(){return this.control?this.control.dirty:null}get touched(){return this.control?this.control.touched:null}get status(){return this.control?this.control.status:null}get untouched(){return this.control?this.control.untouched:null}get statusChanges(){return this.control?this.control.statusChanges:null}get valueChanges(){return this.control?this.control.valueChanges:null}get path(){return null}_setValidators(xe){this._rawValidators=xe||[],this._composedValidatorFn=Zt(this._rawValidators)}_setAsyncValidators(xe){this._rawAsyncValidators=xe||[],this._composedAsyncValidatorFn=Bt(this._rawAsyncValidators)}get validator(){return this._composedValidatorFn||null}get asyncValidator(){return this._composedAsyncValidatorFn||null}_registerOnDestroy(xe){this._onDestroyCallbacks.push(xe)}_invokeOnDestroyCallbacks(){this._onDestroyCallbacks.forEach(xe=>xe()),this._onDestroyCallbacks=[]}reset(xe){this.control&&this.control.reset(xe)}hasError(xe,Ct){return!!this.control&&this.control.hasError(xe,Ct)}getError(xe,Ct){return this.control?this.control.getError(xe,Ct):null}}return Te.\u0275fac=function(xe){return new(xe||Te)},Te.\u0275dir=r.lG2({type:Te}),Te})(),Mr=(()=>{class Te extends Lr{get formDirective(){return null}get path(){return null}}return Te.\u0275fac=function(){let Me;return function(Ct){return(Me||(Me=r.n5z(Te)))(Ct||Te)}}(),Te.\u0275dir=r.lG2({type:Te,features:[r.qOj]}),Te})();class Kr extends Lr{constructor(){super(...arguments),this._parent=null,this.name=null,this.valueAccessor=null}}class ei{constructor(Me){this._cd=Me}is(Me){var xe,Ct,ur;return"submitted"===Me?!!(null===(xe=this._cd)||void 0===xe?void 0:xe.submitted):!!(null===(ur=null===(Ct=this._cd)||void 0===Ct?void 0:Ct.control)||void 0===ur?void 0:ur[Me])}}let Br=(()=>{class Te extends ei{constructor(xe){super(xe)}}return Te.\u0275fac=function(xe){return new(xe||Te)(r.Y36(Kr,2))},Te.\u0275dir=r.lG2({type:Te,selectors:[["","formControlName",""],["","ngModel",""],["","formControl",""]],hostVars:14,hostBindings:function(xe,Ct){2&xe&&r.ekj("ng-untouched",Ct.is("untouched"))("ng-touched",Ct.is("touched"))("ng-pristine",Ct.is("pristine"))("ng-dirty",Ct.is("dirty"))("ng-valid",Ct.is("valid"))("ng-invalid",Ct.is("invalid"))("ng-pending",Ct.is("pending"))},features:[r.qOj]}),Te})(),Yr=(()=>{class Te extends ei{constructor(xe){super(xe)}}return Te.\u0275fac=function(xe){return new(xe||Te)(r.Y36(Mr,10))},Te.\u0275dir=r.lG2({type:Te,selectors:[["","formGroupName",""],["","formArrayName",""],["","ngModelGroup",""],["","formGroup",""],["form",3,"ngNoForm",""],["","ngForm",""]],hostVars:16,hostBindings:function(xe,Ct){2&xe&&r.ekj("ng-untouched",Ct.is("untouched"))("ng-touched",Ct.is("touched"))("ng-pristine",Ct.is("pristine"))("ng-dirty",Ct.is("dirty"))("ng-valid",Ct.is("valid"))("ng-invalid",Ct.is("invalid"))("ng-pending",Ct.is("pending"))("ng-submitted",Ct.is("submitted"))},features:[r.qOj]}),Te})();function wi(Te,Me){return[...Me.path,Te]}function br(Te,Me){gr(Te,Me),Me.valueAccessor.writeValue(Te.value),function(Te,Me){Me.valueAccessor.registerOnChange(xe=>{Te._pendingValue=xe,Te._pendingChange=!0,Te._pendingDirty=!0,"change"===Te.updateOn&&Dn(Te,Me)})}(Te,Me),function(Te,Me){const xe=(Ct,ur)=>{Me.valueAccessor.writeValue(Ct),ur&&Me.viewToModelUpdate(Ct)};Te.registerOnChange(xe),Me._registerOnDestroy(()=>{Te._unregisterOnChange(xe)})}(Te,Me),function(Te,Me){Me.valueAccessor.registerOnTouched(()=>{Te._pendingTouched=!0,"blur"===Te.updateOn&&Te._pendingChange&&Dn(Te,Me),"submit"!==Te.updateOn&&Te.markAsTouched()})}(Te,Me),function(Te,Me){if(Me.valueAccessor.setDisabledState){const xe=Ct=>{Me.valueAccessor.setDisabledState(Ct)};Te.registerOnDisabledChange(xe),Me._registerOnDestroy(()=>{Te._unregisterOnDisabledChange(xe)})}}(Te,Me)}function Dr(Te,Me,xe=!0){const Ct=()=>{};Me.valueAccessor&&(Me.valueAccessor.registerOnChange(Ct),Me.valueAccessor.registerOnTouched(Ct)),Jt(Te,Me),Te&&(Me._invokeOnDestroyCallbacks(),Te._registerOnCollectionChange(()=>{}))}function gn(Te,Me){Te.forEach(xe=>{xe.registerOnValidatorChange&&xe.registerOnValidatorChange(Me)})}function gr(Te,Me){const xe=Gt(Te);null!==Me.validator?Te.setValidators(bt(xe,Me.validator)):"function"==typeof xe&&Te.setValidators([xe]);const Ct=xt(Te);null!==Me.asyncValidator?Te.setAsyncValidators(bt(Ct,Me.asyncValidator)):"function"==typeof Ct&&Te.setAsyncValidators([Ct]);const ur=()=>Te.updateValueAndValidity();gn(Me._rawValidators,ur),gn(Me._rawAsyncValidators,ur)}function Jt(Te,Me){let xe=!1;if(null!==Te){if(null!==Me.validator){const ur=Gt(Te);if(Array.isArray(ur)&&ur.length>0){const Qi=ur.filter(Go=>Go!==Me.validator);Qi.length!==ur.length&&(xe=!0,Te.setValidators(Qi))}}if(null!==Me.asyncValidator){const ur=xt(Te);if(Array.isArray(ur)&&ur.length>0){const Qi=ur.filter(Go=>Go!==Me.asyncValidator);Qi.length!==ur.length&&(xe=!0,Te.setAsyncValidators(Qi))}}}const Ct=()=>{};return gn(Me._rawValidators,Ct),gn(Me._rawAsyncValidators,Ct),xe}function Dn(Te,Me){Te._pendingDirty&&Te.markAsDirty(),Te.setValue(Te._pendingValue,{emitModelToViewChange:!1}),Me.viewToModelUpdate(Te._pendingValue),Te._pendingChange=!1}function Yt(Te,Me){gr(Te,Me)}function mi(Te,Me){if(!Te.hasOwnProperty("model"))return!1;const xe=Te.model;return!!xe.isFirstChange()||!Object.is(Me,xe.currentValue)}function Er(Te,Me){Te._syncPendingControls(),Me.forEach(xe=>{const Ct=xe.control;"submit"===Ct.updateOn&&Ct._pendingChange&&(xe.viewToModelUpdate(Ct._pendingValue),Ct._pendingChange=!1)})}function Wr(Te,Me){if(!Me)return null;let xe,Ct,ur;return Array.isArray(Me),Me.forEach(Qi=>{Qi.constructor===K?xe=Qi:function(Te){return Object.getPrototypeOf(Te.constructor)===y}(Qi)?Ct=Qi:ur=Qi}),ur||Ct||xe||null}function dr(Te,Me){const xe=Te.indexOf(Me);xe>-1&&Te.splice(xe,1)}const ar="VALID",Wi="INVALID",lo="PENDING",vo="DISABLED";function Gi(Te){return(Mi(Te)?Te.validators:Te)||null}function os(Te){return Array.isArray(Te)?Zt(Te):Te||null}function jo(Te,Me){return(Mi(Me)?Me.asyncValidators:Te)||null}function To(Te){return Array.isArray(Te)?Bt(Te):Te||null}function Mi(Te){return null!=Te&&!Array.isArray(Te)&&"object"==typeof Te}class li{constructor(Me,xe){this._hasOwnPendingAsyncValidator=!1,this._onCollectionChange=()=>{},this._parent=null,this.pristine=!0,this.touched=!1,this._onDisabledChange=[],this._rawValidators=Me,this._rawAsyncValidators=xe,this._composedValidatorFn=os(this._rawValidators),this._composedAsyncValidatorFn=To(this._rawAsyncValidators)}get validator(){return this._composedValidatorFn}set validator(Me){this._rawValidators=this._composedValidatorFn=Me}get asyncValidator(){return this._composedAsyncValidatorFn}set asyncValidator(Me){this._rawAsyncValidators=this._composedAsyncValidatorFn=Me}get parent(){return this._parent}get valid(){return this.status===ar}get invalid(){return this.status===Wi}get pending(){return this.status==lo}get disabled(){return this.status===vo}get enabled(){return this.status!==vo}get dirty(){return!this.pristine}get untouched(){return!this.touched}get updateOn(){return this._updateOn?this._updateOn:this.parent?this.parent.updateOn:"change"}setValidators(Me){this._rawValidators=Me,this._composedValidatorFn=os(Me)}setAsyncValidators(Me){this._rawAsyncValidators=Me,this._composedAsyncValidatorFn=To(Me)}addValidators(Me){this.setValidators(Ur(Me,this._rawValidators))}addAsyncValidators(Me){this.setAsyncValidators(Ur(Me,this._rawAsyncValidators))}removeValidators(Me){this.setValidators(di(Me,this._rawValidators))}removeAsyncValidators(Me){this.setAsyncValidators(di(Me,this._rawAsyncValidators))}hasValidator(Me){return Zn(this._rawValidators,Me)}hasAsyncValidator(Me){return Zn(this._rawAsyncValidators,Me)}clearValidators(){this.validator=null}clearAsyncValidators(){this.asyncValidator=null}markAsTouched(Me={}){this.touched=!0,this._parent&&!Me.onlySelf&&this._parent.markAsTouched(Me)}markAllAsTouched(){this.markAsTouched({onlySelf:!0}),this._forEachChild(Me=>Me.markAllAsTouched())}markAsUntouched(Me={}){this.touched=!1,this._pendingTouched=!1,this._forEachChild(xe=>{xe.markAsUntouched({onlySelf:!0})}),this._parent&&!Me.onlySelf&&this._parent._updateTouched(Me)}markAsDirty(Me={}){this.pristine=!1,this._parent&&!Me.onlySelf&&this._parent.markAsDirty(Me)}markAsPristine(Me={}){this.pristine=!0,this._pendingDirty=!1,this._forEachChild(xe=>{xe.markAsPristine({onlySelf:!0})}),this._parent&&!Me.onlySelf&&this._parent._updatePristine(Me)}markAsPending(Me={}){this.status=lo,!1!==Me.emitEvent&&this.statusChanges.emit(this.status),this._parent&&!Me.onlySelf&&this._parent.markAsPending(Me)}disable(Me={}){const xe=this._parentMarkedDirty(Me.onlySelf);this.status=vo,this.errors=null,this._forEachChild(Ct=>{Ct.disable(Object.assign(Object.assign({},Me),{onlySelf:!0}))}),this._updateValue(),!1!==Me.emitEvent&&(this.valueChanges.emit(this.value),this.statusChanges.emit(this.status)),this._updateAncestors(Object.assign(Object.assign({},Me),{skipPristineCheck:xe})),this._onDisabledChange.forEach(Ct=>Ct(!0))}enable(Me={}){const xe=this._parentMarkedDirty(Me.onlySelf);this.status=ar,this._forEachChild(Ct=>{Ct.enable(Object.assign(Object.assign({},Me),{onlySelf:!0}))}),this.updateValueAndValidity({onlySelf:!0,emitEvent:Me.emitEvent}),this._updateAncestors(Object.assign(Object.assign({},Me),{skipPristineCheck:xe})),this._onDisabledChange.forEach(Ct=>Ct(!1))}_updateAncestors(Me){this._parent&&!Me.onlySelf&&(this._parent.updateValueAndValidity(Me),Me.skipPristineCheck||this._parent._updatePristine(),this._parent._updateTouched())}setParent(Me){this._parent=Me}updateValueAndValidity(Me={}){this._setInitialStatus(),this._updateValue(),this.enabled&&(this._cancelExistingSubscription(),this.errors=this._runValidator(),this.status=this._calculateStatus(),(this.status===ar||this.status===lo)&&this._runAsyncValidator(Me.emitEvent)),!1!==Me.emitEvent&&(this.valueChanges.emit(this.value),this.statusChanges.emit(this.status)),this._parent&&!Me.onlySelf&&this._parent.updateValueAndValidity(Me)}_updateTreeValidity(Me={emitEvent:!0}){this._forEachChild(xe=>xe._updateTreeValidity(Me)),this.updateValueAndValidity({onlySelf:!0,emitEvent:Me.emitEvent})}_setInitialStatus(){this.status=this._allControlsDisabled()?vo:ar}_runValidator(){return this.validator?this.validator(this):null}_runAsyncValidator(Me){if(this.asyncValidator){this.status=lo,this._hasOwnPendingAsyncValidator=!0;const xe=je(this.asyncValidator(this));this._asyncValidationSubscription=xe.subscribe(Ct=>{this._hasOwnPendingAsyncValidator=!1,this.setErrors(Ct,{emitEvent:Me})})}}_cancelExistingSubscription(){this._asyncValidationSubscription&&(this._asyncValidationSubscription.unsubscribe(),this._hasOwnPendingAsyncValidator=!1)}setErrors(Me,xe={}){this.errors=Me,this._updateControlsErrors(!1!==xe.emitEvent)}get(Me){return function(Te,Me,xe){if(null==Me||(Array.isArray(Me)||(Me=Me.split(".")),Array.isArray(Me)&&0===Me.length))return null;let Ct=Te;return Me.forEach(ur=>{Ct=Ct instanceof lr?Ct.controls.hasOwnProperty(ur)?Ct.controls[ur]:null:Ct instanceof vr&&Ct.at(ur)||null}),Ct}(this,Me)}getError(Me,xe){const Ct=xe?this.get(xe):this;return Ct&&Ct.errors?Ct.errors[Me]:null}hasError(Me,xe){return!!this.getError(Me,xe)}get root(){let Me=this;for(;Me._parent;)Me=Me._parent;return Me}_updateControlsErrors(Me){this.status=this._calculateStatus(),Me&&this.statusChanges.emit(this.status),this._parent&&this._parent._updateControlsErrors(Me)}_initObservables(){this.valueChanges=new r.vpe,this.statusChanges=new r.vpe}_calculateStatus(){return this._allControlsDisabled()?vo:this.errors?Wi:this._hasOwnPendingAsyncValidator||this._anyControlsHaveStatus(lo)?lo:this._anyControlsHaveStatus(Wi)?Wi:ar}_anyControlsHaveStatus(Me){return this._anyControls(xe=>xe.status===Me)}_anyControlsDirty(){return this._anyControls(Me=>Me.dirty)}_anyControlsTouched(){return this._anyControls(Me=>Me.touched)}_updatePristine(Me={}){this.pristine=!this._anyControlsDirty(),this._parent&&!Me.onlySelf&&this._parent._updatePristine(Me)}_updateTouched(Me={}){this.touched=this._anyControlsTouched(),this._parent&&!Me.onlySelf&&this._parent._updateTouched(Me)}_isBoxedValue(Me){return"object"==typeof Me&&null!==Me&&2===Object.keys(Me).length&&"value"in Me&&"disabled"in Me}_registerOnCollectionChange(Me){this._onCollectionChange=Me}_setUpdateStrategy(Me){Mi(Me)&&null!=Me.updateOn&&(this._updateOn=Me.updateOn)}_parentMarkedDirty(Me){return!Me&&!(!this._parent||!this._parent.dirty)&&!this._parent._anyControlsDirty()}}class Bn extends li{constructor(Me=null,xe,Ct){super(Gi(xe),jo(Ct,xe)),this._onChange=[],this._applyFormState(Me),this._setUpdateStrategy(xe),this._initObservables(),this.updateValueAndValidity({onlySelf:!0,emitEvent:!!this.asyncValidator})}setValue(Me,xe={}){this.value=this._pendingValue=Me,this._onChange.length&&!1!==xe.emitModelToViewChange&&this._onChange.forEach(Ct=>Ct(this.value,!1!==xe.emitViewToModelChange)),this.updateValueAndValidity(xe)}patchValue(Me,xe={}){this.setValue(Me,xe)}reset(Me=null,xe={}){this._applyFormState(Me),this.markAsPristine(xe),this.markAsUntouched(xe),this.setValue(this.value,xe),this._pendingChange=!1}_updateValue(){}_anyControls(Me){return!1}_allControlsDisabled(){return this.disabled}registerOnChange(Me){this._onChange.push(Me)}_unregisterOnChange(Me){dr(this._onChange,Me)}registerOnDisabledChange(Me){this._onDisabledChange.push(Me)}_unregisterOnDisabledChange(Me){dr(this._onDisabledChange,Me)}_forEachChild(Me){}_syncPendingControls(){return!("submit"!==this.updateOn||(this._pendingDirty&&this.markAsDirty(),this._pendingTouched&&this.markAsTouched(),!this._pendingChange)||(this.setValue(this._pendingValue,{onlySelf:!0,emitModelToViewChange:!1}),0))}_applyFormState(Me){this._isBoxedValue(Me)?(this.value=this._pendingValue=Me.value,Me.disabled?this.disable({onlySelf:!0,emitEvent:!1}):this.enable({onlySelf:!0,emitEvent:!1})):this.value=this._pendingValue=Me}}class lr extends li{constructor(Me,xe,Ct){super(Gi(xe),jo(Ct,xe)),this.controls=Me,this._initObservables(),this._setUpdateStrategy(xe),this._setUpControls(),this.updateValueAndValidity({onlySelf:!0,emitEvent:!!this.asyncValidator})}registerControl(Me,xe){return this.controls[Me]?this.controls[Me]:(this.controls[Me]=xe,xe.setParent(this),xe._registerOnCollectionChange(this._onCollectionChange),xe)}addControl(Me,xe,Ct={}){this.registerControl(Me,xe),this.updateValueAndValidity({emitEvent:Ct.emitEvent}),this._onCollectionChange()}removeControl(Me,xe={}){this.controls[Me]&&this.controls[Me]._registerOnCollectionChange(()=>{}),delete this.controls[Me],this.updateValueAndValidity({emitEvent:xe.emitEvent}),this._onCollectionChange()}setControl(Me,xe,Ct={}){this.controls[Me]&&this.controls[Me]._registerOnCollectionChange(()=>{}),delete this.controls[Me],xe&&this.registerControl(Me,xe),this.updateValueAndValidity({emitEvent:Ct.emitEvent}),this._onCollectionChange()}contains(Me){return this.controls.hasOwnProperty(Me)&&this.controls[Me].enabled}setValue(Me,xe={}){this._checkAllValuesPresent(Me),Object.keys(Me).forEach(Ct=>{this._throwIfControlMissing(Ct),this.controls[Ct].setValue(Me[Ct],{onlySelf:!0,emitEvent:xe.emitEvent})}),this.updateValueAndValidity(xe)}patchValue(Me,xe={}){null!=Me&&(Object.keys(Me).forEach(Ct=>{this.controls[Ct]&&this.controls[Ct].patchValue(Me[Ct],{onlySelf:!0,emitEvent:xe.emitEvent})}),this.updateValueAndValidity(xe))}reset(Me={},xe={}){this._forEachChild((Ct,ur)=>{Ct.reset(Me[ur],{onlySelf:!0,emitEvent:xe.emitEvent})}),this._updatePristine(xe),this._updateTouched(xe),this.updateValueAndValidity(xe)}getRawValue(){return this._reduceChildren({},(Me,xe,Ct)=>(Me[Ct]=xe instanceof Bn?xe.value:xe.getRawValue(),Me))}_syncPendingControls(){let Me=this._reduceChildren(!1,(xe,Ct)=>!!Ct._syncPendingControls()||xe);return Me&&this.updateValueAndValidity({onlySelf:!0}),Me}_throwIfControlMissing(Me){if(!Object.keys(this.controls).length)throw new Error("\n There are no form controls registered with this group yet. If you're using ngModel,\n you may want to check next tick (e.g. use setTimeout).\n ");if(!this.controls[Me])throw new Error(`Cannot find form control with name: ${Me}.`)}_forEachChild(Me){Object.keys(this.controls).forEach(xe=>{const Ct=this.controls[xe];Ct&&Me(Ct,xe)})}_setUpControls(){this._forEachChild(Me=>{Me.setParent(this),Me._registerOnCollectionChange(this._onCollectionChange)})}_updateValue(){this.value=this._reduceValue()}_anyControls(Me){for(const xe of Object.keys(this.controls)){const Ct=this.controls[xe];if(this.contains(xe)&&Me(Ct))return!0}return!1}_reduceValue(){return this._reduceChildren({},(Me,xe,Ct)=>((xe.enabled||this.disabled)&&(Me[Ct]=xe.value),Me))}_reduceChildren(Me,xe){let Ct=Me;return this._forEachChild((ur,Qi)=>{Ct=xe(Ct,ur,Qi)}),Ct}_allControlsDisabled(){for(const Me of Object.keys(this.controls))if(this.controls[Me].enabled)return!1;return Object.keys(this.controls).length>0||this.disabled}_checkAllValuesPresent(Me){this._forEachChild((xe,Ct)=>{if(void 0===Me[Ct])throw new Error(`Must supply a value for form control with name: '${Ct}'.`)})}}class vr extends li{constructor(Me,xe,Ct){super(Gi(xe),jo(Ct,xe)),this.controls=Me,this._initObservables(),this._setUpdateStrategy(xe),this._setUpControls(),this.updateValueAndValidity({onlySelf:!0,emitEvent:!!this.asyncValidator})}at(Me){return this.controls[Me]}push(Me,xe={}){this.controls.push(Me),this._registerControl(Me),this.updateValueAndValidity({emitEvent:xe.emitEvent}),this._onCollectionChange()}insert(Me,xe,Ct={}){this.controls.splice(Me,0,xe),this._registerControl(xe),this.updateValueAndValidity({emitEvent:Ct.emitEvent})}removeAt(Me,xe={}){this.controls[Me]&&this.controls[Me]._registerOnCollectionChange(()=>{}),this.controls.splice(Me,1),this.updateValueAndValidity({emitEvent:xe.emitEvent})}setControl(Me,xe,Ct={}){this.controls[Me]&&this.controls[Me]._registerOnCollectionChange(()=>{}),this.controls.splice(Me,1),xe&&(this.controls.splice(Me,0,xe),this._registerControl(xe)),this.updateValueAndValidity({emitEvent:Ct.emitEvent}),this._onCollectionChange()}get length(){return this.controls.length}setValue(Me,xe={}){this._checkAllValuesPresent(Me),Me.forEach((Ct,ur)=>{this._throwIfControlMissing(ur),this.at(ur).setValue(Ct,{onlySelf:!0,emitEvent:xe.emitEvent})}),this.updateValueAndValidity(xe)}patchValue(Me,xe={}){null!=Me&&(Me.forEach((Ct,ur)=>{this.at(ur)&&this.at(ur).patchValue(Ct,{onlySelf:!0,emitEvent:xe.emitEvent})}),this.updateValueAndValidity(xe))}reset(Me=[],xe={}){this._forEachChild((Ct,ur)=>{Ct.reset(Me[ur],{onlySelf:!0,emitEvent:xe.emitEvent})}),this._updatePristine(xe),this._updateTouched(xe),this.updateValueAndValidity(xe)}getRawValue(){return this.controls.map(Me=>Me instanceof Bn?Me.value:Me.getRawValue())}clear(Me={}){this.controls.length<1||(this._forEachChild(xe=>xe._registerOnCollectionChange(()=>{})),this.controls.splice(0),this.updateValueAndValidity({emitEvent:Me.emitEvent}))}_syncPendingControls(){let Me=this.controls.reduce((xe,Ct)=>!!Ct._syncPendingControls()||xe,!1);return Me&&this.updateValueAndValidity({onlySelf:!0}),Me}_throwIfControlMissing(Me){if(!this.controls.length)throw new Error("\n There are no form controls registered with this array yet. If you're using ngModel,\n you may want to check next tick (e.g. use setTimeout).\n ");if(!this.at(Me))throw new Error(`Cannot find form control at index ${Me}`)}_forEachChild(Me){this.controls.forEach((xe,Ct)=>{Me(xe,Ct)})}_updateValue(){this.value=this.controls.filter(Me=>Me.enabled||this.disabled).map(Me=>Me.value)}_anyControls(Me){return this.controls.some(xe=>xe.enabled&&Me(xe))}_setUpControls(){this._forEachChild(Me=>this._registerControl(Me))}_checkAllValuesPresent(Me){this._forEachChild((xe,Ct)=>{if(void 0===Me[Ct])throw new Error(`Must supply a value for form control at index: ${Ct}.`)})}_allControlsDisabled(){for(const Me of this.controls)if(Me.enabled)return!1;return this.controls.length>0||this.disabled}_registerControl(Me){Me.setParent(this),Me._registerOnCollectionChange(this._onCollectionChange)}}const er={provide:Mr,useExisting:(0,r.Gpc)(()=>Ci)},uo=(()=>Promise.resolve(null))();let Ci=(()=>{class Te extends Mr{constructor(xe,Ct){super(),this.submitted=!1,this._directives=[],this.ngSubmit=new r.vpe,this.form=new lr({},Zt(xe),Bt(Ct))}ngAfterViewInit(){this._setUpdateStrategy()}get formDirective(){return this}get control(){return this.form}get path(){return[]}get controls(){return this.form.controls}addControl(xe){uo.then(()=>{const Ct=this._findContainer(xe.path);xe.control=Ct.registerControl(xe.name,xe.control),br(xe.control,xe),xe.control.updateValueAndValidity({emitEvent:!1}),this._directives.push(xe)})}getControl(xe){return this.form.get(xe.path)}removeControl(xe){uo.then(()=>{const Ct=this._findContainer(xe.path);Ct&&Ct.removeControl(xe.name),dr(this._directives,xe)})}addFormGroup(xe){uo.then(()=>{const Ct=this._findContainer(xe.path),ur=new lr({});Yt(ur,xe),Ct.registerControl(xe.name,ur),ur.updateValueAndValidity({emitEvent:!1})})}removeFormGroup(xe){uo.then(()=>{const Ct=this._findContainer(xe.path);Ct&&Ct.removeControl(xe.name)})}getFormGroup(xe){return this.form.get(xe.path)}updateModel(xe,Ct){uo.then(()=>{this.form.get(xe.path).setValue(Ct)})}setValue(xe){this.control.setValue(xe)}onSubmit(xe){return this.submitted=!0,Er(this.form,this._directives),this.ngSubmit.emit(xe),!1}onReset(){this.resetForm()}resetForm(xe){this.form.reset(xe),this.submitted=!1}_setUpdateStrategy(){this.options&&null!=this.options.updateOn&&(this.form._updateOn=this.options.updateOn)}_findContainer(xe){return xe.pop(),xe.length?this.form.get(xe):this.form}}return Te.\u0275fac=function(xe){return new(xe||Te)(r.Y36(ae,10),r.Y36(H,10))},Te.\u0275dir=r.lG2({type:Te,selectors:[["form",3,"ngNoForm","",3,"formGroup",""],["ng-form"],["","ngForm",""]],hostBindings:function(xe,Ct){1&xe&&r.NdJ("submit",function(Qi){return Ct.onSubmit(Qi)})("reset",function(){return Ct.onReset()})},inputs:{options:["ngFormOptions","options"]},outputs:{ngSubmit:"ngSubmit"},exportAs:["ngForm"],features:[r._Bn([er]),r.qOj]}),Te})(),$o=(()=>{class Te extends Mr{ngOnInit(){this._checkParentType(),this.formDirective.addFormGroup(this)}ngOnDestroy(){this.formDirective&&this.formDirective.removeFormGroup(this)}get control(){return this.formDirective.getFormGroup(this)}get path(){return wi(null==this.name?this.name:this.name.toString(),this._parent)}get formDirective(){return this._parent?this._parent.formDirective:null}_checkParentType(){}}return Te.\u0275fac=function(){let Me;return function(Ct){return(Me||(Me=r.n5z(Te)))(Ct||Te)}}(),Te.\u0275dir=r.lG2({type:Te,features:[r.qOj]}),Te})();const on={provide:Kr,useExisting:(0,r.Gpc)(()=>_o)},Vi=(()=>Promise.resolve(null))();let _o=(()=>{class Te extends Kr{constructor(xe,Ct,ur,Qi){super(),this.control=new Bn,this._registered=!1,this.update=new r.vpe,this._parent=xe,this._setValidators(Ct),this._setAsyncValidators(ur),this.valueAccessor=Wr(0,Qi)}ngOnChanges(xe){this._checkForErrors(),this._registered||this._setUpControl(),"isDisabled"in xe&&this._updateDisabled(xe),mi(xe,this.viewModel)&&(this._updateValue(this.model),this.viewModel=this.model)}ngOnDestroy(){this.formDirective&&this.formDirective.removeControl(this)}get path(){return this._parent?wi(this.name,this._parent):[this.name]}get formDirective(){return this._parent?this._parent.formDirective:null}viewToModelUpdate(xe){this.viewModel=xe,this.update.emit(xe)}_setUpControl(){this._setUpdateStrategy(),this._isStandalone()?this._setUpStandalone():this.formDirective.addControl(this),this._registered=!0}_setUpdateStrategy(){this.options&&null!=this.options.updateOn&&(this.control._updateOn=this.options.updateOn)}_isStandalone(){return!this._parent||!(!this.options||!this.options.standalone)}_setUpStandalone(){br(this.control,this),this.control.updateValueAndValidity({emitEvent:!1})}_checkForErrors(){this._isStandalone()||this._checkParentType(),this._checkName()}_checkParentType(){}_checkName(){this.options&&this.options.name&&(this.name=this.options.name),this._isStandalone()}_updateValue(xe){Vi.then(()=>{this.control.setValue(xe,{emitViewToModelChange:!1})})}_updateDisabled(xe){const Ct=xe.isDisabled.currentValue,ur=""===Ct||Ct&&"false"!==Ct;Vi.then(()=>{ur&&!this.control.disabled?this.control.disable():!ur&&this.control.disabled&&this.control.enable()})}}return Te.\u0275fac=function(xe){return new(xe||Te)(r.Y36(Mr,9),r.Y36(ae,10),r.Y36(H,10),r.Y36(S,10))},Te.\u0275dir=r.lG2({type:Te,selectors:[["","ngModel","",3,"formControlName","",3,"formControl",""]],inputs:{name:"name",isDisabled:["disabled","isDisabled"],model:["ngModel","model"],options:["ngModelOptions","options"]},outputs:{update:"ngModelChange"},exportAs:["ngModel"],features:[r._Bn([on]),r.qOj,r.TTD]}),Te})(),co=(()=>{class Te{}return Te.\u0275fac=function(xe){return new(xe||Te)},Te.\u0275dir=r.lG2({type:Te,selectors:[["form",3,"ngNoForm","",3,"ngNativeValidate",""]],hostAttrs:["novalidate",""]}),Te})();const Es={provide:S,useExisting:(0,r.Gpc)(()=>Zs),multi:!0};let Zs=(()=>{class Te extends y{writeValue(xe){this.setProperty("value",null==xe?"":xe)}registerOnChange(xe){this.onChange=Ct=>{xe(""==Ct?null:parseFloat(Ct))}}}return Te.\u0275fac=function(){let Me;return function(Ct){return(Me||(Me=r.n5z(Te)))(Ct||Te)}}(),Te.\u0275dir=r.lG2({type:Te,selectors:[["input","type","number","formControlName",""],["input","type","number","formControl",""],["input","type","number","ngModel",""]],hostBindings:function(xe,Ct){1&xe&&r.NdJ("input",function(Qi){return Ct.onChange(Qi.target.value)})("blur",function(){return Ct.onTouched()})},features:[r._Bn([Es]),r.qOj]}),Te})();const ls={provide:S,useExisting:(0,r.Gpc)(()=>us),multi:!0};let ta=(()=>{class Te{}return Te.\u0275fac=function(xe){return new(xe||Te)},Te.\u0275mod=r.oAB({type:Te}),Te.\u0275inj=r.cJS({}),Te})(),Is=(()=>{class Te{constructor(){this._accessors=[]}add(xe,Ct){this._accessors.push([xe,Ct])}remove(xe){for(let Ct=this._accessors.length-1;Ct>=0;--Ct)if(this._accessors[Ct][1]===xe)return void this._accessors.splice(Ct,1)}select(xe){this._accessors.forEach(Ct=>{this._isSameGroup(Ct,xe)&&Ct[1]!==xe&&Ct[1].fireUncheck(xe.value)})}_isSameGroup(xe,Ct){return!!xe[0].control&&xe[0]._parent===Ct._control._parent&&xe[1].name===Ct.name}}return Te.\u0275fac=function(xe){return new(xe||Te)},Te.\u0275prov=(0,r.Yz7)({factory:function(){return new Te},token:Te,providedIn:ta}),Te})(),us=(()=>{class Te extends y{constructor(xe,Ct,ur,Qi){super(xe,Ct),this._registry=ur,this._injector=Qi,this.onChange=()=>{}}ngOnInit(){this._control=this._injector.get(Kr),this._checkName(),this._registry.add(this._control,this)}ngOnDestroy(){this._registry.remove(this)}writeValue(xe){this._state=xe===this.value,this.setProperty("checked",this._state)}registerOnChange(xe){this._fn=xe,this.onChange=()=>{xe(this.value),this._registry.select(this)}}fireUncheck(xe){this.writeValue(xe)}_checkName(){!this.name&&this.formControlName&&(this.name=this.formControlName)}}return Te.\u0275fac=function(xe){return new(xe||Te)(r.Y36(r.Qsj),r.Y36(r.SBq),r.Y36(Is),r.Y36(r.zs3))},Te.\u0275dir=r.lG2({type:Te,selectors:[["input","type","radio","formControlName",""],["input","type","radio","formControl",""],["input","type","radio","ngModel",""]],hostBindings:function(xe,Ct){1&xe&&r.NdJ("change",function(){return Ct.onChange()})("blur",function(){return Ct.onTouched()})},inputs:{name:"name",formControlName:"formControlName",value:"value"},features:[r._Bn([ls]),r.qOj]}),Te})();const ca=new r.OlP("NgModelWithFormControlWarning"),Il={provide:Mr,useExisting:(0,r.Gpc)(()=>fo)};let fo=(()=>{class Te extends Mr{constructor(xe,Ct){super(),this.validators=xe,this.asyncValidators=Ct,this.submitted=!1,this._onCollectionChange=()=>this._updateDomValue(),this.directives=[],this.form=null,this.ngSubmit=new r.vpe,this._setValidators(xe),this._setAsyncValidators(Ct)}ngOnChanges(xe){this._checkFormPresent(),xe.hasOwnProperty("form")&&(this._updateValidators(),this._updateDomValue(),this._updateRegistrations(),this._oldForm=this.form)}ngOnDestroy(){this.form&&(Jt(this.form,this),this.form._onCollectionChange===this._onCollectionChange&&this.form._registerOnCollectionChange(()=>{}))}get formDirective(){return this}get control(){return this.form}get path(){return[]}addControl(xe){const Ct=this.form.get(xe.path);return br(Ct,xe),Ct.updateValueAndValidity({emitEvent:!1}),this.directives.push(xe),Ct}getControl(xe){return this.form.get(xe.path)}removeControl(xe){Dr(xe.control||null,xe,!1),dr(this.directives,xe)}addFormGroup(xe){this._setUpFormContainer(xe)}removeFormGroup(xe){this._cleanUpFormContainer(xe)}getFormGroup(xe){return this.form.get(xe.path)}addFormArray(xe){this._setUpFormContainer(xe)}removeFormArray(xe){this._cleanUpFormContainer(xe)}getFormArray(xe){return this.form.get(xe.path)}updateModel(xe,Ct){this.form.get(xe.path).setValue(Ct)}onSubmit(xe){return this.submitted=!0,Er(this.form,this.directives),this.ngSubmit.emit(xe),!1}onReset(){this.resetForm()}resetForm(xe){this.form.reset(xe),this.submitted=!1}_updateDomValue(){this.directives.forEach(xe=>{const Ct=xe.control,ur=this.form.get(xe.path);Ct!==ur&&(Dr(Ct||null,xe),ur instanceof Bn&&(br(ur,xe),xe.control=ur))}),this.form._updateTreeValidity({emitEvent:!1})}_setUpFormContainer(xe){const Ct=this.form.get(xe.path);Yt(Ct,xe),Ct.updateValueAndValidity({emitEvent:!1})}_cleanUpFormContainer(xe){if(this.form){const Ct=this.form.get(xe.path);Ct&&function(Te,Me){return Jt(Te,Me)}(Ct,xe)&&Ct.updateValueAndValidity({emitEvent:!1})}}_updateRegistrations(){this.form._registerOnCollectionChange(this._onCollectionChange),this._oldForm&&this._oldForm._registerOnCollectionChange(()=>{})}_updateValidators(){gr(this.form,this),this._oldForm&&Jt(this._oldForm,this)}_checkFormPresent(){}}return Te.\u0275fac=function(xe){return new(xe||Te)(r.Y36(ae,10),r.Y36(H,10))},Te.\u0275dir=r.lG2({type:Te,selectors:[["","formGroup",""]],hostBindings:function(xe,Ct){1&xe&&r.NdJ("submit",function(Qi){return Ct.onSubmit(Qi)})("reset",function(){return Ct.onReset()})},inputs:{form:["formGroup","form"]},outputs:{ngSubmit:"ngSubmit"},exportAs:["ngForm"],features:[r._Bn([Il]),r.qOj,r.TTD]}),Te})();const Ya={provide:Mr,useExisting:(0,r.Gpc)(()=>Ao)};let Ao=(()=>{class Te extends $o{constructor(xe,Ct,ur){super(),this._parent=xe,this._setValidators(Ct),this._setAsyncValidators(ur)}_checkParentType(){Ra(this._parent)}}return Te.\u0275fac=function(xe){return new(xe||Te)(r.Y36(Mr,13),r.Y36(ae,10),r.Y36(H,10))},Te.\u0275dir=r.lG2({type:Te,selectors:[["","formGroupName",""]],inputs:{name:["formGroupName","name"]},features:[r._Bn([Ya]),r.qOj]}),Te})();const fs={provide:Mr,useExisting:(0,r.Gpc)(()=>Ca)};let Ca=(()=>{class Te extends Mr{constructor(xe,Ct,ur){super(),this._parent=xe,this._setValidators(Ct),this._setAsyncValidators(ur)}ngOnInit(){this._checkParentType(),this.formDirective.addFormArray(this)}ngOnDestroy(){this.formDirective&&this.formDirective.removeFormArray(this)}get control(){return this.formDirective.getFormArray(this)}get formDirective(){return this._parent?this._parent.formDirective:null}get path(){return wi(null==this.name?this.name:this.name.toString(),this._parent)}_checkParentType(){Ra(this._parent)}}return Te.\u0275fac=function(xe){return new(xe||Te)(r.Y36(Mr,13),r.Y36(ae,10),r.Y36(H,10))},Te.\u0275dir=r.lG2({type:Te,selectors:[["","formArrayName",""]],inputs:{name:["formArrayName","name"]},features:[r._Bn([fs]),r.qOj]}),Te})();function Ra(Te){return!(Te instanceof Ao||Te instanceof fo||Te instanceof Ca)}const pl={provide:Kr,useExisting:(0,r.Gpc)(()=>Ws)};let Ws=(()=>{class Te extends Kr{constructor(xe,Ct,ur,Qi,Go){super(),this._ngModelWarningConfig=Go,this._added=!1,this.update=new r.vpe,this._ngModelWarningSent=!1,this._parent=xe,this._setValidators(Ct),this._setAsyncValidators(ur),this.valueAccessor=Wr(0,Qi)}set isDisabled(xe){}ngOnChanges(xe){this._added||this._setUpControl(),mi(xe,this.viewModel)&&(this.viewModel=this.model,this.formDirective.updateModel(this,this.model))}ngOnDestroy(){this.formDirective&&this.formDirective.removeControl(this)}viewToModelUpdate(xe){this.viewModel=xe,this.update.emit(xe)}get path(){return wi(null==this.name?this.name:this.name.toString(),this._parent)}get formDirective(){return this._parent?this._parent.formDirective:null}_checkParentType(){}_setUpControl(){this._checkParentType(),this.control=this.formDirective.addControl(this),this.control.disabled&&this.valueAccessor.setDisabledState&&this.valueAccessor.setDisabledState(!0),this._added=!0}}return Te.\u0275fac=function(xe){return new(xe||Te)(r.Y36(Mr,13),r.Y36(ae,10),r.Y36(H,10),r.Y36(S,10),r.Y36(ca,8))},Te.\u0275dir=r.lG2({type:Te,selectors:[["","formControlName",""]],inputs:{isDisabled:["disabled","isDisabled"],name:["formControlName","name"],model:["ngModel","model"]},outputs:{update:"ngModelChange"},features:[r._Bn([pl]),r.qOj,r.TTD]}),Te._ngModelWarningSentOnce=!1,Te})();const Po={provide:S,useExisting:(0,r.Gpc)(()=>ps),multi:!0};function bo(Te,Me){return null==Te?`${Me}`:(Me&&"object"==typeof Me&&(Me="Object"),`${Te}: ${Me}`.slice(0,50))}let ps=(()=>{class Te extends y{constructor(){super(...arguments),this._optionMap=new Map,this._idCounter=0,this._compareWith=Object.is}set compareWith(xe){this._compareWith=xe}writeValue(xe){this.value=xe;const Ct=this._getOptionId(xe);null==Ct&&this.setProperty("selectedIndex",-1);const ur=bo(Ct,xe);this.setProperty("value",ur)}registerOnChange(xe){this.onChange=Ct=>{this.value=this._getOptionValue(Ct),xe(this.value)}}_registerOption(){return(this._idCounter++).toString()}_getOptionId(xe){for(const Ct of Array.from(this._optionMap.keys()))if(this._compareWith(this._optionMap.get(Ct),xe))return Ct;return null}_getOptionValue(xe){const Ct=function(Te){return Te.split(":")[0]}(xe);return this._optionMap.has(Ct)?this._optionMap.get(Ct):xe}}return Te.\u0275fac=function(){let Me;return function(Ct){return(Me||(Me=r.n5z(Te)))(Ct||Te)}}(),Te.\u0275dir=r.lG2({type:Te,selectors:[["select","formControlName","",3,"multiple",""],["select","formControl","",3,"multiple",""],["select","ngModel","",3,"multiple",""]],hostBindings:function(xe,Ct){1&xe&&r.NdJ("change",function(Qi){return Ct.onChange(Qi.target.value)})("blur",function(){return Ct.onTouched()})},inputs:{compareWith:"compareWith"},features:[r._Bn([Po]),r.qOj]}),Te})(),So=(()=>{class Te{constructor(xe,Ct,ur){this._element=xe,this._renderer=Ct,this._select=ur,this._select&&(this.id=this._select._registerOption())}set ngValue(xe){null!=this._select&&(this._select._optionMap.set(this.id,xe),this._setElementValue(bo(this.id,xe)),this._select.writeValue(this._select.value))}set value(xe){this._setElementValue(xe),this._select&&this._select.writeValue(this._select.value)}_setElementValue(xe){this._renderer.setProperty(this._element.nativeElement,"value",xe)}ngOnDestroy(){this._select&&(this._select._optionMap.delete(this.id),this._select.writeValue(this._select.value))}}return Te.\u0275fac=function(xe){return new(xe||Te)(r.Y36(r.SBq),r.Y36(r.Qsj),r.Y36(ps,9))},Te.\u0275dir=r.lG2({type:Te,selectors:[["option"]],inputs:{ngValue:"ngValue",value:"value"}}),Te})();const $r={provide:S,useExisting:(0,r.Gpc)(()=>ne),multi:!0};function to(Te,Me){return null==Te?`${Me}`:("string"==typeof Me&&(Me=`'${Me}'`),Me&&"object"==typeof Me&&(Me="Object"),`${Te}: ${Me}`.slice(0,50))}let ne=(()=>{class Te extends y{constructor(){super(...arguments),this._optionMap=new Map,this._idCounter=0,this._compareWith=Object.is}set compareWith(xe){this._compareWith=xe}writeValue(xe){let Ct;if(this.value=xe,Array.isArray(xe)){const ur=xe.map(Qi=>this._getOptionId(Qi));Ct=(Qi,Go)=>{Qi._setSelected(ur.indexOf(Go.toString())>-1)}}else Ct=(ur,Qi)=>{ur._setSelected(!1)};this._optionMap.forEach(Ct)}registerOnChange(xe){this.onChange=Ct=>{const ur=[];if(void 0!==Ct.selectedOptions){const Qi=Ct.selectedOptions;for(let Go=0;Go{class Te{constructor(xe,Ct,ur){this._element=xe,this._renderer=Ct,this._select=ur,this._select&&(this.id=this._select._registerOption(this))}set ngValue(xe){null!=this._select&&(this._value=xe,this._setElementValue(to(this.id,xe)),this._select.writeValue(this._select.value))}set value(xe){this._select?(this._value=xe,this._setElementValue(to(this.id,xe)),this._select.writeValue(this._select.value)):this._setElementValue(xe)}_setElementValue(xe){this._renderer.setProperty(this._element.nativeElement,"value",xe)}_setSelected(xe){this._renderer.setProperty(this._element.nativeElement,"selected",xe)}ngOnDestroy(){this._select&&(this._select._optionMap.delete(this.id),this._select.writeValue(this._select.value))}}return Te.\u0275fac=function(xe){return new(xe||Te)(r.Y36(r.SBq),r.Y36(r.Qsj),r.Y36(ne,9))},Te.\u0275dir=r.lG2({type:Te,selectors:[["option"]],inputs:{ngValue:"ngValue",value:"value"}}),Te})(),an=(()=>{class Te{constructor(){this._validator=Be}handleChanges(xe){if(this.inputName in xe){const Ct=this.normalizeInput(xe[this.inputName].currentValue);this._validator=this.createValidator(Ct),this._onChange&&this._onChange()}}validate(xe){return this._validator(xe)}registerOnValidatorChange(xe){this._onChange=xe}}return Te.\u0275fac=function(xe){return new(xe||Te)},Te.\u0275dir=r.lG2({type:Te}),Te})();const ti={provide:ae,useExisting:(0,r.Gpc)(()=>pi),multi:!0};let pi=(()=>{class Te extends an{constructor(){super(...arguments),this.inputName="max",this.normalizeInput=xe=>parseFloat(xe),this.createValidator=xe=>he(xe)}ngOnChanges(xe){this.handleChanges(xe)}}return Te.\u0275fac=function(){let Me;return function(Ct){return(Me||(Me=r.n5z(Te)))(Ct||Te)}}(),Te.\u0275dir=r.lG2({type:Te,selectors:[["input","type","number","max","","formControlName",""],["input","type","number","max","","formControl",""],["input","type","number","max","","ngModel",""]],hostVars:1,hostBindings:function(xe,Ct){if(2&xe){let ur;r.uIk("max",null!==(ur=Ct.max)&&void 0!==ur?ur:null)}},inputs:{max:"max"},features:[r._Bn([ti]),r.qOj,r.TTD]}),Te})();const xi={provide:ae,useExisting:(0,r.Gpc)(()=>ts),multi:!0};let ts=(()=>{class Te extends an{constructor(){super(...arguments),this.inputName="min",this.normalizeInput=xe=>parseFloat(xe),this.createValidator=xe=>ie(xe)}ngOnChanges(xe){this.handleChanges(xe)}}return Te.\u0275fac=function(){let Me;return function(Ct){return(Me||(Me=r.n5z(Te)))(Ct||Te)}}(),Te.\u0275dir=r.lG2({type:Te,selectors:[["input","type","number","min","","formControlName",""],["input","type","number","min","","formControl",""],["input","type","number","min","","ngModel",""]],hostVars:1,hostBindings:function(xe,Ct){if(2&xe){let ur;r.uIk("min",null!==(ur=Ct.min)&&void 0!==ur?ur:null)}},inputs:{min:"min"},features:[r._Bn([xi]),r.qOj,r.TTD]}),Te})();const wo={provide:ae,useExisting:(0,r.Gpc)(()=>Eo),multi:!0};let Eo=(()=>{class Te{constructor(){this._required=!1}get required(){return this._required}set required(xe){this._required=null!=xe&&!1!==xe&&"false"!=`${xe}`,this._onChange&&this._onChange()}validate(xe){return this.required?ge(xe):null}registerOnValidatorChange(xe){this._onChange=xe}}return Te.\u0275fac=function(xe){return new(xe||Te)},Te.\u0275dir=r.lG2({type:Te,selectors:[["","required","","formControlName","",3,"type","checkbox"],["","required","","formControl","",3,"type","checkbox"],["","required","","ngModel","",3,"type","checkbox"]],hostVars:1,hostBindings:function(xe,Ct){2&xe&&r.uIk("required",Ct.required?"":null)},inputs:{required:"required"},features:[r._Bn([wo])]}),Te})(),tl=(()=>{class Te{}return Te.\u0275fac=function(xe){return new(xe||Te)},Te.\u0275mod=r.oAB({type:Te}),Te.\u0275inj=r.cJS({imports:[[ta]]}),Te})(),Su=(()=>{class Te{}return Te.\u0275fac=function(xe){return new(xe||Te)},Te.\u0275mod=r.oAB({type:Te}),Te.\u0275inj=r.cJS({imports:[tl]}),Te})(),Zl=(()=>{class Te{static withConfig(xe){return{ngModule:Te,providers:[{provide:ca,useValue:xe.warnOnNgModelWithFormControl}]}}}return Te.\u0275fac=function(xe){return new(xe||Te)},Te.\u0275mod=r.oAB({type:Te}),Te.\u0275inj=r.cJS({imports:[tl]}),Te})(),Pt=(()=>{class Te{group(xe,Ct=null){const ur=this._reduceControls(xe);let ms,Qi=null,Go=null;return null!=Ct&&(function(Te){return void 0!==Te.asyncValidators||void 0!==Te.validators||void 0!==Te.updateOn}(Ct)?(Qi=null!=Ct.validators?Ct.validators:null,Go=null!=Ct.asyncValidators?Ct.asyncValidators:null,ms=null!=Ct.updateOn?Ct.updateOn:void 0):(Qi=null!=Ct.validator?Ct.validator:null,Go=null!=Ct.asyncValidator?Ct.asyncValidator:null)),new lr(ur,{asyncValidators:Go,updateOn:ms,validators:Qi})}control(xe,Ct,ur){return new Bn(xe,Ct,ur)}array(xe,Ct,ur){const Qi=xe.map(Go=>this._createControl(Go));return new vr(Qi,Ct,ur)}_reduceControls(xe){const Ct={};return Object.keys(xe).forEach(ur=>{Ct[ur]=this._createControl(xe[ur])}),Ct}_createControl(xe){return xe instanceof Bn||xe instanceof lr||xe instanceof vr?xe:Array.isArray(xe)?this.control(xe[0],xe.length>1?xe[1]:null,xe.length>2?xe[2]:null):this.control(xe)}}return Te.\u0275fac=function(xe){return new(xe||Te)},Te.\u0275prov=(0,r.Yz7)({factory:function(){return new Te},token:Te,providedIn:Zl}),Te})()},91211:(v,T,i)=>{"use strict";i.d(T,{b2:()=>Fn,H7:()=>Vn,HJ:()=>li,q6:()=>Wr,se:()=>di});var r=i(12057),u=i(74788);class p extends r.w_{constructor(){super(...arguments),this.supportsDOMEvents=!0}}class d extends p{static makeCurrent(){(0,r.HT)(new d)}onAndCancel(_t,mt,jt){return _t.addEventListener(mt,jt,!1),()=>{_t.removeEventListener(mt,jt,!1)}}dispatchEvent(_t,mt){_t.dispatchEvent(mt)}remove(_t){_t.parentNode&&_t.parentNode.removeChild(_t)}createElement(_t,mt){return(mt=mt||this.getDefaultDocument()).createElement(_t)}createHtmlDocument(){return document.implementation.createHTMLDocument("fakeTitle")}getDefaultDocument(){return document}isElementNode(_t){return _t.nodeType===Node.ELEMENT_NODE}isShadowRoot(_t){return _t instanceof DocumentFragment}getGlobalEventTarget(_t,mt){return"window"===mt?window:"document"===mt?_t:"body"===mt?_t.body:null}getBaseHref(_t){const mt=(e=e||document.querySelector("base"),e?e.getAttribute("href"):null);return null==mt?null:function(Rt){y=y||document.createElement("a"),y.setAttribute("href",Rt);const _t=y.pathname;return"/"===_t.charAt(0)?_t:`/${_t}`}(mt)}resetBaseElement(){e=null}getUserAgent(){return window.navigator.userAgent}getCookie(_t){return(0,r.Mx)(document.cookie,_t)}}let y,e=null;const A=new u.OlP("TRANSITION_ID"),L=[{provide:u.ip1,useFactory:function(Rt,_t,mt){return()=>{mt.get(u.CZH).donePromise.then(()=>{const jt=(0,r.q)(),on=_t.querySelectorAll(`style[ng-transition="${Rt}"]`);for(let si=0;si{const si=_t.findTestabilityInTree(jt,on);if(null==si)throw new Error("Could not find testability for element.");return si},u.dqk.getAllAngularTestabilities=()=>_t.getAllTestabilities(),u.dqk.getAllAngularRootElements=()=>_t.getAllRootElements(),u.dqk.frameworkStabilizers||(u.dqk.frameworkStabilizers=[]),u.dqk.frameworkStabilizers.push(jt=>{const on=u.dqk.getAllAngularTestabilities();let si=on.length,Vi=!1;const _o=function(co){Vi=Vi||co,si--,0==si&&jt(Vi)};on.forEach(function(co){co.whenStable(_o)})})}findTestabilityInTree(_t,mt,jt){if(null==mt)return null;const on=_t.getTestability(mt);return null!=on?on:jt?(0,r.q)().isShadowRoot(mt)?this.findTestabilityInTree(_t,mt.host,!0):this.findTestabilityInTree(_t,mt.parentElement,!0):null}}let J=(()=>{class Rt{build(){return new XMLHttpRequest}}return Rt.\u0275fac=function(mt){return new(mt||Rt)},Rt.\u0275prov=u.Yz7({token:Rt,factory:Rt.\u0275fac}),Rt})();const Be=new u.OlP("EventManagerPlugins");let Pe=(()=>{class Rt{constructor(mt,jt){this._zone=jt,this._eventNameToPlugin=new Map,mt.forEach(on=>on.manager=this),this._plugins=mt.slice().reverse()}addEventListener(mt,jt,on){return this._findPluginFor(jt).addEventListener(mt,jt,on)}addGlobalEventListener(mt,jt,on){return this._findPluginFor(jt).addGlobalEventListener(mt,jt,on)}getZone(){return this._zone}_findPluginFor(mt){const jt=this._eventNameToPlugin.get(mt);if(jt)return jt;const on=this._plugins;for(let si=0;si{class Rt{constructor(){this._stylesSet=new Set}addStyles(mt){const jt=new Set;mt.forEach(on=>{this._stylesSet.has(on)||(this._stylesSet.add(on),jt.add(on))}),this.onStylesAdded(jt)}onStylesAdded(mt){}getAllStyles(){return Array.from(this._stylesSet)}}return Rt.\u0275fac=function(mt){return new(mt||Rt)},Rt.\u0275prov=u.Yz7({token:Rt,factory:Rt.\u0275fac}),Rt})(),Vt=(()=>{class Rt extends He{constructor(mt){super(),this._doc=mt,this._hostNodes=new Map,this._hostNodes.set(mt.head,[])}_addStylesToHost(mt,jt,on){mt.forEach(si=>{const Vi=this._doc.createElement("style");Vi.textContent=si,on.push(jt.appendChild(Vi))})}addHost(mt){const jt=[];this._addStylesToHost(this._stylesSet,mt,jt),this._hostNodes.set(mt,jt)}removeHost(mt){const jt=this._hostNodes.get(mt);jt&&jt.forEach(it),this._hostNodes.delete(mt)}onStylesAdded(mt){this._hostNodes.forEach((jt,on)=>{this._addStylesToHost(mt,on,jt)})}ngOnDestroy(){this._hostNodes.forEach(mt=>mt.forEach(it))}}return Rt.\u0275fac=function(mt){return new(mt||Rt)(u.LFG(r.K0))},Rt.\u0275prov=u.Yz7({token:Rt,factory:Rt.\u0275fac}),Rt})();function it(Rt){(0,r.q)().remove(Rt)}const tn={svg:"http://www.w3.org/2000/svg",xhtml:"http://www.w3.org/1999/xhtml",xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"},It=/%COMP%/g;function Xt(Rt,_t,mt){for(let jt=0;jt<_t.length;jt++){let on=_t[jt];Array.isArray(on)?Xt(Rt,on,mt):(on=on.replace(It,Rt),mt.push(on))}return mt}function Zn(Rt){return _t=>{if("__ngUnwrap__"===_t)return Rt;!1===Rt(_t)&&(_t.preventDefault(),_t.returnValue=!1)}}let di=(()=>{class Rt{constructor(mt,jt,on){this.eventManager=mt,this.sharedStylesHost=jt,this.appId=on,this.rendererByCompId=new Map,this.defaultRenderer=new Lr(mt)}createRenderer(mt,jt){if(!mt||!jt)return this.defaultRenderer;switch(jt.encapsulation){case u.ifc.Emulated:{let on=this.rendererByCompId.get(jt.id);return on||(on=new Nn(this.eventManager,this.sharedStylesHost,jt,this.appId),this.rendererByCompId.set(jt.id,on)),on.applyToHost(mt),on}case 1:case u.ifc.ShadowDom:return new $n(this.eventManager,this.sharedStylesHost,mt,jt);default:if(!this.rendererByCompId.has(jt.id)){const on=Xt(jt.id,jt.styles,[]);this.sharedStylesHost.addStyles(on),this.rendererByCompId.set(jt.id,this.defaultRenderer)}return this.defaultRenderer}}begin(){}end(){}}return Rt.\u0275fac=function(mt){return new(mt||Rt)(u.LFG(Pe),u.LFG(Vt),u.LFG(u.AFp))},Rt.\u0275prov=u.Yz7({token:Rt,factory:Rt.\u0275fac}),Rt})();class Lr{constructor(_t){this.eventManager=_t,this.data=Object.create(null)}destroy(){}createElement(_t,mt){return mt?document.createElementNS(tn[mt]||mt,_t):document.createElement(_t)}createComment(_t){return document.createComment(_t)}createText(_t){return document.createTextNode(_t)}appendChild(_t,mt){_t.appendChild(mt)}insertBefore(_t,mt,jt){_t&&_t.insertBefore(mt,jt)}removeChild(_t,mt){_t&&_t.removeChild(mt)}selectRootElement(_t,mt){let jt="string"==typeof _t?document.querySelector(_t):_t;if(!jt)throw new Error(`The selector "${_t}" did not match any elements`);return mt||(jt.textContent=""),jt}parentNode(_t){return _t.parentNode}nextSibling(_t){return _t.nextSibling}setAttribute(_t,mt,jt,on){if(on){mt=on+":"+mt;const si=tn[on];si?_t.setAttributeNS(si,mt,jt):_t.setAttribute(mt,jt)}else _t.setAttribute(mt,jt)}removeAttribute(_t,mt,jt){if(jt){const on=tn[jt];on?_t.removeAttributeNS(on,mt):_t.removeAttribute(`${jt}:${mt}`)}else _t.removeAttribute(mt)}addClass(_t,mt){_t.classList.add(mt)}removeClass(_t,mt){_t.classList.remove(mt)}setStyle(_t,mt,jt,on){on&(u.JOm.DashCase|u.JOm.Important)?_t.style.setProperty(mt,jt,on&u.JOm.Important?"important":""):_t.style[mt]=jt}removeStyle(_t,mt,jt){jt&u.JOm.DashCase?_t.style.removeProperty(mt):_t.style[mt]=""}setProperty(_t,mt,jt){_t[mt]=jt}setValue(_t,mt){_t.nodeValue=mt}listen(_t,mt,jt){return"string"==typeof _t?this.eventManager.addGlobalEventListener(_t,mt,Zn(jt)):this.eventManager.addEventListener(_t,mt,Zn(jt))}}class Nn extends Lr{constructor(_t,mt,jt,on){super(_t),this.component=jt;const si=Xt(on+"-"+jt.id,jt.styles,[]);mt.addStyles(si),this.contentAttr="_ngcontent-%COMP%".replace(It,on+"-"+jt.id),this.hostAttr="_nghost-%COMP%".replace(It,on+"-"+jt.id)}applyToHost(_t){super.setAttribute(_t,this.hostAttr,"")}createElement(_t,mt){const jt=super.createElement(_t,mt);return super.setAttribute(jt,this.contentAttr,""),jt}}class $n extends Lr{constructor(_t,mt,jt,on){super(_t),this.sharedStylesHost=mt,this.hostEl=jt,this.shadowRoot=jt.attachShadow({mode:"open"}),this.sharedStylesHost.addHost(this.shadowRoot);const si=Xt(on.id,on.styles,[]);for(let Vi=0;Vi{class Rt extends je{constructor(mt){super(mt)}supports(mt){return!0}addEventListener(mt,jt,on){return mt.addEventListener(jt,on,!1),()=>this.removeEventListener(mt,jt,on)}removeEventListener(mt,jt,on){return mt.removeEventListener(jt,on)}}return Rt.\u0275fac=function(mt){return new(mt||Rt)(u.LFG(r.K0))},Rt.\u0275prov=u.Yz7({token:Rt,factory:Rt.\u0275fac}),Rt})();const Fr=["alt","control","meta","shift"],Jr={"\b":"Backspace","\t":"Tab","\x7f":"Delete","\x1b":"Escape",Del:"Delete",Esc:"Escape",Left:"ArrowLeft",Right:"ArrowRight",Up:"ArrowUp",Down:"ArrowDown",Menu:"ContextMenu",Scroll:"ScrollLock",Win:"OS"},_i={A:"1",B:"2",C:"3",D:"4",E:"5",F:"6",G:"7",H:"8",I:"9",J:"*",K:"+",M:"-",N:".",O:"/","`":"0","\x90":"NumLock"},yn={alt:Rt=>Rt.altKey,control:Rt=>Rt.ctrlKey,meta:Rt=>Rt.metaKey,shift:Rt=>Rt.shiftKey};let gr=(()=>{class Rt extends je{constructor(mt){super(mt)}supports(mt){return null!=Rt.parseEventName(mt)}addEventListener(mt,jt,on){const si=Rt.parseEventName(jt),Vi=Rt.eventCallback(si.fullKey,on,this.manager.getZone());return this.manager.getZone().runOutsideAngular(()=>(0,r.q)().onAndCancel(mt,si.domEventName,Vi))}static parseEventName(mt){const jt=mt.toLowerCase().split("."),on=jt.shift();if(0===jt.length||"keydown"!==on&&"keyup"!==on)return null;const si=Rt._normalizeKey(jt.pop());let Vi="";if(Fr.forEach(co=>{const Es=jt.indexOf(co);Es>-1&&(jt.splice(Es,1),Vi+=co+".")}),Vi+=si,0!=jt.length||0===si.length)return null;const _o={};return _o.domEventName=on,_o.fullKey=Vi,_o}static getEventFullKey(mt){let jt="",on=function(Rt){let _t=Rt.key;if(null==_t){if(_t=Rt.keyIdentifier,null==_t)return"Unidentified";_t.startsWith("U+")&&(_t=String.fromCharCode(parseInt(_t.substring(2),16)),3===Rt.location&&_i.hasOwnProperty(_t)&&(_t=_i[_t]))}return Jr[_t]||_t}(mt);return on=on.toLowerCase()," "===on?on="space":"."===on&&(on="dot"),Fr.forEach(si=>{si!=on&&yn[si](mt)&&(jt+=si+".")}),jt+=on,jt}static eventCallback(mt,jt,on){return si=>{Rt.getEventFullKey(si)===mt&&on.runGuarded(()=>jt(si))}}static _normalizeKey(mt){switch(mt){case"esc":return"escape";default:return mt}}}return Rt.\u0275fac=function(mt){return new(mt||Rt)(u.LFG(r.K0))},Rt.\u0275prov=u.Yz7({token:Rt,factory:Rt.\u0275fac}),Rt})(),Vn=(()=>{class Rt{}return Rt.\u0275fac=function(mt){return new(mt||Rt)},Rt.\u0275prov=(0,u.Yz7)({factory:function(){return(0,u.LFG)(Dn)},token:Rt,providedIn:"root"}),Rt})(),Dn=(()=>{class Rt extends Vn{constructor(mt){super(),this._doc=mt}sanitize(mt,jt){if(null==jt)return null;switch(mt){case u.q3G.NONE:return jt;case u.q3G.HTML:return(0,u.qzn)(jt,"HTML")?(0,u.z3N)(jt):(0,u.EiD)(this._doc,String(jt)).toString();case u.q3G.STYLE:return(0,u.qzn)(jt,"Style")?(0,u.z3N)(jt):jt;case u.q3G.SCRIPT:if((0,u.qzn)(jt,"Script"))return(0,u.z3N)(jt);throw new Error("unsafe value used in a script context");case u.q3G.URL:return(0,u.yhl)(jt),(0,u.qzn)(jt,"URL")?(0,u.z3N)(jt):(0,u.mCW)(String(jt));case u.q3G.RESOURCE_URL:if((0,u.qzn)(jt,"ResourceURL"))return(0,u.z3N)(jt);throw new Error("unsafe value used in a resource URL context (see https://g.co/ng/security#xss)");default:throw new Error(`Unexpected SecurityContext ${mt} (see https://g.co/ng/security#xss)`)}}bypassSecurityTrustHtml(mt){return(0,u.JVY)(mt)}bypassSecurityTrustStyle(mt){return(0,u.L6k)(mt)}bypassSecurityTrustScript(mt){return(0,u.eBb)(mt)}bypassSecurityTrustUrl(mt){return(0,u.LAX)(mt)}bypassSecurityTrustResourceUrl(mt){return(0,u.pB0)(mt)}}return Rt.\u0275fac=function(mt){return new(mt||Rt)(u.LFG(r.K0))},Rt.\u0275prov=(0,u.Yz7)({factory:function(){return function(Rt){return new Dn(Rt.get(r.K0))}((0,u.LFG)(u.gxx))},token:Rt,providedIn:"root"}),Rt})();const Wr=(0,u.eFA)(u._c5,"browser",[{provide:u.Lbi,useValue:r.bD},{provide:u.g9A,useValue:function(){d.makeCurrent(),Z.init()},multi:!0},{provide:r.K0,useFactory:function(){return(0,u.RDi)(document),document},deps:[]}]),dr=[[],{provide:u.zSh,useValue:"root"},{provide:u.qLn,useFactory:function(){return new u.qLn},deps:[]},{provide:Be,useClass:Br,multi:!0,deps:[r.K0,u.R0b,u.Lbi]},{provide:Be,useClass:gr,multi:!0,deps:[r.K0]},[],{provide:di,useClass:di,deps:[Pe,Vt,u.AFp]},{provide:u.FYo,useExisting:di},{provide:He,useExisting:Vt},{provide:Vt,useClass:Vt,deps:[r.K0]},{provide:u.dDg,useClass:u.dDg,deps:[u.R0b]},{provide:Pe,useClass:Pe,deps:[Be,u.R0b]},{provide:r.JF,useClass:J,deps:[]},[]];let Fn=(()=>{class Rt{constructor(mt){if(mt)throw new Error("BrowserModule has already been loaded. If you need access to common directives such as NgIf and NgFor from a lazy loaded module, import CommonModule instead.")}static withServerTransition(mt){return{ngModule:Rt,providers:[{provide:u.AFp,useValue:mt.appId},{provide:A,useExisting:u.AFp},L]}}}return Rt.\u0275fac=function(mt){return new(mt||Rt)(u.LFG(Rt,12))},Rt.\u0275mod=u.oAB({type:Rt}),Rt.\u0275inj=u.cJS({providers:dr,imports:[r.ez,u.hGG]}),Rt})();const Gi="undefined"!=typeof window&&window||{};class os{constructor(_t,mt){this.msPerTick=_t,this.numTicks=mt}}class jo{constructor(_t){this.appRef=_t.injector.get(u.z2F)}timeChangeDetection(_t){const mt=_t&&_t.record,jt="Change Detection",on=null!=Gi.console.profile;mt&&on&&Gi.console.profile(jt);const si=To();let Vi=0;for(;Vi<5||To()-si<500;)this.appRef.tick(),Vi++;const _o=To();mt&&on&&Gi.console.profileEnd(jt);const co=(_o-si)/Vi;return Gi.console.log(`ran ${Vi} change detection cycles`),Gi.console.log(`${co.toFixed(2)} ms per check`),new os(co,Vi)}}function To(){return Gi.performance&&Gi.performance.now?Gi.performance.now():(new Date).getTime()}function li(Rt){return function(Rt,_t){"undefined"!=typeof COMPILED&&COMPILED||((u.dqk.ng=u.dqk.ng||{})[Rt]=_t)}("profiler",new jo(Rt)),Rt}},6283:(v,T,i)=>{"use strict";i.d(T,{gz:()=>tr,m2:()=>He,OD:()=>je,wm:()=>Tl,F0:()=>zr,rH:()=>Io,Od:()=>Cr,yS:()=>Fo,Bz:()=>Js,lC:()=>ao});var r=i(12057),u=i(74788),p=i(18891),d=i(94402),e=i(59193);function _(Ye){return new p.y(Ie=>{let Ce;try{Ce=Ye()}catch(Et){return void Ie.error(Et)}return(Ce?(0,d.D)(Ce):(0,e.c)()).subscribe(Ie)})}var y=i(25917),S=i(26215),A=i(9112),N=i(13410),L=i(58071),Z=i(52441),J=i(79765),K=i(88002),ee=i(43190),ue=i(15257),ae=i(39761),H=i(42145),se=i(45435),Ee=i(5304),ie=i(94612),he=i(12627),ge=i(28049),De=i(19773),ce=i(68307),lt=i(548),Ve=i(51307),ze=i(68939),Be=i(63282);class Pe{constructor(Ie,Ce){this.id=Ie,this.url=Ce}}class je extends Pe{constructor(Ie,Ce,ot="imperative",Et=null){super(Ie,Ce),this.navigationTrigger=ot,this.restoredState=Et}toString(){return`NavigationStart(id: ${this.id}, url: '${this.url}')`}}class He extends Pe{constructor(Ie,Ce,ot){super(Ie,Ce),this.urlAfterRedirects=ot}toString(){return`NavigationEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}')`}}class Vt extends Pe{constructor(Ie,Ce,ot){super(Ie,Ce),this.reason=ot}toString(){return`NavigationCancel(id: ${this.id}, url: '${this.url}')`}}class it extends Pe{constructor(Ie,Ce,ot){super(Ie,Ce),this.error=ot}toString(){return`NavigationError(id: ${this.id}, url: '${this.url}', error: ${this.error})`}}class tn extends Pe{constructor(Ie,Ce,ot,Et){super(Ie,Ce),this.urlAfterRedirects=ot,this.state=Et}toString(){return`RoutesRecognized(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`}}class It extends Pe{constructor(Ie,Ce,ot,Et){super(Ie,Ce),this.urlAfterRedirects=ot,this.state=Et}toString(){return`GuardsCheckStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`}}class Zt extends Pe{constructor(Ie,Ce,ot,Et,qt){super(Ie,Ce),this.urlAfterRedirects=ot,this.state=Et,this.shouldActivate=qt}toString(){return`GuardsCheckEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state}, shouldActivate: ${this.shouldActivate})`}}class Ut extends Pe{constructor(Ie,Ce,ot,Et){super(Ie,Ce),this.urlAfterRedirects=ot,this.state=Et}toString(){return`ResolveStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`}}class Bt extends Pe{constructor(Ie,Ce,ot,Et){super(Ie,Ce),this.urlAfterRedirects=ot,this.state=Et}toString(){return`ResolveEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`}}class bt{constructor(Ie){this.route=Ie}toString(){return`RouteConfigLoadStart(path: ${this.route.path})`}}class Gt{constructor(Ie){this.route=Ie}toString(){return`RouteConfigLoadEnd(path: ${this.route.path})`}}class xt{constructor(Ie){this.snapshot=Ie}toString(){return`ChildActivationStart(path: '${this.snapshot.routeConfig&&this.snapshot.routeConfig.path||""}')`}}class Xt{constructor(Ie){this.snapshot=Ie}toString(){return`ChildActivationEnd(path: '${this.snapshot.routeConfig&&this.snapshot.routeConfig.path||""}')`}}class Zn{constructor(Ie){this.snapshot=Ie}toString(){return`ActivationStart(path: '${this.snapshot.routeConfig&&this.snapshot.routeConfig.path||""}')`}}class Ur{constructor(Ie){this.snapshot=Ie}toString(){return`ActivationEnd(path: '${this.snapshot.routeConfig&&this.snapshot.routeConfig.path||""}')`}}class di{constructor(Ie,Ce,ot){this.routerEvent=Ie,this.position=Ce,this.anchor=ot}toString(){return`Scroll(anchor: '${this.anchor}', position: '${this.position?`${this.position[0]}, ${this.position[1]}`:null}')`}}const Lr="primary";class Mr{constructor(Ie){this.params=Ie||{}}has(Ie){return Object.prototype.hasOwnProperty.call(this.params,Ie)}get(Ie){if(this.has(Ie)){const Ce=this.params[Ie];return Array.isArray(Ce)?Ce[0]:Ce}return null}getAll(Ie){if(this.has(Ie)){const Ce=this.params[Ie];return Array.isArray(Ce)?Ce:[Ce]}return[]}get keys(){return Object.keys(this.params)}}function Kr(Ye){return new Mr(Ye)}const ei="ngNavigationCancelingError";function Nn(Ye){const Ie=Error("NavigationCancelingError: "+Ye);return Ie[ei]=!0,Ie}function Br(Ye,Ie,Ce){const ot=Ce.path.split("/");if(ot.length>Ye.length||"full"===Ce.pathMatch&&(Ie.hasChildren()||ot.lengthot[qt]===Et)}return Ye===Ie}function Hi(Ye){return Array.prototype.concat.apply([],Ye)}function Zr(Ye){return Ye.length>0?Ye[Ye.length-1]:null}function Wt(Ye,Ie){for(const Ce in Ye)Ye.hasOwnProperty(Ce)&&Ie(Ye[Ce],Ce)}function zn(Ye){return(0,u.CqO)(Ye)?Ye:(0,u.QGY)(Ye)?(0,d.D)(Promise.resolve(Ye)):(0,y.of)(Ye)}const Fr={exact:function wi(Ye,Ie,Ce){if(!Dn(Ye.segments,Ie.segments)||!yn(Ye.segments,Ie.segments,Ce)||Ye.numberOfChildren!==Ie.numberOfChildren)return!1;for(const ot in Ie.children)if(!Ye.children[ot]||!wi(Ye.children[ot],Ie.children[ot],Ce))return!1;return!0},subset:Dr},Gn={exact:function(Ye,Ie){return fi(Ye,Ie)},subset:function(Ye,Ie){return Object.keys(Ie).length<=Object.keys(Ye).length&&Object.keys(Ie).every(Ce=>ki(Ye[Ce],Ie[Ce]))},ignored:()=>!0};function Jr(Ye,Ie,Ce){return Fr[Ce.paths](Ye.root,Ie.root,Ce.matrixParams)&&Gn[Ce.queryParams](Ye.queryParams,Ie.queryParams)&&!("exact"===Ce.fragment&&Ye.fragment!==Ie.fragment)}function Dr(Ye,Ie,Ce){return gn(Ye,Ie,Ie.segments,Ce)}function gn(Ye,Ie,Ce,ot){if(Ye.segments.length>Ce.length){const Et=Ye.segments.slice(0,Ce.length);return!(!Dn(Et,Ce)||Ie.hasChildren()||!yn(Et,Ce,ot))}if(Ye.segments.length===Ce.length){if(!Dn(Ye.segments,Ce)||!yn(Ye.segments,Ce,ot))return!1;for(const Et in Ie.children)if(!Ye.children[Et]||!Dr(Ye.children[Et],Ie.children[Et],ot))return!1;return!0}{const Et=Ce.slice(0,Ye.segments.length),qt=Ce.slice(Ye.segments.length);return!!(Dn(Ye.segments,Et)&&yn(Ye.segments,Et,ot)&&Ye.children[Lr])&&gn(Ye.children[Lr],Ie,qt,ot)}}function yn(Ye,Ie,Ce){return Ie.every((ot,Et)=>Gn[Ce](Ye[Et].parameters,ot.parameters))}class gr{constructor(Ie,Ce,ot){this.root=Ie,this.queryParams=Ce,this.fragment=ot}get queryParamMap(){return this._queryParamMap||(this._queryParamMap=Kr(this.queryParams)),this._queryParamMap}toString(){return Ge.serialize(this)}}class Jt{constructor(Ie,Ce){this.segments=Ie,this.children=Ce,this.parent=null,Wt(Ce,(ot,Et)=>ot.parent=this)}hasChildren(){return this.numberOfChildren>0}get numberOfChildren(){return Object.keys(this.children).length}toString(){return kr(this)}}class Vn{constructor(Ie,Ce){this.path=Ie,this.parameters=Ce}get parameterMap(){return this._parameterMap||(this._parameterMap=Kr(this.parameters)),this._parameterMap}toString(){return Wi(this)}}function Dn(Ye,Ie){return Ye.length===Ie.length&&Ye.every((Ce,ot)=>Ce.path===Ie[ot].path)}class Yt{}class _n{parse(Ie){const Ce=new li(Ie);return new gr(Ce.parseRootSegment(),Ce.parseQueryParams(),Ce.parseFragment())}serialize(Ie){var Ye;return`${`/${mi(Ie.root,!0)}`}${function(Ye){const Ie=Object.keys(Ye).map(Ce=>{const ot=Ye[Ce];return Array.isArray(ot)?ot.map(Et=>`${Er(Ce)}=${Er(Et)}`).join("&"):`${Er(Ce)}=${Er(ot)}`}).filter(Ce=>!!Ce);return Ie.length?`?${Ie.join("&")}`:""}(Ie.queryParams)}${"string"==typeof Ie.fragment?`#${Ye=Ie.fragment,encodeURI(Ye)}`:""}`}}const Ge=new _n;function kr(Ye){return Ye.segments.map(Ie=>Wi(Ie)).join("/")}function mi(Ye,Ie){if(!Ye.hasChildren())return kr(Ye);if(Ie){const Ce=Ye.children[Lr]?mi(Ye.children[Lr],!1):"",ot=[];return Wt(Ye.children,(Et,qt)=>{qt!==Lr&&ot.push(`${qt}:${mi(Et,!1)}`)}),ot.length>0?`${Ce}(${ot.join("//")})`:Ce}{const Ce=function(Ye,Ie){let Ce=[];return Wt(Ye.children,(ot,Et)=>{Et===Lr&&(Ce=Ce.concat(Ie(ot,Et)))}),Wt(Ye.children,(ot,Et)=>{Et!==Lr&&(Ce=Ce.concat(Ie(ot,Et)))}),Ce}(Ye,(ot,Et)=>Et===Lr?[mi(Ye.children[Lr],!1)]:[`${Et}:${mi(ot,!1)}`]);return 1===Object.keys(Ye.children).length&&null!=Ye.children[Lr]?`${kr(Ye)}/${Ce[0]}`:`${kr(Ye)}/(${Ce.join("//")})`}}function An(Ye){return encodeURIComponent(Ye).replace(/%40/g,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",")}function Er(Ye){return An(Ye).replace(/%3B/gi,";")}function dr(Ye){return An(Ye).replace(/\(/g,"%28").replace(/\)/g,"%29").replace(/%26/gi,"&")}function Fn(Ye){return decodeURIComponent(Ye)}function ar(Ye){return Fn(Ye.replace(/\+/g,"%20"))}function Wi(Ye){return`${dr(Ye.path)}${function(Ye){return Object.keys(Ye).map(Ie=>`;${dr(Ie)}=${dr(Ye[Ie])}`).join("")}(Ye.parameters)}`}const Co=/^[^\/()?;=#]+/;function Gi(Ye){const Ie=Ye.match(Co);return Ie?Ie[0]:""}const os=/^[^=?&#]+/,To=/^[^?&#]+/;class li{constructor(Ie){this.url=Ie,this.remaining=Ie}parseRootSegment(){return this.consumeOptional("/"),""===this.remaining||this.peekStartsWith("?")||this.peekStartsWith("#")?new Jt([],{}):new Jt([],this.parseChildren())}parseQueryParams(){const Ie={};if(this.consumeOptional("?"))do{this.parseQueryParam(Ie)}while(this.consumeOptional("&"));return Ie}parseFragment(){return this.consumeOptional("#")?decodeURIComponent(this.remaining):null}parseChildren(){if(""===this.remaining)return{};this.consumeOptional("/");const Ie=[];for(this.peekStartsWith("(")||Ie.push(this.parseSegment());this.peekStartsWith("/")&&!this.peekStartsWith("//")&&!this.peekStartsWith("/(");)this.capture("/"),Ie.push(this.parseSegment());let Ce={};this.peekStartsWith("/(")&&(this.capture("/"),Ce=this.parseParens(!0));let ot={};return this.peekStartsWith("(")&&(ot=this.parseParens(!1)),(Ie.length>0||Object.keys(Ce).length>0)&&(ot[Lr]=new Jt(Ie,Ce)),ot}parseSegment(){const Ie=Gi(this.remaining);if(""===Ie&&this.peekStartsWith(";"))throw new Error(`Empty path url segment cannot have parameters: '${this.remaining}'.`);return this.capture(Ie),new Vn(Fn(Ie),this.parseMatrixParams())}parseMatrixParams(){const Ie={};for(;this.consumeOptional(";");)this.parseParam(Ie);return Ie}parseParam(Ie){const Ce=Gi(this.remaining);if(!Ce)return;this.capture(Ce);let ot="";if(this.consumeOptional("=")){const Et=Gi(this.remaining);Et&&(ot=Et,this.capture(ot))}Ie[Fn(Ce)]=Fn(ot)}parseQueryParam(Ie){const Ce=function(Ye){const Ie=Ye.match(os);return Ie?Ie[0]:""}(this.remaining);if(!Ce)return;this.capture(Ce);let ot="";if(this.consumeOptional("=")){const Hn=function(Ye){const Ie=Ye.match(To);return Ie?Ie[0]:""}(this.remaining);Hn&&(ot=Hn,this.capture(ot))}const Et=ar(Ce),qt=ar(ot);if(Ie.hasOwnProperty(Et)){let Hn=Ie[Et];Array.isArray(Hn)||(Hn=[Hn],Ie[Et]=Hn),Hn.push(qt)}else Ie[Et]=qt}parseParens(Ie){const Ce={};for(this.capture("(");!this.consumeOptional(")")&&this.remaining.length>0;){const ot=Gi(this.remaining),Et=this.remaining[ot.length];if("/"!==Et&&")"!==Et&&";"!==Et)throw new Error(`Cannot parse url '${this.url}'`);let qt;ot.indexOf(":")>-1?(qt=ot.substr(0,ot.indexOf(":")),this.capture(qt),this.capture(":")):Ie&&(qt=Lr);const Hn=this.parseChildren();Ce[qt]=1===Object.keys(Hn).length?Hn[Lr]:new Jt([],Hn),this.consumeOptional("//")}return Ce}peekStartsWith(Ie){return this.remaining.startsWith(Ie)}consumeOptional(Ie){return!!this.peekStartsWith(Ie)&&(this.remaining=this.remaining.substring(Ie.length),!0)}capture(Ie){if(!this.consumeOptional(Ie))throw new Error(`Expected "${Ie}".`)}}class Bn{constructor(Ie){this._root=Ie}get root(){return this._root.value}parent(Ie){const Ce=this.pathFromRoot(Ie);return Ce.length>1?Ce[Ce.length-2]:null}children(Ie){const Ce=lr(Ie,this._root);return Ce?Ce.children.map(ot=>ot.value):[]}firstChild(Ie){const Ce=lr(Ie,this._root);return Ce&&Ce.children.length>0?Ce.children[0].value:null}siblings(Ie){const Ce=vr(Ie,this._root);return Ce.length<2?[]:Ce[Ce.length-2].children.map(Et=>Et.value).filter(Et=>Et!==Ie)}pathFromRoot(Ie){return vr(Ie,this._root).map(Ce=>Ce.value)}}function lr(Ye,Ie){if(Ye===Ie.value)return Ie;for(const Ce of Ie.children){const ot=lr(Ye,Ce);if(ot)return ot}return null}function vr(Ye,Ie){if(Ye===Ie.value)return[Ie];for(const Ce of Ie.children){const ot=vr(Ye,Ce);if(ot.length)return ot.unshift(Ie),ot}return[]}class er{constructor(Ie,Ce){this.value=Ie,this.children=Ce}toString(){return`TreeNode(${this.value})`}}function ri(Ye){const Ie={};return Ye&&Ye.children.forEach(Ce=>Ie[Ce.value.outlet]=Ce),Ie}class uo extends Bn{constructor(Ie,Ce){super(Ie),this.snapshot=Ce,jt(this,Ie)}toString(){return this.snapshot.toString()}}function Ci(Ye,Ie){const Ce=function(Ye,Ie){const Hn=new _t([],{},{},"",{},Lr,Ie,null,Ye.root,-1,{});return new mt("",new er(Hn,[]))}(Ye,Ie),ot=new S.X([new Vn("",{})]),Et=new S.X({}),qt=new S.X({}),Hn=new S.X({}),Xn=new S.X(""),Ei=new tr(ot,Et,Hn,Xn,qt,Lr,Ie,Ce.root);return Ei.snapshot=Ce.root,new uo(new er(Ei,[]),Ce)}class tr{constructor(Ie,Ce,ot,Et,qt,Hn,Xn,Ei){this.url=Ie,this.params=Ce,this.queryParams=ot,this.fragment=Et,this.data=qt,this.outlet=Hn,this.component=Xn,this._futureSnapshot=Ei}get routeConfig(){return this._futureSnapshot.routeConfig}get root(){return this._routerState.root}get parent(){return this._routerState.parent(this)}get firstChild(){return this._routerState.firstChild(this)}get children(){return this._routerState.children(this)}get pathFromRoot(){return this._routerState.pathFromRoot(this)}get paramMap(){return this._paramMap||(this._paramMap=this.params.pipe((0,K.U)(Ie=>Kr(Ie)))),this._paramMap}get queryParamMap(){return this._queryParamMap||(this._queryParamMap=this.queryParams.pipe((0,K.U)(Ie=>Kr(Ie)))),this._queryParamMap}toString(){return this.snapshot?this.snapshot.toString():`Future(${this._futureSnapshot})`}}function Ar(Ye,Ie="emptyOnly"){const Ce=Ye.pathFromRoot;let ot=0;if("always"!==Ie)for(ot=Ce.length-1;ot>=1;){const Et=Ce[ot],qt=Ce[ot-1];if(Et.routeConfig&&""===Et.routeConfig.path)ot--;else{if(qt.component)break;ot--}}return function(Ye){return Ye.reduce((Ie,Ce)=>({params:Object.assign(Object.assign({},Ie.params),Ce.params),data:Object.assign(Object.assign({},Ie.data),Ce.data),resolve:Object.assign(Object.assign({},Ie.resolve),Ce._resolvedData)}),{params:{},data:{},resolve:{}})}(Ce.slice(ot))}class _t{constructor(Ie,Ce,ot,Et,qt,Hn,Xn,Ei,Yo,$a,ns){this.url=Ie,this.params=Ce,this.queryParams=ot,this.fragment=Et,this.data=qt,this.outlet=Hn,this.component=Xn,this.routeConfig=Ei,this._urlSegment=Yo,this._lastPathIndex=$a,this._resolve=ns}get root(){return this._routerState.root}get parent(){return this._routerState.parent(this)}get firstChild(){return this._routerState.firstChild(this)}get children(){return this._routerState.children(this)}get pathFromRoot(){return this._routerState.pathFromRoot(this)}get paramMap(){return this._paramMap||(this._paramMap=Kr(this.params)),this._paramMap}get queryParamMap(){return this._queryParamMap||(this._queryParamMap=Kr(this.queryParams)),this._queryParamMap}toString(){return`Route(url:'${this.url.map(ot=>ot.toString()).join("/")}', path:'${this.routeConfig?this.routeConfig.path:""}')`}}class mt extends Bn{constructor(Ie,Ce){super(Ce),this.url=Ie,jt(this,Ce)}toString(){return on(this._root)}}function jt(Ye,Ie){Ie.value._routerState=Ye,Ie.children.forEach(Ce=>jt(Ye,Ce))}function on(Ye){const Ie=Ye.children.length>0?` { ${Ye.children.map(on).join(", ")} } `:"";return`${Ye.value}${Ie}`}function si(Ye){if(Ye.snapshot){const Ie=Ye.snapshot,Ce=Ye._futureSnapshot;Ye.snapshot=Ce,fi(Ie.queryParams,Ce.queryParams)||Ye.queryParams.next(Ce.queryParams),Ie.fragment!==Ce.fragment&&Ye.fragment.next(Ce.fragment),fi(Ie.params,Ce.params)||Ye.params.next(Ce.params),function(Ye,Ie){if(Ye.length!==Ie.length)return!1;for(let Ce=0;Cefi(Ce.parameters,Ie[ot].parameters))}(Ye.url,Ie.url)&&!(!Ye.parent!=!Ie.parent)&&(!Ye.parent||Vi(Ye.parent,Ie.parent))}function co(Ye,Ie,Ce){if(Ce&&Ye.shouldReuseRoute(Ie.value,Ce.value.snapshot)){const ot=Ce.value;ot._futureSnapshot=Ie.value;const Et=function(Ye,Ie,Ce){return Ie.children.map(ot=>{for(const Et of Ce.children)if(Ye.shouldReuseRoute(ot.value,Et.value.snapshot))return co(Ye,ot,Et);return co(Ye,ot)})}(Ye,Ie,Ce);return new er(ot,Et)}{if(Ye.shouldAttach(Ie.value)){const qt=Ye.retrieve(Ie.value);if(null!==qt){const Hn=qt.route;return Es(Ie,Hn),Hn}}const ot=function(Ye){return new tr(new S.X(Ye.url),new S.X(Ye.params),new S.X(Ye.queryParams),new S.X(Ye.fragment),new S.X(Ye.data),Ye.outlet,Ye.component,Ye)}(Ie.value),Et=Ie.children.map(qt=>co(Ye,qt));return new er(ot,Et)}}function Es(Ye,Ie){if(Ye.value.routeConfig!==Ie.value.routeConfig)throw new Error("Cannot reattach ActivatedRouteSnapshot created from a different route");if(Ye.children.length!==Ie.children.length)throw new Error("Cannot reattach ActivatedRouteSnapshot with a different number of children");Ie.value._futureSnapshot=Ye.value;for(let Ce=0;Ce{qt[Xn]=Array.isArray(Hn)?Hn.map(Ei=>`${Ei}`):`${Hn}`}),new gr(Ce.root===Ye?Ie:ya(Ce.root,Ye,Ie),qt,Et)}function ya(Ye,Ie,Ce){const ot={};return Wt(Ye.children,(Et,qt)=>{ot[qt]=Et===Ie?Ce:ya(Et,Ie,Ce)}),new Jt(Ye.segments,ot)}class el{constructor(Ie,Ce,ot){if(this.isAbsolute=Ie,this.numberOfDoubleDots=Ce,this.commands=ot,Ie&&ot.length>0&&ta(ot[0]))throw new Error("Root segment cannot have matrix parameters");const Et=ot.find(Is);if(Et&&Et!==Zr(ot))throw new Error("{outlets:{}} has to be the last command")}toRoot(){return this.isAbsolute&&1===this.commands.length&&"/"==this.commands[0]}}class $s{constructor(Ie,Ce,ot){this.segmentGroup=Ie,this.processChildren=Ce,this.index=ot}}function Ya(Ye,Ie,Ce){if(Ye||(Ye=new Jt([],{})),0===Ye.segments.length&&Ye.hasChildren())return Ao(Ye,Ie,Ce);const ot=function(Ye,Ie,Ce){let ot=0,Et=Ie;const qt={match:!1,pathIndex:0,commandIndex:0};for(;Et=Ce.length)return qt;const Hn=Ye.segments[Et],Xn=Ce[ot];if(Is(Xn))break;const Ei=`${Xn}`,Yo=ot0&&void 0===Ei)break;if(Ei&&Yo&&"object"==typeof Yo&&void 0===Yo.outlets){if(!Ws(Ei,Yo,Hn))return qt;ot+=2}else{if(!Ws(Ei,{},Hn))return qt;ot++}Et++}return{match:!0,pathIndex:Et,commandIndex:ot}}(Ye,Ie,Ce),Et=Ce.slice(ot.commandIndex);if(ot.match&&ot.pathIndex{"string"==typeof qt&&(qt=[qt]),null!==qt&&(Et[Hn]=Ya(Ye.children[Hn],Ie,qt))}),Wt(Ye.children,(qt,Hn)=>{void 0===ot[Hn]&&(Et[Hn]=qt)}),new Jt(Ye.segments,Et)}}function Ca(Ye,Ie,Ce){const ot=Ye.segments.slice(0,Ie);let Et=0;for(;Et{"string"==typeof Ce&&(Ce=[Ce]),null!==Ce&&(Ie[ot]=Ca(new Jt([],{}),0,Ce))}),Ie}function pl(Ye){const Ie={};return Wt(Ye,(Ce,ot)=>Ie[ot]=`${Ce}`),Ie}function Ws(Ye,Ie,Ce){return Ye==Ce.path&&fi(Ie,Ce.parameters)}class bo{constructor(Ie,Ce,ot,Et){this.routeReuseStrategy=Ie,this.futureState=Ce,this.currState=ot,this.forwardEvent=Et}activate(Ie){const Ce=this.futureState._root,ot=this.currState?this.currState._root:null;this.deactivateChildRoutes(Ce,ot,Ie),si(this.futureState.root),this.activateChildRoutes(Ce,ot,Ie)}deactivateChildRoutes(Ie,Ce,ot){const Et=ri(Ce);Ie.children.forEach(qt=>{const Hn=qt.value.outlet;this.deactivateRoutes(qt,Et[Hn],ot),delete Et[Hn]}),Wt(Et,(qt,Hn)=>{this.deactivateRouteAndItsChildren(qt,ot)})}deactivateRoutes(Ie,Ce,ot){const Et=Ie.value,qt=Ce?Ce.value:null;if(Et===qt)if(Et.component){const Hn=ot.getContext(Et.outlet);Hn&&this.deactivateChildRoutes(Ie,Ce,Hn.children)}else this.deactivateChildRoutes(Ie,Ce,ot);else qt&&this.deactivateRouteAndItsChildren(Ce,ot)}deactivateRouteAndItsChildren(Ie,Ce){this.routeReuseStrategy.shouldDetach(Ie.value.snapshot)?this.detachAndStoreRouteSubtree(Ie,Ce):this.deactivateRouteAndOutlet(Ie,Ce)}detachAndStoreRouteSubtree(Ie,Ce){const ot=Ce.getContext(Ie.value.outlet);if(ot&&ot.outlet){const Et=ot.outlet.detach(),qt=ot.children.onOutletDeactivated();this.routeReuseStrategy.store(Ie.value.snapshot,{componentRef:Et,route:Ie,contexts:qt})}}deactivateRouteAndOutlet(Ie,Ce){const ot=Ce.getContext(Ie.value.outlet),Et=ot&&Ie.value.component?ot.children:Ce,qt=ri(Ie);for(const Hn of Object.keys(qt))this.deactivateRouteAndItsChildren(qt[Hn],Et);ot&&ot.outlet&&(ot.outlet.deactivate(),ot.children.onOutletDeactivated(),ot.attachRef=null,ot.resolver=null,ot.route=null)}activateChildRoutes(Ie,Ce,ot){const Et=ri(Ce);Ie.children.forEach(qt=>{this.activateRoutes(qt,Et[qt.value.outlet],ot),this.forwardEvent(new Ur(qt.value.snapshot))}),Ie.children.length&&this.forwardEvent(new Xt(Ie.value.snapshot))}activateRoutes(Ie,Ce,ot){const Et=Ie.value,qt=Ce?Ce.value:null;if(si(Et),Et===qt)if(Et.component){const Hn=ot.getOrCreateContext(Et.outlet);this.activateChildRoutes(Ie,Ce,Hn.children)}else this.activateChildRoutes(Ie,Ce,ot);else if(Et.component){const Hn=ot.getOrCreateContext(Et.outlet);if(this.routeReuseStrategy.shouldAttach(Et.snapshot)){const Xn=this.routeReuseStrategy.retrieve(Et.snapshot);this.routeReuseStrategy.store(Et.snapshot,null),Hn.children.onOutletReAttached(Xn.contexts),Hn.attachRef=Xn.componentRef,Hn.route=Xn.route.value,Hn.outlet&&Hn.outlet.attach(Xn.componentRef,Xn.route.value),Ls(Xn.route)}else{const Xn=function(Ye){for(let Ie=Ye.parent;Ie;Ie=Ie.parent){const Ce=Ie.routeConfig;if(Ce&&Ce._loadedConfig)return Ce._loadedConfig;if(Ce&&Ce.component)return null}return null}(Et.snapshot),Ei=Xn?Xn.module.componentFactoryResolver:null;Hn.attachRef=null,Hn.route=Et,Hn.resolver=Ei,Hn.outlet&&Hn.outlet.activateWith(Et,Ei),this.activateChildRoutes(Ie,null,Hn.children)}}else this.activateChildRoutes(Ie,null,ot)}}function Ls(Ye){si(Ye.value),Ye.children.forEach(Ls)}class So{constructor(Ie,Ce){this.routes=Ie,this.module=Ce}}function $r(Ye){return"function"==typeof Ye}function tt(Ye){return Ye instanceof gr}const an=Symbol("INITIAL_VALUE");function ti(){return(0,ee.w)(Ye=>(0,A.aj)(Ye.map(Ie=>Ie.pipe((0,ue.q)(1),(0,ae.O)(an)))).pipe((0,H.R)((Ie,Ce)=>{let ot=!1;return Ce.reduce((Et,qt,Hn)=>Et!==an?Et:(qt===an&&(ot=!0),ot||!1!==qt&&Hn!==Ce.length-1&&!tt(qt)?Et:qt),Ie)},an),(0,se.h)(Ie=>Ie!==an),(0,K.U)(Ie=>tt(Ie)?Ie:!0===Ie),(0,ue.q)(1)))}let pi=(()=>{class Ye{}return Ye.\u0275fac=function(Ce){return new(Ce||Ye)},Ye.\u0275cmp=u.Xpm({type:Ye,selectors:[["ng-component"]],decls:1,vars:0,template:function(Ce,ot){1&Ce&&u._UZ(0,"router-outlet")},directives:function(){return[ao]},encapsulation:2}),Ye})();function xi(Ye,Ie=""){for(let Ce=0;CeEo(ot)===Ie);return Ce.push(...Ye.filter(ot=>Eo(ot)!==Ie)),Ce}const sl={matched:!1,consumedSegments:[],lastChild:0,parameters:{},positionalParamSegments:{}};function tc(Ye,Ie,Ce){var ot;if(""===Ie.path)return"full"===Ie.pathMatch&&(Ye.hasChildren()||Ce.length>0)?Object.assign({},sl):{matched:!0,consumedSegments:[],lastChild:0,parameters:{},positionalParamSegments:{}};const qt=(Ie.matcher||Br)(Ce,Ye,Ie);if(!qt)return Object.assign({},sl);const Hn={};Wt(qt.posParams,(Ei,Yo)=>{Hn[Yo]=Ei.path});const Xn=qt.consumed.length>0?Object.assign(Object.assign({},Hn),qt.consumed[qt.consumed.length-1].parameters):Hn;return{matched:!0,consumedSegments:qt.consumed,lastChild:qt.consumed.length,parameters:Xn,positionalParamSegments:null!==(ot=qt.posParams)&&void 0!==ot?ot:{}}}function Nu(Ye,Ie,Ce,ot,Et="corrected"){if(Ce.length>0&&function(Ye,Ie,Ce){return Ce.some(ot=>Yl(Ye,Ie,ot)&&Eo(ot)!==Lr)}(Ye,Ce,ot)){const Hn=new Jt(Ie,function(Ye,Ie,Ce,ot){const Et={};Et[Lr]=ot,ot._sourceSegment=Ye,ot._segmentIndexShift=Ie.length;for(const qt of Ce)if(""===qt.path&&Eo(qt)!==Lr){const Hn=new Jt([],{});Hn._sourceSegment=Ye,Hn._segmentIndexShift=Ie.length,Et[Eo(qt)]=Hn}return Et}(Ye,Ie,ot,new Jt(Ce,Ye.children)));return Hn._sourceSegment=Ye,Hn._segmentIndexShift=Ie.length,{segmentGroup:Hn,slicedSegments:[]}}if(0===Ce.length&&function(Ye,Ie,Ce){return Ce.some(ot=>Yl(Ye,Ie,ot))}(Ye,Ce,ot)){const Hn=new Jt(Ye.segments,function(Ye,Ie,Ce,ot,Et,qt){const Hn={};for(const Xn of ot)if(Yl(Ye,Ce,Xn)&&!Et[Eo(Xn)]){const Ei=new Jt([],{});Ei._sourceSegment=Ye,Ei._segmentIndexShift="legacy"===qt?Ye.segments.length:Ie.length,Hn[Eo(Xn)]=Ei}return Object.assign(Object.assign({},Et),Hn)}(Ye,Ie,Ce,ot,Ye.children,Et));return Hn._sourceSegment=Ye,Hn._segmentIndexShift=Ie.length,{segmentGroup:Hn,slicedSegments:Ce}}const qt=new Jt(Ye.segments,Ye.children);return qt._sourceSegment=Ye,qt._segmentIndexShift=Ie.length,{segmentGroup:qt,slicedSegments:Ce}}function Yl(Ye,Ie,Ce){return(!(Ye.hasChildren()||Ie.length>0)||"full"!==Ce.pathMatch)&&""===Ce.path}function Fs(Ye,Ie,Ce,ot){return!!(Eo(Ye)===ot||ot!==Lr&&Yl(Ie,Ce,Ye))&&("**"===Ye.path||tc(Ie,Ye,Ce).matched)}function dc(Ye,Ie,Ce){return 0===Ie.length&&!Ye.children[Ce]}class iu{constructor(Ie){this.segmentGroup=Ie||null}}class tl{constructor(Ie){this.urlTree=Ie}}function Su(Ye){return new p.y(Ie=>Ie.error(new iu(Ye)))}function Zl(Ye){return new p.y(Ie=>Ie.error(new tl(Ye)))}function rt(Ye){return new p.y(Ie=>Ie.error(new Error(`Only absolute redirects can have named outlets. redirectTo: '${Ye}'`)))}class Te{constructor(Ie,Ce,ot,Et,qt){this.configLoader=Ce,this.urlSerializer=ot,this.urlTree=Et,this.config=qt,this.allowRedirects=!0,this.ngModule=Ie.get(u.h0i)}apply(){const Ie=Nu(this.urlTree.root,[],[],this.config).segmentGroup,Ce=new Jt(Ie.segments,Ie.children);return this.expandSegmentGroup(this.ngModule,this.config,Ce,Lr).pipe((0,K.U)(qt=>this.createUrlTree(xe(qt),this.urlTree.queryParams,this.urlTree.fragment))).pipe((0,Ee.K)(qt=>{if(qt instanceof tl)return this.allowRedirects=!1,this.match(qt.urlTree);throw qt instanceof iu?this.noMatchError(qt):qt}))}match(Ie){return this.expandSegmentGroup(this.ngModule,this.config,Ie.root,Lr).pipe((0,K.U)(Et=>this.createUrlTree(xe(Et),Ie.queryParams,Ie.fragment))).pipe((0,Ee.K)(Et=>{throw Et instanceof iu?this.noMatchError(Et):Et}))}noMatchError(Ie){return new Error(`Cannot match any routes. URL Segment: '${Ie.segmentGroup}'`)}createUrlTree(Ie,Ce,ot){const Et=Ie.segments.length>0?new Jt([],{[Lr]:Ie}):Ie;return new gr(Et,Ce,ot)}expandSegmentGroup(Ie,Ce,ot,Et){return 0===ot.segments.length&&ot.hasChildren()?this.expandChildren(Ie,Ce,ot).pipe((0,K.U)(qt=>new Jt([],qt))):this.expandSegment(Ie,ot,Ce,ot.segments,Et,!0)}expandChildren(Ie,Ce,ot){const Et=[];for(const qt of Object.keys(ot.children))"primary"===qt?Et.unshift(qt):Et.push(qt);return(0,d.D)(Et).pipe((0,ie.b)(qt=>{const Hn=ot.children[qt],Xn=ba(Ce,qt);return this.expandSegmentGroup(Ie,Xn,Hn,qt).pipe((0,K.U)(Ei=>({segment:Ei,outlet:qt})))}),(0,H.R)((qt,Hn)=>(qt[Hn.outlet]=Hn.segment,qt),{}),(0,he.Z)())}expandSegment(Ie,Ce,ot,Et,qt,Hn){return(0,d.D)(ot).pipe((0,ie.b)(Xn=>this.expandSegmentAgainstRoute(Ie,Ce,ot,Xn,Et,qt,Hn).pipe((0,Ee.K)(Yo=>{if(Yo instanceof iu)return(0,y.of)(null);throw Yo}))),(0,ge.P)(Xn=>!!Xn),(0,Ee.K)((Xn,Ei)=>{if(Xn instanceof N.K||"EmptyError"===Xn.name){if(dc(Ce,Et,qt))return(0,y.of)(new Jt([],{}));throw new iu(Ce)}throw Xn}))}expandSegmentAgainstRoute(Ie,Ce,ot,Et,qt,Hn,Xn){return Fs(Et,Ce,qt,Hn)?void 0===Et.redirectTo?this.matchSegmentAgainstRoute(Ie,Ce,Et,qt,Hn):Xn&&this.allowRedirects?this.expandSegmentAgainstRouteUsingRedirect(Ie,Ce,ot,Et,qt,Hn):Su(Ce):Su(Ce)}expandSegmentAgainstRouteUsingRedirect(Ie,Ce,ot,Et,qt,Hn){return"**"===Et.path?this.expandWildCardWithParamsAgainstRouteUsingRedirect(Ie,ot,Et,Hn):this.expandRegularSegmentAgainstRouteUsingRedirect(Ie,Ce,ot,Et,qt,Hn)}expandWildCardWithParamsAgainstRouteUsingRedirect(Ie,Ce,ot,Et){const qt=this.applyRedirectCommands([],ot.redirectTo,{});return ot.redirectTo.startsWith("/")?Zl(qt):this.lineralizeSegments(ot,qt).pipe((0,De.zg)(Hn=>{const Xn=new Jt(Hn,{});return this.expandSegment(Ie,Xn,Ce,Hn,Et,!1)}))}expandRegularSegmentAgainstRouteUsingRedirect(Ie,Ce,ot,Et,qt,Hn){const{matched:Xn,consumedSegments:Ei,lastChild:Yo,positionalParamSegments:$a}=tc(Ce,Et,qt);if(!Xn)return Su(Ce);const ns=this.applyRedirectCommands(Ei,Et.redirectTo,$a);return Et.redirectTo.startsWith("/")?Zl(ns):this.lineralizeSegments(Et,ns).pipe((0,De.zg)(Oa=>this.expandSegment(Ie,Ce,ot,Oa.concat(qt.slice(Yo)),Hn,!1)))}matchSegmentAgainstRoute(Ie,Ce,ot,Et,qt){if("**"===ot.path)return ot.loadChildren?(ot._loadedConfig?(0,y.of)(ot._loadedConfig):this.configLoader.load(Ie.injector,ot)).pipe((0,K.U)(Oa=>(ot._loadedConfig=Oa,new Jt(Et,{})))):(0,y.of)(new Jt(Et,{}));const{matched:Hn,consumedSegments:Xn,lastChild:Ei}=tc(Ce,ot,Et);if(!Hn)return Su(Ce);const Yo=Et.slice(Ei);return this.getChildConfig(Ie,ot,Et).pipe((0,De.zg)(ns=>{const Oa=ns.module,Hs=ns.routes,{segmentGroup:fc,slicedSegments:ga}=Nu(Ce,Xn,Yo,Hs),Ol=new Jt(fc.segments,fc.children);if(0===ga.length&&Ol.hasChildren())return this.expandChildren(Oa,Hs,Ol).pipe((0,K.U)(ul=>new Jt(Xn,ul)));if(0===Hs.length&&0===ga.length)return(0,y.of)(new Jt(Xn,{}));const ll=Eo(ot)===qt;return this.expandSegment(Oa,Ol,Hs,ga,ll?Lr:qt,!0).pipe((0,K.U)(ja=>new Jt(Xn.concat(ja.segments),ja.children)))}))}getChildConfig(Ie,Ce,ot){return Ce.children?(0,y.of)(new So(Ce.children,Ie)):Ce.loadChildren?void 0!==Ce._loadedConfig?(0,y.of)(Ce._loadedConfig):this.runCanLoadGuards(Ie.injector,Ce,ot).pipe((0,De.zg)(Et=>{return Et?this.configLoader.load(Ie.injector,Ce).pipe((0,K.U)(qt=>(Ce._loadedConfig=qt,qt))):(Ye=Ce,new p.y(Ie=>Ie.error(Nn(`Cannot load children because the guard of the route "path: '${Ye.path}'" returned false`))));var Ye})):(0,y.of)(new So([],Ie))}runCanLoadGuards(Ie,Ce,ot){const Et=Ce.canLoad;if(!Et||0===Et.length)return(0,y.of)(!0);const qt=Et.map(Hn=>{const Xn=Ie.get(Hn);let Ei;if((Ye=Xn)&&$r(Ye.canLoad))Ei=Xn.canLoad(Ce,ot);else{if(!$r(Xn))throw new Error("Invalid CanLoad guard");Ei=Xn(Ce,ot)}var Ye;return zn(Ei)});return(0,y.of)(qt).pipe(ti(),(0,ce.b)(Hn=>{if(!tt(Hn))return;const Xn=Nn(`Redirecting to "${this.urlSerializer.serialize(Hn)}"`);throw Xn.url=Hn,Xn}),(0,K.U)(Hn=>!0===Hn))}lineralizeSegments(Ie,Ce){let ot=[],Et=Ce.root;for(;;){if(ot=ot.concat(Et.segments),0===Et.numberOfChildren)return(0,y.of)(ot);if(Et.numberOfChildren>1||!Et.children[Lr])return rt(Ie.redirectTo);Et=Et.children[Lr]}}applyRedirectCommands(Ie,Ce,ot){return this.applyRedirectCreatreUrlTree(Ce,this.urlSerializer.parse(Ce),Ie,ot)}applyRedirectCreatreUrlTree(Ie,Ce,ot,Et){const qt=this.createSegmentGroup(Ie,Ce.root,ot,Et);return new gr(qt,this.createQueryParams(Ce.queryParams,this.urlTree.queryParams),Ce.fragment)}createQueryParams(Ie,Ce){const ot={};return Wt(Ie,(Et,qt)=>{if("string"==typeof Et&&Et.startsWith(":")){const Xn=Et.substring(1);ot[qt]=Ce[Xn]}else ot[qt]=Et}),ot}createSegmentGroup(Ie,Ce,ot,Et){const qt=this.createSegments(Ie,Ce.segments,ot,Et);let Hn={};return Wt(Ce.children,(Xn,Ei)=>{Hn[Ei]=this.createSegmentGroup(Ie,Xn,ot,Et)}),new Jt(qt,Hn)}createSegments(Ie,Ce,ot,Et){return Ce.map(qt=>qt.path.startsWith(":")?this.findPosParam(Ie,qt,Et):this.findOrReturn(qt,ot))}findPosParam(Ie,Ce,ot){const Et=ot[Ce.path.substring(1)];if(!Et)throw new Error(`Cannot redirect to '${Ie}'. Cannot find '${Ce.path}'.`);return Et}findOrReturn(Ie,Ce){let ot=0;for(const Et of Ce){if(Et.path===Ie.path)return Ce.splice(ot),Et;ot++}return Ie}}function xe(Ye){const Ie={};for(const ot of Object.keys(Ye.children)){const qt=xe(Ye.children[ot]);(qt.segments.length>0||qt.hasChildren())&&(Ie[ot]=qt)}return function(Ye){if(1===Ye.numberOfChildren&&Ye.children[Lr]){const Ie=Ye.children[Lr];return new Jt(Ye.segments.concat(Ie.segments),Ie.children)}return Ye}(new Jt(Ye.segments,Ie))}class ur{constructor(Ie){this.path=Ie,this.route=this.path[this.path.length-1]}}class Qi{constructor(Ie,Ce){this.component=Ie,this.route=Ce}}function Go(Ye,Ie,Ce){const ot=Ye._root;return Ma(ot,Ie?Ie._root:null,Ce,[ot.value])}function hs(Ye,Ie,Ce){const ot=function(Ye){if(!Ye)return null;for(let Ie=Ye.parent;Ie;Ie=Ie.parent){const Ce=Ie.routeConfig;if(Ce&&Ce._loadedConfig)return Ce._loadedConfig}return null}(Ie);return(ot?ot.module.injector:Ce).get(Ye)}function Ma(Ye,Ie,Ce,ot,Et={canDeactivateChecks:[],canActivateChecks:[]}){const qt=ri(Ie);return Ye.children.forEach(Hn=>{(function(Ye,Ie,Ce,ot,Et={canDeactivateChecks:[],canActivateChecks:[]}){const qt=Ye.value,Hn=Ie?Ie.value:null,Xn=Ce?Ce.getContext(Ye.value.outlet):null;if(Hn&&qt.routeConfig===Hn.routeConfig){const Ei=function(Ye,Ie,Ce){if("function"==typeof Ce)return Ce(Ye,Ie);switch(Ce){case"pathParamsChange":return!Dn(Ye.url,Ie.url);case"pathParamsOrQueryParamsChange":return!Dn(Ye.url,Ie.url)||!fi(Ye.queryParams,Ie.queryParams);case"always":return!0;case"paramsOrQueryParamsChange":return!Vi(Ye,Ie)||!fi(Ye.queryParams,Ie.queryParams);case"paramsChange":default:return!Vi(Ye,Ie)}}(Hn,qt,qt.routeConfig.runGuardsAndResolvers);Ei?Et.canActivateChecks.push(new ur(ot)):(qt.data=Hn.data,qt._resolvedData=Hn._resolvedData),Ma(Ye,Ie,qt.component?Xn?Xn.children:null:Ce,ot,Et),Ei&&Xn&&Xn.outlet&&Xn.outlet.isActivated&&Et.canDeactivateChecks.push(new Qi(Xn.outlet.component,Hn))}else Hn&&Rl(Ie,Xn,Et),Et.canActivateChecks.push(new ur(ot)),Ma(Ye,null,qt.component?Xn?Xn.children:null:Ce,ot,Et)})(Hn,qt[Hn.value.outlet],Ce,ot.concat([Hn.value]),Et),delete qt[Hn.value.outlet]}),Wt(qt,(Hn,Xn)=>Rl(Hn,Ce.getContext(Xn),Et)),Et}function Rl(Ye,Ie,Ce){const ot=ri(Ye),Et=Ye.value;Wt(ot,(qt,Hn)=>{Rl(qt,Et.component?Ie?Ie.children.getContext(Hn):null:Ie,Ce)}),Ce.canDeactivateChecks.push(new Qi(Et.component&&Ie&&Ie.outlet&&Ie.outlet.isActivated?Ie.outlet.component:null,Et))}class gd{}function rc(Ye){return new p.y(Ie=>Ie.error(Ye))}class sa{constructor(Ie,Ce,ot,Et,qt,Hn){this.rootComponentType=Ie,this.config=Ce,this.urlTree=ot,this.url=Et,this.paramsInheritanceStrategy=qt,this.relativeLinkResolution=Hn}recognize(){const Ie=Nu(this.urlTree.root,[],[],this.config.filter(Hn=>void 0===Hn.redirectTo),this.relativeLinkResolution).segmentGroup,Ce=this.processSegmentGroup(this.config,Ie,Lr);if(null===Ce)return null;const ot=new _t([],Object.freeze({}),Object.freeze(Object.assign({},this.urlTree.queryParams)),this.urlTree.fragment,{},Lr,this.rootComponentType,null,this.urlTree.root,-1,{}),Et=new er(ot,Ce),qt=new mt(this.url,Et);return this.inheritParamsAndData(qt._root),qt}inheritParamsAndData(Ie){const Ce=Ie.value,ot=Ar(Ce,this.paramsInheritanceStrategy);Ce.params=Object.freeze(ot.params),Ce.data=Object.freeze(ot.data),Ie.children.forEach(Et=>this.inheritParamsAndData(Et))}processSegmentGroup(Ie,Ce,ot){return 0===Ce.segments.length&&Ce.hasChildren()?this.processChildren(Ie,Ce):this.processSegment(Ie,Ce,Ce.segments,ot)}processChildren(Ie,Ce){const ot=[];for(const qt of Object.keys(Ce.children)){const Hn=Ce.children[qt],Xn=ba(Ie,qt),Ei=this.processSegmentGroup(Xn,Hn,qt);if(null===Ei)return null;ot.push(...Ei)}const Et=yl(ot);return Et.sort((Ie,Ce)=>Ie.value.outlet===Lr?-1:Ce.value.outlet===Lr?1:Ie.value.outlet.localeCompare(Ce.value.outlet)),Et}processSegment(Ie,Ce,ot,Et){for(const qt of Ie){const Hn=this.processSegmentAgainstRoute(qt,Ce,ot,Et);if(null!==Hn)return Hn}return dc(Ce,ot,Et)?[]:null}processSegmentAgainstRoute(Ie,Ce,ot,Et){if(Ie.redirectTo||!Fs(Ie,Ce,ot,Et))return null;let qt,Hn=[],Xn=[];if("**"===Ie.path){const Hs=ot.length>0?Zr(ot).parameters:{};qt=new _t(ot,Hs,Object.freeze(Object.assign({},this.urlTree.queryParams)),this.urlTree.fragment,js(Ie),Eo(Ie),Ie.component,Ie,yu(Ce),lu(Ce)+ot.length,Hu(Ie))}else{const Hs=tc(Ce,Ie,ot);if(!Hs.matched)return null;Hn=Hs.consumedSegments,Xn=ot.slice(Hs.lastChild),qt=new _t(Hn,Hs.parameters,Object.freeze(Object.assign({},this.urlTree.queryParams)),this.urlTree.fragment,js(Ie),Eo(Ie),Ie.component,Ie,yu(Ce),lu(Ce)+Hn.length,Hu(Ie))}const Ei=(Ye=Ie).children?Ye.children:Ye.loadChildren?Ye._loadedConfig.routes:[],{segmentGroup:Yo,slicedSegments:$a}=Nu(Ce,Hn,Xn,Ei.filter(Hs=>void 0===Hs.redirectTo),this.relativeLinkResolution);var Ye;if(0===$a.length&&Yo.hasChildren()){const Hs=this.processChildren(Ei,Yo);return null===Hs?null:[new er(qt,Hs)]}if(0===Ei.length&&0===$a.length)return[new er(qt,[])];const ns=Eo(Ie)===Et,Oa=this.processSegment(Ei,Yo,$a,ns?Lr:Et);return null===Oa?null:[new er(qt,Oa)]}}function $u(Ye){const Ie=Ye.value.routeConfig;return Ie&&""===Ie.path&&void 0===Ie.redirectTo}function yl(Ye){const Ie=[],Ce=new Set;for(const ot of Ye){if(!$u(ot)){Ie.push(ot);continue}const Et=Ie.find(qt=>ot.value.routeConfig===qt.value.routeConfig);void 0!==Et?(Et.children.push(...ot.children),Ce.add(Et)):Ie.push(ot)}for(const ot of Ce){const Et=yl(ot.children);Ie.push(new er(ot.value,Et))}return Ie.filter(ot=>!Ce.has(ot))}function yu(Ye){let Ie=Ye;for(;Ie._sourceSegment;)Ie=Ie._sourceSegment;return Ie}function lu(Ye){let Ie=Ye,Ce=Ie._segmentIndexShift?Ie._segmentIndexShift:0;for(;Ie._sourceSegment;)Ie=Ie._sourceSegment,Ce+=Ie._segmentIndexShift?Ie._segmentIndexShift:0;return Ce-1}function js(Ye){return Ye.data||{}}function Hu(Ye){return Ye.resolve||{}}function pu(Ye){return(0,ee.w)(Ie=>{const Ce=Ye(Ie);return Ce?(0,d.D)(Ce).pipe((0,K.U)(()=>Ie)):(0,y.of)(Ie)})}class bl extends class{shouldDetach(Ie){return!1}store(Ie,Ce){}shouldAttach(Ie){return!1}retrieve(Ie){return null}shouldReuseRoute(Ie,Ce){return Ie.routeConfig===Ce.routeConfig}}{}const El=new u.OlP("ROUTES");class Ul{constructor(Ie,Ce,ot,Et){this.loader=Ie,this.compiler=Ce,this.onLoadStartListener=ot,this.onLoadEndListener=Et}load(Ie,Ce){if(Ce._loader$)return Ce._loader$;this.onLoadStartListener&&this.onLoadStartListener(Ce);const Et=this.loadModuleFactory(Ce.loadChildren).pipe((0,K.U)(qt=>{this.onLoadEndListener&&this.onLoadEndListener(Ce);const Hn=qt.create(Ie);return new So(Hi(Hn.injector.get(El,void 0,u.XFs.Self|u.XFs.Optional)).map(ko),Hn)}),(0,Ee.K)(qt=>{throw Ce._loader$=void 0,qt}));return Ce._loader$=new Z.c(Et,()=>new J.xQ).pipe((0,Ve.x)()),Ce._loader$}loadModuleFactory(Ie){return"string"==typeof Ie?(0,d.D)(this.loader.load(Ie)):zn(Ie()).pipe((0,De.zg)(Ce=>Ce instanceof u.YKP?(0,y.of)(Ce):(0,d.D)(this.compiler.compileModuleAsync(Ce))))}}class ks{constructor(){this.outlet=null,this.route=null,this.resolver=null,this.children=new V,this.attachRef=null}}class V{constructor(){this.contexts=new Map}onChildOutletCreated(Ie,Ce){const ot=this.getOrCreateContext(Ie);ot.outlet=Ce,this.contexts.set(Ie,ot)}onChildOutletDestroyed(Ie){const Ce=this.getContext(Ie);Ce&&(Ce.outlet=null,Ce.attachRef=null)}onOutletDeactivated(){const Ie=this.contexts;return this.contexts=new Map,Ie}onOutletReAttached(Ie){this.contexts=Ie}getOrCreateContext(Ie){let Ce=this.getContext(Ie);return Ce||(Ce=new ks,this.contexts.set(Ie,Ce)),Ce}getContext(Ie){return this.contexts.get(Ie)||null}}class st{shouldProcessUrl(Ie){return!0}extract(Ie){return Ie}merge(Ie,Ce){return Ie}}function vt(Ye){throw Ye}function ut(Ye,Ie,Ce){return Ie.parse("/")}function un(Ye,Ie){return(0,y.of)(null)}const mn={paths:"exact",fragment:"ignored",matrixParams:"ignored",queryParams:"exact"},Or={paths:"subset",fragment:"ignored",matrixParams:"ignored",queryParams:"subset"};let zr=(()=>{class Ye{constructor(Ce,ot,Et,qt,Hn,Xn,Ei,Yo){this.rootComponentType=Ce,this.urlSerializer=ot,this.rootContexts=Et,this.location=qt,this.config=Yo,this.lastSuccessfulNavigation=null,this.currentNavigation=null,this.disposed=!1,this.lastLocationChangeInfo=null,this.navigationId=0,this.currentPageId=0,this.isNgZoneEnabled=!1,this.events=new J.xQ,this.errorHandler=vt,this.malformedUriErrorHandler=ut,this.navigated=!1,this.lastSuccessfulId=-1,this.hooks={beforePreactivation:un,afterPreactivation:un},this.urlHandlingStrategy=new st,this.routeReuseStrategy=new bl,this.onSameUrlNavigation="ignore",this.paramsInheritanceStrategy="emptyOnly",this.urlUpdateStrategy="deferred",this.relativeLinkResolution="corrected",this.canceledNavigationResolution="replace",this.ngModule=Hn.get(u.h0i),this.console=Hn.get(u.c2e);const Oa=Hn.get(u.R0b);this.isNgZoneEnabled=Oa instanceof u.R0b&&u.R0b.isInAngularZone(),this.resetConfig(Yo),this.currentUrlTree=new gr(new Jt([],{}),{},null),this.rawUrlTree=this.currentUrlTree,this.browserUrlTree=this.currentUrlTree,this.configLoader=new Ul(Xn,Ei,Hs=>this.triggerEvent(new bt(Hs)),Hs=>this.triggerEvent(new Gt(Hs))),this.routerState=Ci(this.currentUrlTree,this.rootComponentType),this.transitions=new S.X({id:0,targetPageId:0,currentUrlTree:this.currentUrlTree,currentRawUrl:this.currentUrlTree,extractedUrl:this.urlHandlingStrategy.extract(this.currentUrlTree),urlAfterRedirects:this.urlHandlingStrategy.extract(this.currentUrlTree),rawUrl:this.currentUrlTree,extras:{},resolve:null,reject:null,promise:Promise.resolve(!0),source:"imperative",restoredState:null,currentSnapshot:this.routerState.snapshot,targetSnapshot:null,currentRouterState:this.routerState,targetRouterState:null,guards:{canActivateChecks:[],canDeactivateChecks:[]},guardsResult:null}),this.navigations=this.setupNavigations(this.transitions),this.processNavigations()}get browserPageId(){var Ce;return null===(Ce=this.location.getState())||void 0===Ce?void 0:Ce.\u0275routerPageId}setupNavigations(Ce){const ot=this.events;return Ce.pipe((0,se.h)(Et=>0!==Et.id),(0,K.U)(Et=>Object.assign(Object.assign({},Et),{extractedUrl:this.urlHandlingStrategy.extract(Et.rawUrl)})),(0,ee.w)(Et=>{let qt=!1,Hn=!1;return(0,y.of)(Et).pipe((0,ce.b)(Xn=>{this.currentNavigation={id:Xn.id,initialUrl:Xn.currentRawUrl,extractedUrl:Xn.extractedUrl,trigger:Xn.source,extras:Xn.extras,previousNavigation:this.lastSuccessfulNavigation?Object.assign(Object.assign({},this.lastSuccessfulNavigation),{previousNavigation:null}):null}}),(0,ee.w)(Xn=>{const Ei=this.browserUrlTree.toString(),Yo=!this.navigated||Xn.extractedUrl.toString()!==Ei||Ei!==this.currentUrlTree.toString();if(("reload"===this.onSameUrlNavigation||Yo)&&this.urlHandlingStrategy.shouldProcessUrl(Xn.rawUrl))return ai(Xn.source)&&(this.browserUrlTree=Xn.extractedUrl),(0,y.of)(Xn).pipe((0,ee.w)(ns=>{const Oa=this.transitions.getValue();return ot.next(new je(ns.id,this.serializeUrl(ns.extractedUrl),ns.source,ns.restoredState)),Oa!==this.transitions.getValue()?e.E:Promise.resolve(ns)}),function(Ye,Ie,Ce,ot){return(0,ee.w)(Et=>function(Ye,Ie,Ce,ot,Et){return new Te(Ye,Ie,Ce,ot,Et).apply()}(Ye,Ie,Ce,Et.extractedUrl,ot).pipe((0,K.U)(qt=>Object.assign(Object.assign({},Et),{urlAfterRedirects:qt}))))}(this.ngModule.injector,this.configLoader,this.urlSerializer,this.config),(0,ce.b)(ns=>{this.currentNavigation=Object.assign(Object.assign({},this.currentNavigation),{finalUrl:ns.urlAfterRedirects})}),function(Ye,Ie,Ce,ot,Et){return(0,De.zg)(qt=>function(Ye,Ie,Ce,ot,Et="emptyOnly",qt="legacy"){try{const Hn=new sa(Ye,Ie,Ce,ot,Et,qt).recognize();return null===Hn?rc(new gd):(0,y.of)(Hn)}catch(Hn){return rc(Hn)}}(Ye,Ie,qt.urlAfterRedirects,Ce(qt.urlAfterRedirects),ot,Et).pipe((0,K.U)(Hn=>Object.assign(Object.assign({},qt),{targetSnapshot:Hn}))))}(this.rootComponentType,this.config,ns=>this.serializeUrl(ns),this.paramsInheritanceStrategy,this.relativeLinkResolution),(0,ce.b)(ns=>{"eager"===this.urlUpdateStrategy&&(ns.extras.skipLocationChange||this.setBrowserUrl(ns.urlAfterRedirects,ns),this.browserUrlTree=ns.urlAfterRedirects);const Oa=new tn(ns.id,this.serializeUrl(ns.extractedUrl),this.serializeUrl(ns.urlAfterRedirects),ns.targetSnapshot);ot.next(Oa)}));if(Yo&&this.rawUrlTree&&this.urlHandlingStrategy.shouldProcessUrl(this.rawUrlTree)){const{id:Oa,extractedUrl:Hs,source:fc,restoredState:ga,extras:Ol}=Xn,ll=new je(Oa,this.serializeUrl(Hs),fc,ga);ot.next(ll);const Za=Ci(Hs,this.rootComponentType).snapshot;return(0,y.of)(Object.assign(Object.assign({},Xn),{targetSnapshot:Za,urlAfterRedirects:Hs,extras:Object.assign(Object.assign({},Ol),{skipLocationChange:!1,replaceUrl:!1})}))}return this.rawUrlTree=Xn.rawUrl,this.browserUrlTree=Xn.urlAfterRedirects,Xn.resolve(null),e.E}),pu(Xn=>{const{targetSnapshot:Ei,id:Yo,extractedUrl:$a,rawUrl:ns,extras:{skipLocationChange:Oa,replaceUrl:Hs}}=Xn;return this.hooks.beforePreactivation(Ei,{navigationId:Yo,appliedUrlTree:$a,rawUrlTree:ns,skipLocationChange:!!Oa,replaceUrl:!!Hs})}),(0,ce.b)(Xn=>{const Ei=new It(Xn.id,this.serializeUrl(Xn.extractedUrl),this.serializeUrl(Xn.urlAfterRedirects),Xn.targetSnapshot);this.triggerEvent(Ei)}),(0,K.U)(Xn=>Object.assign(Object.assign({},Xn),{guards:Go(Xn.targetSnapshot,Xn.currentSnapshot,this.rootContexts)})),function(Ye,Ie){return(0,De.zg)(Ce=>{const{targetSnapshot:ot,currentSnapshot:Et,guards:{canActivateChecks:qt,canDeactivateChecks:Hn}}=Ce;return 0===Hn.length&&0===qt.length?(0,y.of)(Object.assign(Object.assign({},Ce),{guardsResult:!0})):function(Ye,Ie,Ce,ot){return(0,d.D)(Ye).pipe((0,De.zg)(Et=>function(Ye,Ie,Ce,ot,Et){const qt=Ie&&Ie.routeConfig?Ie.routeConfig.canDeactivate:null;if(!qt||0===qt.length)return(0,y.of)(!0);const Hn=qt.map(Xn=>{const Ei=hs(Xn,Ie,Et);let Yo;if(function(Ye){return Ye&&$r(Ye.canDeactivate)}(Ei))Yo=zn(Ei.canDeactivate(Ye,Ie,Ce,ot));else{if(!$r(Ei))throw new Error("Invalid CanDeactivate guard");Yo=zn(Ei(Ye,Ie,Ce,ot))}return Yo.pipe((0,ge.P)())});return(0,y.of)(Hn).pipe(ti())}(Et.component,Et.route,Ce,Ie,ot)),(0,ge.P)(Et=>!0!==Et,!0))}(Hn,ot,Et,Ye).pipe((0,De.zg)(Xn=>Xn&&function(Ye){return"boolean"==typeof Ye}(Xn)?function(Ye,Ie,Ce,ot){return(0,d.D)(Ie).pipe((0,ie.b)(Et=>(0,L.z)(function(Ye,Ie){return null!==Ye&&Ie&&Ie(new xt(Ye)),(0,y.of)(!0)}(Et.route.parent,ot),function(Ye,Ie){return null!==Ye&&Ie&&Ie(new Zn(Ye)),(0,y.of)(!0)}(Et.route,ot),function(Ye,Ie,Ce){const ot=Ie[Ie.length-1],qt=Ie.slice(0,Ie.length-1).reverse().map(Hn=>function(Ye){const Ie=Ye.routeConfig?Ye.routeConfig.canActivateChild:null;return Ie&&0!==Ie.length?{node:Ye,guards:Ie}:null}(Hn)).filter(Hn=>null!==Hn).map(Hn=>_(()=>{const Xn=Hn.guards.map(Ei=>{const Yo=hs(Ei,Hn.node,Ce);let $a;if(function(Ye){return Ye&&$r(Ye.canActivateChild)}(Yo))$a=zn(Yo.canActivateChild(ot,Ye));else{if(!$r(Yo))throw new Error("Invalid CanActivateChild guard");$a=zn(Yo(ot,Ye))}return $a.pipe((0,ge.P)())});return(0,y.of)(Xn).pipe(ti())}));return(0,y.of)(qt).pipe(ti())}(Ye,Et.path,Ce),function(Ye,Ie,Ce){const ot=Ie.routeConfig?Ie.routeConfig.canActivate:null;if(!ot||0===ot.length)return(0,y.of)(!0);const Et=ot.map(qt=>_(()=>{const Hn=hs(qt,Ie,Ce);let Xn;if(function(Ye){return Ye&&$r(Ye.canActivate)}(Hn))Xn=zn(Hn.canActivate(Ie,Ye));else{if(!$r(Hn))throw new Error("Invalid CanActivate guard");Xn=zn(Hn(Ie,Ye))}return Xn.pipe((0,ge.P)())}));return(0,y.of)(Et).pipe(ti())}(Ye,Et.route,Ce))),(0,ge.P)(Et=>!0!==Et,!0))}(ot,qt,Ye,Ie):(0,y.of)(Xn)),(0,K.U)(Xn=>Object.assign(Object.assign({},Ce),{guardsResult:Xn})))})}(this.ngModule.injector,Xn=>this.triggerEvent(Xn)),(0,ce.b)(Xn=>{if(tt(Xn.guardsResult)){const Yo=Nn(`Redirecting to "${this.serializeUrl(Xn.guardsResult)}"`);throw Yo.url=Xn.guardsResult,Yo}const Ei=new Zt(Xn.id,this.serializeUrl(Xn.extractedUrl),this.serializeUrl(Xn.urlAfterRedirects),Xn.targetSnapshot,!!Xn.guardsResult);this.triggerEvent(Ei)}),(0,se.h)(Xn=>!!Xn.guardsResult||(this.restoreHistory(Xn),this.cancelNavigationTransition(Xn,""),!1)),pu(Xn=>{if(Xn.guards.canActivateChecks.length)return(0,y.of)(Xn).pipe((0,ce.b)(Ei=>{const Yo=new Ut(Ei.id,this.serializeUrl(Ei.extractedUrl),this.serializeUrl(Ei.urlAfterRedirects),Ei.targetSnapshot);this.triggerEvent(Yo)}),(0,ee.w)(Ei=>{let Yo=!1;return(0,y.of)(Ei).pipe(function(Ye,Ie){return(0,De.zg)(Ce=>{const{targetSnapshot:ot,guards:{canActivateChecks:Et}}=Ce;if(!Et.length)return(0,y.of)(Ce);let qt=0;return(0,d.D)(Et).pipe((0,ie.b)(Hn=>function(Ye,Ie,Ce,ot){return function(Ye,Ie,Ce,ot){const Et=Object.keys(Ye);if(0===Et.length)return(0,y.of)({});const qt={};return(0,d.D)(Et).pipe((0,De.zg)(Hn=>function(Ye,Ie,Ce,ot){const Et=hs(Ye,Ie,ot);return zn(Et.resolve?Et.resolve(Ie,Ce):Et(Ie,Ce))}(Ye[Hn],Ie,Ce,ot).pipe((0,ce.b)(Xn=>{qt[Hn]=Xn}))),(0,lt.h)(1),(0,De.zg)(()=>Object.keys(qt).length===Et.length?(0,y.of)(qt):e.E))}(Ye._resolve,Ye,Ie,ot).pipe((0,K.U)(qt=>(Ye._resolvedData=qt,Ye.data=Object.assign(Object.assign({},Ye.data),Ar(Ye,Ce).resolve),null)))}(Hn.route,ot,Ye,Ie)),(0,ce.b)(()=>qt++),(0,lt.h)(1),(0,De.zg)(Hn=>qt===Et.length?(0,y.of)(Ce):e.E))})}(this.paramsInheritanceStrategy,this.ngModule.injector),(0,ce.b)({next:()=>Yo=!0,complete:()=>{Yo||(this.restoreHistory(Ei),this.cancelNavigationTransition(Ei,"At least one route resolver didn't emit any value."))}}))}),(0,ce.b)(Ei=>{const Yo=new Bt(Ei.id,this.serializeUrl(Ei.extractedUrl),this.serializeUrl(Ei.urlAfterRedirects),Ei.targetSnapshot);this.triggerEvent(Yo)}))}),pu(Xn=>{const{targetSnapshot:Ei,id:Yo,extractedUrl:$a,rawUrl:ns,extras:{skipLocationChange:Oa,replaceUrl:Hs}}=Xn;return this.hooks.afterPreactivation(Ei,{navigationId:Yo,appliedUrlTree:$a,rawUrlTree:ns,skipLocationChange:!!Oa,replaceUrl:!!Hs})}),(0,K.U)(Xn=>{const Ei=function(Ye,Ie,Ce){const ot=co(Ye,Ie._root,Ce?Ce._root:void 0);return new uo(ot,Ie)}(this.routeReuseStrategy,Xn.targetSnapshot,Xn.currentRouterState);return Object.assign(Object.assign({},Xn),{targetRouterState:Ei})}),(0,ce.b)(Xn=>{this.currentUrlTree=Xn.urlAfterRedirects,this.rawUrlTree=this.urlHandlingStrategy.merge(Xn.urlAfterRedirects,Xn.rawUrl),this.routerState=Xn.targetRouterState,"deferred"===this.urlUpdateStrategy&&(Xn.extras.skipLocationChange||this.setBrowserUrl(this.rawUrlTree,Xn),this.browserUrlTree=Xn.urlAfterRedirects)}),((Ye,Ie,Ce)=>(0,K.U)(ot=>(new bo(Ie,ot.targetRouterState,ot.currentRouterState,Ce).activate(Ye),ot)))(this.rootContexts,this.routeReuseStrategy,Xn=>this.triggerEvent(Xn)),(0,ce.b)({next(){qt=!0},complete(){qt=!0}}),(0,ze.x)(()=>{var Xn;if(!qt&&!Hn){const Ei=`Navigation ID ${Et.id} is not equal to the current navigation id ${this.navigationId}`;"replace"===this.canceledNavigationResolution?(this.restoreHistory(Et),this.cancelNavigationTransition(Et,Ei)):this.cancelNavigationTransition(Et,Ei)}(null===(Xn=this.currentNavigation)||void 0===Xn?void 0:Xn.id)===Et.id&&(this.currentNavigation=null)}),(0,Ee.K)(Xn=>{if(Hn=!0,function(Ye){return Ye&&Ye[ei]}(Xn)){const Ei=tt(Xn.url);Ei||(this.navigated=!0,this.restoreHistory(Et,!0));const Yo=new Vt(Et.id,this.serializeUrl(Et.extractedUrl),Xn.message);ot.next(Yo),Ei?setTimeout(()=>{const $a=this.urlHandlingStrategy.merge(Xn.url,this.rawUrlTree),ns={skipLocationChange:Et.extras.skipLocationChange,replaceUrl:"eager"===this.urlUpdateStrategy||ai(Et.source)};this.scheduleNavigation($a,"imperative",null,ns,{resolve:Et.resolve,reject:Et.reject,promise:Et.promise})},0):Et.resolve(!1)}else{this.restoreHistory(Et,!0);const Ei=new it(Et.id,this.serializeUrl(Et.extractedUrl),Xn);ot.next(Ei);try{Et.resolve(this.errorHandler(Xn))}catch(Yo){Et.reject(Yo)}}return e.E}))}))}resetRootComponentType(Ce){this.rootComponentType=Ce,this.routerState.root.component=this.rootComponentType}getTransition(){const Ce=this.transitions.value;return Ce.urlAfterRedirects=this.browserUrlTree,Ce}setTransition(Ce){this.transitions.next(Object.assign(Object.assign({},this.getTransition()),Ce))}initialNavigation(){this.setUpLocationChangeListener(),0===this.navigationId&&this.navigateByUrl(this.location.path(!0),{replaceUrl:!0})}setUpLocationChangeListener(){this.locationSubscription||(this.locationSubscription=this.location.subscribe(Ce=>{const ot=this.extractLocationChangeInfoFromEvent(Ce);this.shouldScheduleNavigation(this.lastLocationChangeInfo,ot)&&setTimeout(()=>{const{source:Et,state:qt,urlTree:Hn}=ot,Xn={replaceUrl:!0};if(qt){const Ei=Object.assign({},qt);delete Ei.navigationId,delete Ei.\u0275routerPageId,0!==Object.keys(Ei).length&&(Xn.state=Ei)}this.scheduleNavigation(Hn,Et,qt,Xn)},0),this.lastLocationChangeInfo=ot}))}extractLocationChangeInfoFromEvent(Ce){var ot;return{source:"popstate"===Ce.type?"popstate":"hashchange",urlTree:this.parseUrl(Ce.url),state:(null===(ot=Ce.state)||void 0===ot?void 0:ot.navigationId)?Ce.state:null,transitionId:this.getTransition().id}}shouldScheduleNavigation(Ce,ot){if(!Ce)return!0;const Et=ot.urlTree.toString()===Ce.urlTree.toString();return ot.transitionId!==Ce.transitionId||!Et||!("hashchange"===ot.source&&"popstate"===Ce.source||"popstate"===ot.source&&"hashchange"===Ce.source)}get url(){return this.serializeUrl(this.currentUrlTree)}getCurrentNavigation(){return this.currentNavigation}triggerEvent(Ce){this.events.next(Ce)}resetConfig(Ce){xi(Ce),this.config=Ce.map(ko),this.navigated=!1,this.lastSuccessfulId=-1}ngOnDestroy(){this.dispose()}dispose(){this.transitions.complete(),this.locationSubscription&&(this.locationSubscription.unsubscribe(),this.locationSubscription=void 0),this.disposed=!0}createUrlTree(Ce,ot={}){const{relativeTo:Et,queryParams:qt,fragment:Hn,queryParamsHandling:Xn,preserveFragment:Ei}=ot,Yo=Et||this.routerState.root,$a=Ei?this.currentUrlTree.fragment:Hn;let ns=null;switch(Xn){case"merge":ns=Object.assign(Object.assign({},this.currentUrlTree.queryParams),qt);break;case"preserve":ns=this.currentUrlTree.queryParams;break;default:ns=qt||null}return null!==ns&&(ns=this.removeEmptyProps(ns)),function(Ye,Ie,Ce,ot,Et){if(0===Ce.length)return us(Ie.root,Ie.root,Ie,ot,Et);const qt=function(Ye){if("string"==typeof Ye[0]&&1===Ye.length&&"/"===Ye[0])return new el(!0,0,Ye);let Ie=0,Ce=!1;const ot=Ye.reduce((Et,qt,Hn)=>{if("object"==typeof qt&&null!=qt){if(qt.outlets){const Xn={};return Wt(qt.outlets,(Ei,Yo)=>{Xn[Yo]="string"==typeof Ei?Ei.split("/"):Ei}),[...Et,{outlets:Xn}]}if(qt.segmentPath)return[...Et,qt.segmentPath]}return"string"!=typeof qt?[...Et,qt]:0===Hn?(qt.split("/").forEach((Xn,Ei)=>{0==Ei&&"."===Xn||(0==Ei&&""===Xn?Ce=!0:".."===Xn?Ie++:""!=Xn&&Et.push(Xn))}),Et):[...Et,qt]},[]);return new el(Ce,Ie,ot)}(Ce);if(qt.toRoot())return us(Ie.root,new Jt([],{}),Ie,ot,Et);const Hn=function(Ye,Ie,Ce){if(Ye.isAbsolute)return new $s(Ie.root,!0,0);if(-1===Ce.snapshot._lastPathIndex){const qt=Ce.snapshot._urlSegment;return new $s(qt,qt===Ie.root,0)}const ot=ta(Ye.commands[0])?0:1;return function(Ye,Ie,Ce){let ot=Ye,Et=Ie,qt=Ce;for(;qt>Et;){if(qt-=Et,ot=ot.parent,!ot)throw new Error("Invalid number of '../'");Et=ot.segments.length}return new $s(ot,!1,Et-qt)}(Ce.snapshot._urlSegment,Ce.snapshot._lastPathIndex+ot,Ye.numberOfDoubleDots)}(qt,Ie,Ye),Xn=Hn.processChildren?Ao(Hn.segmentGroup,Hn.index,qt.commands):Ya(Hn.segmentGroup,Hn.index,qt.commands);return us(Hn.segmentGroup,Xn,Ie,ot,Et)}(Yo,this.currentUrlTree,Ce,ns,null!=$a?$a:null)}navigateByUrl(Ce,ot={skipLocationChange:!1}){const Et=tt(Ce)?Ce:this.parseUrl(Ce),qt=this.urlHandlingStrategy.merge(Et,this.rawUrlTree);return this.scheduleNavigation(qt,"imperative",null,ot)}navigate(Ce,ot={skipLocationChange:!1}){return function(Ye){for(let Ie=0;Ie{const qt=Ce[Et];return null!=qt&&(ot[Et]=qt),ot},{})}processNavigations(){this.navigations.subscribe(Ce=>{this.navigated=!0,this.lastSuccessfulId=Ce.id,this.currentPageId=Ce.targetPageId,this.events.next(new He(Ce.id,this.serializeUrl(Ce.extractedUrl),this.serializeUrl(this.currentUrlTree))),this.lastSuccessfulNavigation=this.currentNavigation,Ce.resolve(!0)},Ce=>{this.console.warn(`Unhandled Navigation Error: ${Ce}`)})}scheduleNavigation(Ce,ot,Et,qt,Hn){var Xn,Ei;if(this.disposed)return Promise.resolve(!1);const Yo=this.getTransition(),$a=ai(ot)&&Yo&&!ai(Yo.source),Hs=(this.lastSuccessfulId===Yo.id||this.currentNavigation?Yo.rawUrl:Yo.urlAfterRedirects).toString()===Ce.toString();if($a&&Hs)return Promise.resolve(!0);let fc,ga,Ol;Hn?(fc=Hn.resolve,ga=Hn.reject,Ol=Hn.promise):Ol=new Promise((ja,ul)=>{fc=ja,ga=ul});const ll=++this.navigationId;let Za;return"computed"===this.canceledNavigationResolution?(0===this.currentPageId&&(Et=this.location.getState()),Za=Et&&Et.\u0275routerPageId?Et.\u0275routerPageId:qt.replaceUrl||qt.skipLocationChange?null!==(Xn=this.browserPageId)&&void 0!==Xn?Xn:0:(null!==(Ei=this.browserPageId)&&void 0!==Ei?Ei:0)+1):Za=0,this.setTransition({id:ll,targetPageId:Za,source:ot,restoredState:Et,currentUrlTree:this.currentUrlTree,currentRawUrl:this.rawUrlTree,rawUrl:Ce,extras:qt,resolve:fc,reject:ga,promise:Ol,currentSnapshot:this.routerState.snapshot,currentRouterState:this.routerState}),Ol.catch(ja=>Promise.reject(ja))}setBrowserUrl(Ce,ot){const Et=this.urlSerializer.serialize(Ce),qt=Object.assign(Object.assign({},ot.extras.state),this.generateNgRouterState(ot.id,ot.targetPageId));this.location.isCurrentPathEqualTo(Et)||ot.extras.replaceUrl?this.location.replaceState(Et,"",qt):this.location.go(Et,"",qt)}restoreHistory(Ce,ot=!1){var Et,qt;if("computed"===this.canceledNavigationResolution){const Hn=this.currentPageId-Ce.targetPageId;"popstate"!==Ce.source&&"eager"!==this.urlUpdateStrategy&&this.currentUrlTree!==(null===(Et=this.currentNavigation)||void 0===Et?void 0:Et.finalUrl)||0===Hn?this.currentUrlTree===(null===(qt=this.currentNavigation)||void 0===qt?void 0:qt.finalUrl)&&0===Hn&&(this.resetState(Ce),this.browserUrlTree=Ce.currentUrlTree,this.resetUrlToCurrentUrlTree()):this.location.historyGo(Hn)}else"replace"===this.canceledNavigationResolution&&(ot&&this.resetState(Ce),this.resetUrlToCurrentUrlTree())}resetState(Ce){this.routerState=Ce.currentRouterState,this.currentUrlTree=Ce.currentUrlTree,this.rawUrlTree=this.urlHandlingStrategy.merge(this.currentUrlTree,Ce.rawUrl)}resetUrlToCurrentUrlTree(){this.location.replaceState(this.urlSerializer.serialize(this.rawUrlTree),"",this.generateNgRouterState(this.lastSuccessfulId,this.currentPageId))}cancelNavigationTransition(Ce,ot){const Et=new Vt(Ce.id,this.serializeUrl(Ce.extractedUrl),ot);this.triggerEvent(Et),Ce.resolve(!1)}generateNgRouterState(Ce,ot){return"computed"===this.canceledNavigationResolution?{navigationId:Ce,\u0275routerPageId:ot}:{navigationId:Ce}}}return Ye.\u0275fac=function(Ce){return new(Ce||Ye)(u.LFG(u.DyG),u.LFG(Yt),u.LFG(V),u.LFG(r.Ye),u.LFG(u.zs3),u.LFG(u.v3s),u.LFG(u.Sil),u.LFG(void 0))},Ye.\u0275prov=u.Yz7({token:Ye,factory:Ye.\u0275fac}),Ye})();function ai(Ye){return"imperative"!==Ye}let Io=(()=>{class Ye{constructor(Ce,ot,Et,qt,Hn){this.router=Ce,this.route=ot,this.commands=[],this.onChanges=new J.xQ,null==Et&&qt.setAttribute(Hn.nativeElement,"tabindex","0")}ngOnChanges(Ce){this.onChanges.next(this)}set routerLink(Ce){this.commands=null!=Ce?Array.isArray(Ce)?Ce:[Ce]:[]}onClick(){const Ce={skipLocationChange:In(this.skipLocationChange),replaceUrl:In(this.replaceUrl),state:this.state};return this.router.navigateByUrl(this.urlTree,Ce),!0}get urlTree(){return this.router.createUrlTree(this.commands,{relativeTo:void 0!==this.relativeTo?this.relativeTo:this.route,queryParams:this.queryParams,fragment:this.fragment,queryParamsHandling:this.queryParamsHandling,preserveFragment:In(this.preserveFragment)})}}return Ye.\u0275fac=function(Ce){return new(Ce||Ye)(u.Y36(zr),u.Y36(tr),u.$8M("tabindex"),u.Y36(u.Qsj),u.Y36(u.SBq))},Ye.\u0275dir=u.lG2({type:Ye,selectors:[["","routerLink","",5,"a",5,"area"]],hostBindings:function(Ce,ot){1&Ce&&u.NdJ("click",function(){return ot.onClick()})},inputs:{routerLink:"routerLink",queryParams:"queryParams",fragment:"fragment",queryParamsHandling:"queryParamsHandling",preserveFragment:"preserveFragment",skipLocationChange:"skipLocationChange",replaceUrl:"replaceUrl",state:"state",relativeTo:"relativeTo"},features:[u.TTD]}),Ye})(),Fo=(()=>{class Ye{constructor(Ce,ot,Et){this.router=Ce,this.route=ot,this.locationStrategy=Et,this.commands=[],this.onChanges=new J.xQ,this.subscription=Ce.events.subscribe(qt=>{qt instanceof He&&this.updateTargetUrlAndHref()})}set routerLink(Ce){this.commands=null!=Ce?Array.isArray(Ce)?Ce:[Ce]:[]}ngOnChanges(Ce){this.updateTargetUrlAndHref(),this.onChanges.next(this)}ngOnDestroy(){this.subscription.unsubscribe()}onClick(Ce,ot,Et,qt,Hn){if(0!==Ce||ot||Et||qt||Hn||"string"==typeof this.target&&"_self"!=this.target)return!0;const Xn={skipLocationChange:In(this.skipLocationChange),replaceUrl:In(this.replaceUrl),state:this.state};return this.router.navigateByUrl(this.urlTree,Xn),!1}updateTargetUrlAndHref(){this.href=this.locationStrategy.prepareExternalUrl(this.router.serializeUrl(this.urlTree))}get urlTree(){return this.router.createUrlTree(this.commands,{relativeTo:void 0!==this.relativeTo?this.relativeTo:this.route,queryParams:this.queryParams,fragment:this.fragment,queryParamsHandling:this.queryParamsHandling,preserveFragment:In(this.preserveFragment)})}}return Ye.\u0275fac=function(Ce){return new(Ce||Ye)(u.Y36(zr),u.Y36(tr),u.Y36(r.S$))},Ye.\u0275dir=u.lG2({type:Ye,selectors:[["a","routerLink",""],["area","routerLink",""]],hostVars:2,hostBindings:function(Ce,ot){1&Ce&&u.NdJ("click",function(qt){return ot.onClick(qt.button,qt.ctrlKey,qt.shiftKey,qt.altKey,qt.metaKey)}),2&Ce&&(u.Ikx("href",ot.href,u.LSH),u.uIk("target",ot.target))},inputs:{routerLink:"routerLink",target:"target",queryParams:"queryParams",fragment:"fragment",queryParamsHandling:"queryParamsHandling",preserveFragment:"preserveFragment",skipLocationChange:"skipLocationChange",replaceUrl:"replaceUrl",state:"state",relativeTo:"relativeTo"},features:[u.TTD]}),Ye})();function In(Ye){return""===Ye||!!Ye}let Cr=(()=>{class Ye{constructor(Ce,ot,Et,qt,Hn,Xn){this.router=Ce,this.element=ot,this.renderer=Et,this.cdr=qt,this.link=Hn,this.linkWithHref=Xn,this.classes=[],this.isActive=!1,this.routerLinkActiveOptions={exact:!1},this.routerEventsSubscription=Ce.events.subscribe(Ei=>{Ei instanceof He&&this.update()})}ngAfterContentInit(){(0,y.of)(this.links.changes,this.linksWithHrefs.changes,(0,y.of)(null)).pipe((0,Be.J)()).subscribe(Ce=>{this.update(),this.subscribeToEachLinkOnChanges()})}subscribeToEachLinkOnChanges(){var Ce;null===(Ce=this.linkInputChangesSubscription)||void 0===Ce||Ce.unsubscribe();const ot=[...this.links.toArray(),...this.linksWithHrefs.toArray(),this.link,this.linkWithHref].filter(Et=>!!Et).map(Et=>Et.onChanges);this.linkInputChangesSubscription=(0,d.D)(ot).pipe((0,Be.J)()).subscribe(Et=>{this.isActive!==this.isLinkActive(this.router)(Et)&&this.update()})}set routerLinkActive(Ce){const ot=Array.isArray(Ce)?Ce:Ce.split(" ");this.classes=ot.filter(Et=>!!Et)}ngOnChanges(Ce){this.update()}ngOnDestroy(){var Ce;this.routerEventsSubscription.unsubscribe(),null===(Ce=this.linkInputChangesSubscription)||void 0===Ce||Ce.unsubscribe()}update(){!this.links||!this.linksWithHrefs||!this.router.navigated||Promise.resolve().then(()=>{const Ce=this.hasActiveLinks();this.isActive!==Ce&&(this.isActive=Ce,this.cdr.markForCheck(),this.classes.forEach(ot=>{Ce?this.renderer.addClass(this.element.nativeElement,ot):this.renderer.removeClass(this.element.nativeElement,ot)}))})}isLinkActive(Ce){const ot=function(Ye){return!!Ye.paths}(this.routerLinkActiveOptions)?this.routerLinkActiveOptions:this.routerLinkActiveOptions.exact||!1;return Et=>Ce.isActive(Et.urlTree,ot)}hasActiveLinks(){const Ce=this.isLinkActive(this.router);return this.link&&Ce(this.link)||this.linkWithHref&&Ce(this.linkWithHref)||this.links.some(Ce)||this.linksWithHrefs.some(Ce)}}return Ye.\u0275fac=function(Ce){return new(Ce||Ye)(u.Y36(zr),u.Y36(u.SBq),u.Y36(u.Qsj),u.Y36(u.sBO),u.Y36(Io,8),u.Y36(Fo,8))},Ye.\u0275dir=u.lG2({type:Ye,selectors:[["","routerLinkActive",""]],contentQueries:function(Ce,ot,Et){if(1&Ce&&(u.Suo(Et,Io,5),u.Suo(Et,Fo,5)),2&Ce){let qt;u.iGM(qt=u.CRH())&&(ot.links=qt),u.iGM(qt=u.CRH())&&(ot.linksWithHrefs=qt)}},inputs:{routerLinkActiveOptions:"routerLinkActiveOptions",routerLinkActive:"routerLinkActive"},exportAs:["routerLinkActive"],features:[u.TTD]}),Ye})(),ao=(()=>{class Ye{constructor(Ce,ot,Et,qt,Hn){this.parentContexts=Ce,this.location=ot,this.resolver=Et,this.changeDetector=Hn,this.activated=null,this._activatedRoute=null,this.activateEvents=new u.vpe,this.deactivateEvents=new u.vpe,this.name=qt||Lr,Ce.onChildOutletCreated(this.name,this)}ngOnDestroy(){this.parentContexts.onChildOutletDestroyed(this.name)}ngOnInit(){if(!this.activated){const Ce=this.parentContexts.getContext(this.name);Ce&&Ce.route&&(Ce.attachRef?this.attach(Ce.attachRef,Ce.route):this.activateWith(Ce.route,Ce.resolver||null))}}get isActivated(){return!!this.activated}get component(){if(!this.activated)throw new Error("Outlet is not activated");return this.activated.instance}get activatedRoute(){if(!this.activated)throw new Error("Outlet is not activated");return this._activatedRoute}get activatedRouteData(){return this._activatedRoute?this._activatedRoute.snapshot.data:{}}detach(){if(!this.activated)throw new Error("Outlet is not activated");this.location.detach();const Ce=this.activated;return this.activated=null,this._activatedRoute=null,Ce}attach(Ce,ot){this.activated=Ce,this._activatedRoute=ot,this.location.insert(Ce.hostView)}deactivate(){if(this.activated){const Ce=this.component;this.activated.destroy(),this.activated=null,this._activatedRoute=null,this.deactivateEvents.emit(Ce)}}activateWith(Ce,ot){if(this.isActivated)throw new Error("Cannot activate an already activated outlet");this._activatedRoute=Ce;const Hn=(ot=ot||this.resolver).resolveComponentFactory(Ce._futureSnapshot.routeConfig.component),Xn=this.parentContexts.getOrCreateContext(this.name).children,Ei=new ys(Ce,Xn,this.location.injector);this.activated=this.location.createComponent(Hn,this.location.length,Ei),this.changeDetector.markForCheck(),this.activateEvents.emit(this.activated.instance)}}return Ye.\u0275fac=function(Ce){return new(Ce||Ye)(u.Y36(V),u.Y36(u.s_b),u.Y36(u._Vd),u.$8M("name"),u.Y36(u.sBO))},Ye.\u0275dir=u.lG2({type:Ye,selectors:[["router-outlet"]],outputs:{activateEvents:"activate",deactivateEvents:"deactivate"},exportAs:["outlet"]}),Ye})();class ys{constructor(Ie,Ce,ot){this.route=Ie,this.childContexts=Ce,this.parent=ot}get(Ie,Ce){return Ie===tr?this.route:Ie===V?this.childContexts:this.parent.get(Ie,Ce)}}class Na{}class Tl{preload(Ie,Ce){return Ce().pipe((0,Ee.K)(()=>(0,y.of)(null)))}}class Qs{preload(Ie,Ce){return(0,y.of)(null)}}let ed=(()=>{class Ye{constructor(Ce,ot,Et,qt,Hn){this.router=Ce,this.injector=qt,this.preloadingStrategy=Hn,this.loader=new Ul(ot,Et,Yo=>Ce.triggerEvent(new bt(Yo)),Yo=>Ce.triggerEvent(new Gt(Yo)))}setUpPreloading(){this.subscription=this.router.events.pipe((0,se.h)(Ce=>Ce instanceof He),(0,ie.b)(()=>this.preload())).subscribe(()=>{})}preload(){const Ce=this.injector.get(u.h0i);return this.processRoutes(Ce,this.router.config)}ngOnDestroy(){this.subscription&&this.subscription.unsubscribe()}processRoutes(Ce,ot){const Et=[];for(const qt of ot)if(qt.loadChildren&&!qt.canLoad&&qt._loadedConfig){const Hn=qt._loadedConfig;Et.push(this.processRoutes(Hn.module,Hn.routes))}else qt.loadChildren&&!qt.canLoad?Et.push(this.preloadConfig(Ce,qt)):qt.children&&Et.push(this.processRoutes(Ce,qt.children));return(0,d.D)(Et).pipe((0,Be.J)(),(0,K.U)(qt=>{}))}preloadConfig(Ce,ot){return this.preloadingStrategy.preload(ot,()=>(ot._loadedConfig?(0,y.of)(ot._loadedConfig):this.loader.load(Ce.injector,ot)).pipe((0,De.zg)(qt=>(ot._loadedConfig=qt,this.processRoutes(qt.module,qt.routes)))))}}return Ye.\u0275fac=function(Ce){return new(Ce||Ye)(u.LFG(zr),u.LFG(u.v3s),u.LFG(u.Sil),u.LFG(u.zs3),u.LFG(Na))},Ye.\u0275prov=u.Yz7({token:Ye,factory:Ye.\u0275fac}),Ye})(),$c=(()=>{class Ye{constructor(Ce,ot,Et={}){this.router=Ce,this.viewportScroller=ot,this.options=Et,this.lastId=0,this.lastSource="imperative",this.restoredId=0,this.store={},Et.scrollPositionRestoration=Et.scrollPositionRestoration||"disabled",Et.anchorScrolling=Et.anchorScrolling||"disabled"}init(){"disabled"!==this.options.scrollPositionRestoration&&this.viewportScroller.setHistoryScrollRestoration("manual"),this.routerEventsSubscription=this.createScrollEvents(),this.scrollEventsSubscription=this.consumeScrollEvents()}createScrollEvents(){return this.router.events.subscribe(Ce=>{Ce instanceof je?(this.store[this.lastId]=this.viewportScroller.getScrollPosition(),this.lastSource=Ce.navigationTrigger,this.restoredId=Ce.restoredState?Ce.restoredState.navigationId:0):Ce instanceof He&&(this.lastId=Ce.id,this.scheduleScrollEvent(Ce,this.router.parseUrl(Ce.urlAfterRedirects).fragment))})}consumeScrollEvents(){return this.router.events.subscribe(Ce=>{Ce instanceof di&&(Ce.position?"top"===this.options.scrollPositionRestoration?this.viewportScroller.scrollToPosition([0,0]):"enabled"===this.options.scrollPositionRestoration&&this.viewportScroller.scrollToPosition(Ce.position):Ce.anchor&&"enabled"===this.options.anchorScrolling?this.viewportScroller.scrollToAnchor(Ce.anchor):"disabled"!==this.options.scrollPositionRestoration&&this.viewportScroller.scrollToPosition([0,0]))})}scheduleScrollEvent(Ce,ot){this.router.triggerEvent(new di(Ce,"popstate"===this.lastSource?this.store[this.restoredId]:null,ot))}ngOnDestroy(){this.routerEventsSubscription&&this.routerEventsSubscription.unsubscribe(),this.scrollEventsSubscription&&this.scrollEventsSubscription.unsubscribe()}}return Ye.\u0275fac=function(Ce){return new(Ce||Ye)(u.LFG(zr),u.LFG(r.EM),u.LFG(void 0))},Ye.\u0275prov=u.Yz7({token:Ye,factory:Ye.\u0275fac}),Ye})();const $i=new u.OlP("ROUTER_CONFIGURATION"),xo=new u.OlP("ROUTER_FORROOT_GUARD"),hl=[r.Ye,{provide:Yt,useClass:_n},{provide:zr,useFactory:function(Ye,Ie,Ce,ot,Et,qt,Hn,Xn={},Ei,Yo){const $a=new zr(null,Ye,Ie,Ce,ot,Et,qt,Hi(Hn));return Ei&&($a.urlHandlingStrategy=Ei),Yo&&($a.routeReuseStrategy=Yo),function(Ye,Ie){Ye.errorHandler&&(Ie.errorHandler=Ye.errorHandler),Ye.malformedUriErrorHandler&&(Ie.malformedUriErrorHandler=Ye.malformedUriErrorHandler),Ye.onSameUrlNavigation&&(Ie.onSameUrlNavigation=Ye.onSameUrlNavigation),Ye.paramsInheritanceStrategy&&(Ie.paramsInheritanceStrategy=Ye.paramsInheritanceStrategy),Ye.relativeLinkResolution&&(Ie.relativeLinkResolution=Ye.relativeLinkResolution),Ye.urlUpdateStrategy&&(Ie.urlUpdateStrategy=Ye.urlUpdateStrategy)}(Xn,$a),Xn.enableTracing&&$a.events.subscribe(ns=>{var Oa,Hs;null===(Oa=console.group)||void 0===Oa||Oa.call(console,`Router Event: ${ns.constructor.name}`),console.log(ns.toString()),console.log(ns),null===(Hs=console.groupEnd)||void 0===Hs||Hs.call(console)}),$a},deps:[Yt,V,r.Ye,u.zs3,u.v3s,u.Sil,El,$i,[class{},new u.FiY],[class{},new u.FiY]]},V,{provide:tr,useFactory:function(Ye){return Ye.routerState.root},deps:[zr]},{provide:u.v3s,useClass:u.EAV},ed,Qs,Tl,{provide:$i,useValue:{enableTracing:!1}}];function Bl(){return new u.PXZ("Router",zr)}let Js=(()=>{class Ye{constructor(Ce,ot){}static forRoot(Ce,ot){return{ngModule:Ye,providers:[hl,td(Ce),{provide:xo,useFactory:Nl,deps:[[zr,new u.FiY,new u.tp0]]},{provide:$i,useValue:ot||{}},{provide:r.S$,useFactory:Mn,deps:[r.lw,[new u.tBr(r.mr),new u.FiY],$i]},{provide:$c,useFactory:md,deps:[zr,r.EM,$i]},{provide:Na,useExisting:ot&&ot.preloadingStrategy?ot.preloadingStrategy:Qs},{provide:u.PXZ,multi:!0,useFactory:Bl},[Zc,{provide:u.ip1,multi:!0,useFactory:ic,deps:[Zc]},{provide:al,useFactory:df,deps:[Zc]},{provide:u.tb,multi:!0,useExisting:al}]]}}static forChild(Ce){return{ngModule:Ye,providers:[td(Ce)]}}}return Ye.\u0275fac=function(Ce){return new(Ce||Ye)(u.LFG(xo,8),u.LFG(zr,8))},Ye.\u0275mod=u.oAB({type:Ye}),Ye.\u0275inj=u.cJS({}),Ye})();function md(Ye,Ie,Ce){return Ce.scrollOffset&&Ie.setOffset(Ce.scrollOffset),new $c(Ye,Ie,Ce)}function Mn(Ye,Ie,Ce={}){return Ce.useHash?new r.Do(Ye,Ie):new r.b0(Ye,Ie)}function Nl(Ye){return"guarded"}function td(Ye){return[{provide:u.deG,multi:!0,useValue:Ye},{provide:El,multi:!0,useValue:Ye}]}let Zc=(()=>{class Ye{constructor(Ce){this.injector=Ce,this.initNavigation=!1,this.destroyed=!1,this.resultOfPreactivationDone=new J.xQ}appInitializer(){return this.injector.get(r.V_,Promise.resolve(null)).then(()=>{if(this.destroyed)return Promise.resolve(!0);let ot=null;const Et=new Promise(Xn=>ot=Xn),qt=this.injector.get(zr),Hn=this.injector.get($i);return"disabled"===Hn.initialNavigation?(qt.setUpLocationChangeListener(),ot(!0)):"enabled"===Hn.initialNavigation||"enabledBlocking"===Hn.initialNavigation?(qt.hooks.afterPreactivation=()=>this.initNavigation?(0,y.of)(null):(this.initNavigation=!0,ot(!0),this.resultOfPreactivationDone),qt.initialNavigation()):ot(!0),Et})}bootstrapListener(Ce){const ot=this.injector.get($i),Et=this.injector.get(ed),qt=this.injector.get($c),Hn=this.injector.get(zr),Xn=this.injector.get(u.z2F);Ce===Xn.components[0]&&(("enabledNonBlocking"===ot.initialNavigation||void 0===ot.initialNavigation)&&Hn.initialNavigation(),Et.setUpPreloading(),qt.init(),Hn.resetRootComponentType(Xn.componentTypes[0]),this.resultOfPreactivationDone.next(null),this.resultOfPreactivationDone.complete())}ngOnDestroy(){this.destroyed=!0}}return Ye.\u0275fac=function(Ce){return new(Ce||Ye)(u.LFG(u.zs3))},Ye.\u0275prov=u.Yz7({token:Ye,factory:Ye.\u0275fac}),Ye})();function ic(Ye){return Ye.appInitializer.bind(Ye)}function df(Ye){return Ye.bootstrapListener.bind(Ye)}const al=new u.OlP("Router Initializer")},43155:(v,T)=>{"use strict";T.N=void 0;var r=/^([^\w]*)(javascript|data|vbscript)/im,u=/&#(\w+)(^\w|;)?/g,p=/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim,d=/^([^:]+):/gm,e=[".","/"];T.N=function(A){var N=function(A){return A.replace(u,function(N,L){return String.fromCharCode(L)})}(A||"").replace(p,"").trim();if(!N)return"about:blank";if(function(A){return e.indexOf(A[0])>-1}(N))return N;var L=N.match(d);return L&&r.test(L[0])?"about:blank":N}},19723:(v,T,i)=>{"use strict";i.d(T,{iM:()=>rp,qr:()=>Om,xc:()=>zg});var r=i(74788),u=i(12057),p=function(I,z){return(p=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(B,be){B.__proto__=be}||function(B,be){for(var at in be)be.hasOwnProperty(at)&&(B[at]=be[at])})(I,z)};function d(I,z){function B(){this.constructor=I}p(I,z),I.prototype=null===z?Object.create(z):(B.prototype=z.prototype,new B)}var e=function(){return(e=Object.assign||function(z){for(var B,be=1,at=arguments.length;be0)&&!(at=be.next()).done;)$t.push(at.value)}catch(fr){jn={error:fr}}finally{try{at&&!at.done&&(B=be.return)&&B.call(be)}finally{if(jn)throw jn.error}}return $t}function y(){for(var I=[],z=0;z2&&Jt("box");var B=Gn(z);return new Ci(I,Jr(B),B.name,!0,B.equals)},shallowBox:function(I,z){return arguments.length>2&&Jt("shallowBox"),gr.box(I,{name:z,deep:!1})},array:function(I,z){arguments.length>2&&Jt("array");var B=Gn(z);return new Bo(I,Jr(B),B.name)},shallowArray:function(I,z){return arguments.length>2&&Jt("shallowArray"),gr.array(I,{name:z,deep:!1})},map:function(I,z){arguments.length>2&&Jt("map");var B=Gn(z);return new El(I,Jr(B),B.name)},shallowMap:function(I,z){return arguments.length>2&&Jt("shallowMap"),gr.map(I,{name:z,deep:!1})},set:function(I,z){arguments.length>2&&Jt("set");var B=Gn(z);return new Ae(I,Jr(B),B.name)},object:function(I,z,B){"string"==typeof arguments[1]&&Jt("object");var be=Gn(B);return bc({},I,z,be)},shallowObject:function(I,z){return"string"==typeof arguments[1]&&Jt("shallowObject"),gr.object(I,{},{name:z,deep:!1})},ref:br,shallow:wi,deep:_i,struct:Dr},gr=function(I,z,B){if("string"==typeof arguments[1])return _i.apply(null,arguments);if(xe(I))return I;var be=he(I)?gr.object(I,z,B):Array.isArray(I)?gr.array(I,z):He(I)?gr.map(I,z):Vt(I)?gr.set(I,z):I;if(be!==I)return be;K(!1)};function Jt(I){K("Expected one or two arguments to observable."+I+". Did you accidentally try to use observable."+I+" as decorator?")}Object.keys(yn).forEach(function(I){return gr[I]=yn[I]});var Vn=Yr(!1,function(I,z,B,be,at){!function(I,z,B){var be=ut(I);B.name=be.name+"."+z,B.context=I,be.values[z]=new tr(B),Object.defineProperty(I,z,function(I){return zr[I]||(zr[I]={configurable:on.computedConfigurable,enumerable:!1,get:function(){return ai(this).read(this,I)},set:function(z){ai(this).write(this,I,z)}})}(z))}(I,z,e({get:B.get,set:B.set},at[0]||{}))}),mr=Vn({equals:Kr.structural}),Dn=function(z,B,be){if("string"==typeof B||null!==z&&"object"==typeof z&&1===arguments.length)return Vn.apply(null,arguments);var at="object"==typeof B?B:{};return at.get=z,at.set="function"==typeof B?B:at.set,at.name=at.name||z.name||"",new tr(at)};Dn.struct=mr;var Pr=(()=>{return(I=Pr||(Pr={}))[I.NOT_TRACKING=-1]="NOT_TRACKING",I[I.UP_TO_DATE=0]="UP_TO_DATE",I[I.POSSIBLY_STALE=1]="POSSIBLY_STALE",I[I.STALE=2]="STALE",Pr;var I})(),Yt=(()=>{return(I=Yt||(Yt={}))[I.NONE=0]="NONE",I[I.LOG=1]="LOG",I[I.BREAK=2]="BREAK",Yt;var I})(),_n=function(z){this.cause=z};function Ge(I){return I instanceof _n}function kr(I){switch(I.dependenciesState){case Pr.UP_TO_DATE:return!1;case Pr.NOT_TRACKING:case Pr.STALE:return!0;case Pr.POSSIBLY_STALE:for(var z=lo(),B=I.observing,be=B.length,at=0;at0;on.computationDepth>0&&z&&K(!1),!on.allowStateChanges&&(z||"strict"===on.enforceActions)&&K(!1)}function Wr(I,z,B){var be=Co(!0);os(I),I.newObserving=new Array(I.observing.length+100),I.unboundDepsCount=0,I.runId=++on.runId;var $t,at=on.trackingDerivation;if(on.trackingDerivation=I,!0===on.disableErrorBoundaries)$t=z.call(B);else try{$t=z.call(B)}catch(jn){$t=new _n(jn)}return on.trackingDerivation=at,function(I){for(var z=I.observing,B=I.observing=I.newObserving,be=Pr.UP_TO_DATE,at=0,$t=I.unboundDepsCount,jn=0;jn<$t;jn++)0===(fr=B[jn]).diffValue&&(fr.diffValue=1,at!==jn&&(B[at]=fr),at++),fr.dependenciesState>be&&(be=fr.dependenciesState);for(B.length=at,I.newObserving=null,$t=z.length;$t--;)0===(fr=z[$t]).diffValue&&ls(fr,I),fr.diffValue=0;for(;at--;){var fr;1===(fr=B[at]).diffValue&&(fr.diffValue=0,Zs(fr,I))}be!==Pr.UP_TO_DATE&&(I.dependenciesState=be,I.onBecomeStale())}(I),Gi(be),$t}function ar(I){var z=I.observing;I.observing=[];for(var B=z.length;B--;)ls(z[B],I);I.dependenciesState=Pr.NOT_TRACKING}function Wi(I){var z=lo(),B=I();return vo(z),B}function lo(){var I=on.trackingDerivation;return on.trackingDerivation=null,I}function vo(I){on.trackingDerivation=I}function Co(I){var z=on.allowStateReads;return on.allowStateReads=I,z}function Gi(I){on.allowStateReads=I}function os(I){if(I.dependenciesState!==Pr.UP_TO_DATE){I.dependenciesState=Pr.UP_TO_DATE;for(var z=I.observing,B=z.length;B--;)z[B].lowestObserverState=Pr.UP_TO_DATE}}var jo=0,To=1;function Mi(I,z){var B=function(){return li(I,z,this,arguments)};return B.isMobxAction=!0,B}function li(I,z,B,be){var at=function(I,z,B){var be=Ws()&&!!I,at=0;if(be){at=Date.now();var $t=B&&B.length||0,jn=new Array($t);if($t>0)for(var fr=0;fr<$t;fr++)jn[fr]=B[fr];bo({type:"action",name:I,object:z,arguments:jn})}var ui=lo();ta();var Ms={prevDerivation:ui,prevAllowStateChanges:er(!0),prevAllowStateReads:Co(!0),notifySpy:be,startTime:at,actionId:To++,parentActionId:jo};return jo=Ms.actionId,Ms}(I,B,be);try{return z.apply(B,be)}catch($t){throw at.error=$t,$t}finally{!function(I){jo!==I.actionId&&K("invalid action stack. did you forget to finish an action?"),jo=I.parentActionId,void 0!==I.error&&(on.suppressReactionErrors=!0),ri(I.prevAllowStateChanges),Gi(I.prevAllowStateReads),Is(),vo(I.prevDerivation),I.notifySpy&&ps({time:Date.now()-I.startTime}),on.suppressReactionErrors=!1}(at)}}function er(I){var z=on.allowStateChanges;return on.allowStateChanges=I,z}function ri(I){on.allowStateChanges=I}var Ci=function(I){function z(B,be,at,$t,jn){void 0===at&&(at="ObservableValue@"+J()),void 0===$t&&($t=!0),void 0===jn&&(jn=Kr.default);var fr=I.call(this,at)||this;return fr.enhancer=be,fr.name=at,fr.equals=jn,fr.hasUnreportedChange=!1,fr.value=be(B,void 0,at),$t&&Ws()&&Po({type:"create",name:fr.name,newValue:""+fr.value}),fr}return d(z,I),z.prototype.dehanceValue=function(B){return void 0!==this.dehancer?this.dehancer(B):B},z.prototype.set=function(B){var be=this.value;if((B=this.prepareNewValue(B))!==on.UNCHANGED){var at=Ws();at&&bo({type:"update",name:this.name,newValue:B,oldValue:be}),this.setNewValue(B),at&&ps()}},z.prototype.prepareNewValue=function(B){if(An(this),Li(this)){var be=Aa(this,{object:this,type:"update",newValue:B});if(!be)return on.UNCHANGED;B=be.newValue}return B=this.enhancer(B,this.value,this.name),this.equals(this.value,B)?on.UNCHANGED:B},z.prototype.setNewValue=function(B){var be=this.value;this.value=B,this.reportChanged(),Ss(this)&&yl(this,{type:"update",object:this,newValue:B,oldValue:be})},z.prototype.get=function(){return this.reportObserved(),this.dehanceValue(this.value)},z.prototype.intercept=function(B){return sa(this,B)},z.prototype.observe=function(B,be){return be&&B({object:this,type:"update",newValue:this.value,oldValue:void 0}),$u(this,B)},z.prototype.toJSON=function(){return this.get()},z.prototype.toString=function(){return this.name+"["+this.value+"]"},z.prototype.valueOf=function(){return It(this.get())},z}(xt);Ci.prototype[tn()]=Ci.prototype.valueOf,Be("ObservableValue",Ci);var tr=function(){function I(z){this.dependenciesState=Pr.NOT_TRACKING,this.observing=[],this.newObserving=null,this.isBeingObserved=!1,this.isPendingUnobservation=!1,this.observers=[],this.observersIndexes={},this.diffValue=0,this.runId=0,this.lastAccessedBy=0,this.lowestObserverState=Pr.UP_TO_DATE,this.unboundDepsCount=0,this.__mapid="#"+J(),this.value=new _n(null),this.isComputing=!1,this.isRunningSetter=!1,this.isTracing=Yt.NONE,this.derivation=z.get,this.name=z.name||"ComputedValue@"+J(),z.set&&(this.setter=Mi(this.name+"-setter",z.set)),this.equals=z.equals||(z.compareStructural||z.struct?Kr.structural:Kr.default),this.scope=z.context,this.requiresReaction=!!z.requiresReaction,this.keepAlive=!!z.keepAlive}return I.prototype.onBecomeStale=function(){!function(I){if(I.lowestObserverState===Pr.UP_TO_DATE){I.lowestObserverState=Pr.POSSIBLY_STALE;for(var z=I.observers,B=z.length;B--;){var be=z[B];be.dependenciesState===Pr.UP_TO_DATE&&(be.dependenciesState=Pr.POSSIBLY_STALE,be.isTracing!==Yt.NONE&&$s(be,I),be.onBecomeStale())}}}(this)},I.prototype.onBecomeUnobserved=function(){},I.prototype.onBecomeObserved=function(){},I.prototype.get=function(){this.isComputing&&K("Cycle detected in computation "+this.name+": "+this.derivation),0!==on.inBatch||0!==this.observers.length||this.keepAlive?(us(this),kr(this)&&this.trackAndCompute()&&function(I){if(I.lowestObserverState!==Pr.STALE){I.lowestObserverState=Pr.STALE;for(var z=I.observers,B=z.length;B--;){var be=z[B];be.dependenciesState===Pr.POSSIBLY_STALE?be.dependenciesState=Pr.STALE:be.dependenciesState===Pr.UP_TO_DATE&&(I.lowestObserverState=Pr.UP_TO_DATE)}}}(this)):kr(this)&&(this.warnAboutUntrackedRead(),ta(),this.value=this.computeValue(!1),Is());var z=this.value;if(Ge(z))throw z.cause;return z},I.prototype.peek=function(){var z=this.computeValue(!1);if(Ge(z))throw z.cause;return z},I.prototype.set=function(z){if(this.setter){ee(!this.isRunningSetter,"The setter of computed value '"+this.name+"' is trying to update itself. Did you intend to update an _observable_ value, instead of the computed property?"),this.isRunningSetter=!0;try{this.setter.call(this.scope,z)}finally{this.isRunningSetter=!1}}else ee(!1,!1)},I.prototype.trackAndCompute=function(){Ws()&&Po({object:this.scope,type:"compute",name:this.name});var z=this.value,B=this.dependenciesState===Pr.NOT_TRACKING,be=this.computeValue(!0),at=B||Ge(z)||Ge(be)||!this.equals(z,be);return at&&(this.value=be),at},I.prototype.computeValue=function(z){var B;if(this.isComputing=!0,on.computationDepth++,z)B=Wr(this,this.derivation,this.scope);else if(!0===on.disableErrorBoundaries)B=this.derivation.call(this.scope);else try{B=this.derivation.call(this.scope)}catch(be){B=new _n(be)}return on.computationDepth--,this.isComputing=!1,B},I.prototype.suspend=function(){this.keepAlive||(ar(this),this.value=void 0)},I.prototype.observe=function(z,B){var be=this,at=!0,$t=void 0;return ti(function(){var jn=be.get();if(!at||B){var fr=lo();z({type:"update",object:be,newValue:jn,oldValue:$t}),vo(fr)}at=!1,$t=jn})},I.prototype.warnAboutUntrackedRead=function(){},I.prototype.toJSON=function(){return this.get()},I.prototype.toString=function(){return this.name+"["+this.derivation.toString()+"]"},I.prototype.valueOf=function(){return It(this.get())},I}();tr.prototype[tn()]=tr.prototype.valueOf;var I,Ar=Be("ComputedValue",tr),_t=function(){this.version=5,this.UNCHANGED={},this.trackingDerivation=null,this.computationDepth=0,this.runId=0,this.mobxGuid=0,this.inBatch=0,this.pendingUnobservations=[],this.pendingReactions=[],this.isRunningReactions=!1,this.allowStateChanges=!0,this.allowStateReads=!0,this.enforceActions=!1,this.spyListeners=[],this.globalReactionErrorHandlers=[],this.computedRequiresReaction=!1,this.reactionRequiresObservable=!1,this.observableRequiresReaction=!1,this.computedConfigurable=!1,this.disableErrorBoundaries=!1,this.suppressReactionErrors=!1},mt=!0,on=((I=Z()).__mobxInstanceCount>0&&!I.__mobxGlobals&&(mt=!1),I.__mobxGlobals&&I.__mobxGlobals.version!==(new _t).version&&(mt=!1),mt?I.__mobxGlobals?(I.__mobxInstanceCount+=1,I.__mobxGlobals.UNCHANGED||(I.__mobxGlobals.UNCHANGED={}),I.__mobxGlobals):(I.__mobxInstanceCount=1,I.__mobxGlobals=new _t):(setTimeout(function(){K("There are multiple, different versions of MobX active. Make sure MobX is loaded only once or use `configure({ isolateGlobalState: true })`")},1),new _t));function Zs(I,z){var B=I.observers.length;B&&(I.observersIndexes[z.__mapid]=B),I.observers[B]=z,I.lowestObserverState>z.dependenciesState&&(I.lowestObserverState=z.dependenciesState)}function ls(I,z){if(1===I.observers.length)I.observers.length=0,La(I);else{var B=I.observers,be=I.observersIndexes,at=B.pop();if(at!==z){var $t=be[z.__mapid]||0;$t?be[at.__mapid]=$t:delete be[at.__mapid],B[$t]=at}delete be[z.__mapid]}}function La(I){!1===I.isPendingUnobservation&&(I.isPendingUnobservation=!0,on.pendingUnobservations.push(I))}function ta(){on.inBatch++}function Is(){if(0==--on.inBatch){fs();for(var I=on.pendingUnobservations,z=0;z0&&La(I),!1)}function $s(I,z){if(console.log("[mobx.trace] '"+I.name+"' is invalidated due to a change in: '"+z.name+"'"),I.isTracing===Yt.BREAK){var B=[];da(function(I,z){return qc(hr(I,void 0))}(I),B,1),new Function("debugger;\n/*\nTracing '"+I.name+"'\n\nYou are entering this break point because derivation '"+I.name+"' is being traced and '"+z.name+"' is now forcing it to update.\nJust follow the stacktrace you should now see in the devtools to see precisely what piece of your code is causing this update\nThe stackframe you are looking for is at least ~6-8 stack-frames up.\n\n"+(I instanceof tr?I.derivation.toString().replace(/[*]\//g,"/"):"")+"\n\nThe dependencies for this derivation are:\n\n"+B.join("\n")+"\n*/\n ")()}}function da(I,z,B){z.length>=1e3?z.push("(and many more)"):(z.push(""+new Array(B).join("\t")+I.name),I.dependencies&&I.dependencies.forEach(function(be){return da(be,z,B+1)}))}var Il=function(){function I(z,B,be,at){void 0===z&&(z="Reaction@"+J()),void 0===at&&(at=!1),this.name=z,this.onInvalidate=B,this.errorHandler=be,this.requiresObservable=at,this.observing=[],this.newObserving=[],this.dependenciesState=Pr.NOT_TRACKING,this.diffValue=0,this.runId=0,this.unboundDepsCount=0,this.__mapid="#"+J(),this.isDisposed=!1,this._isScheduled=!1,this._isTrackPending=!1,this._isRunning=!1,this.isTracing=Yt.NONE}return I.prototype.onBecomeStale=function(){this.schedule()},I.prototype.schedule=function(){this._isScheduled||(this._isScheduled=!0,on.pendingReactions.push(this),fs())},I.prototype.isScheduled=function(){return this._isScheduled},I.prototype.runReaction=function(){if(!this.isDisposed){if(ta(),this._isScheduled=!1,kr(this)){this._isTrackPending=!0;try{this.onInvalidate(),this._isTrackPending&&Ws()&&Po({name:this.name,type:"scheduled-reaction"})}catch(z){this.reportExceptionInDerivation(z)}}Is()}},I.prototype.track=function(z){ta();var be,B=Ws();B&&(be=Date.now(),bo({name:this.name,type:"reaction"})),this._isRunning=!0;var at=Wr(this,z,void 0);this._isRunning=!1,this._isTrackPending=!1,this.isDisposed&&ar(this),Ge(at)&&this.reportExceptionInDerivation(at.cause),B&&ps({time:Date.now()-be}),Is()},I.prototype.reportExceptionInDerivation=function(z){var B=this;if(this.errorHandler)this.errorHandler(z,this);else{if(on.disableErrorBoundaries)throw z;var be="[mobx] Encountered an uncaught exception that was thrown by a reaction or observer component, in: '"+this+"'";on.suppressReactionErrors?console.warn("[mobx] (error in reaction '"+this.name+"' suppressed, fix error of causing action below)"):console.error(be,z),Ws()&&Po({type:"error",name:this.name,message:be,error:""+z}),on.globalReactionErrorHandlers.forEach(function(at){return at(z,B)})}},I.prototype.dispose=function(){this.isDisposed||(this.isDisposed=!0,this._isRunning||(ta(),ar(this),Is()))},I.prototype.getDisposer=function(){var z=this.dispose.bind(this);return z.$mobx=this,z},I.prototype.toString=function(){return"Reaction["+this.name+"]"},I.prototype.trace=function(z){void 0===z&&(z=!1),function(){for(var I=[],z=0;z0||on.isRunningReactions||function(I){I()}(Ca)}function Ca(){on.isRunningReactions=!0;for(var I=on.pendingReactions,z=0;I.length>0;){100==++z&&(console.error("Reaction doesn't converge to a stable state after 100 iterations. Probably there is a cycle in the reactive function: "+I[0]),I.splice(0));for(var B=I.splice(0),be=0,at=B.length;be",z):2===arguments.length&&"function"==typeof B?Mi(z,B):1===arguments.length&&"string"==typeof z?to(z):!0!==at?to(B).apply(null,arguments):void(z[B]=Mi(z.name||B,be.value))};function an(I,z,B){ce(I,z,Mi(z,B.bind(I)))}function ti(I,z){void 0===z&&(z=N);var at,B=z&&z.name||I.name||"Autorun@"+J();if(z.scheduler||z.delay){var $t=xi(z),jn=!1;at=new Il(B,function(){jn||(jn=!0,$t(function(){jn=!1,at.isDisposed||at.track(fr)}))},z.onError,z.requiresObservable)}else at=new Il(B,function(){this.track(fr)},z.onError,z.requiresObservable);function fr(){I(at)}return at.schedule(),at.getDisposer()}ne.bound=function(I,z,B,be){return!0===be?(an(I,z,B.value),null):B?{configurable:!0,enumerable:!1,get:function(){return an(this,z,B.value||B.initializer.call(this)),this[z]},set:$r}:{enumerable:!1,configurable:!0,set:function(at){an(this,z,at)},get:function(){}}};var pi=function(I){return I()};function xi(I){return I.scheduler?I.scheduler:I.delay?function(z){return setTimeout(z,I.delay)}:pi}function ts(I,z,B){void 0===B&&(B=N),"boolean"==typeof B&&(B={fireImmediately:B});var Ro,be=B.name||"Reaction@"+J(),at=ne(be,B.onError?function(I,z){return function(){try{return z.apply(this,arguments)}catch(B){I.call(this,B)}}}(B.onError,z):z),$t=!B.scheduler&&!B.delay,jn=xi(B),fr=!0,ui=!1,Vo=B.compareStructural?Kr.structural:B.equals||Kr.default,Ms=new Il(be,function(){fr||$t?Dl():ui||(ui=!0,jn(Dl))},B.onError,B.requiresObservable);function Dl(){if(ui=!1,!Ms.isDisposed){var ql=!1;Ms.track(function(){var eu=I(Ms);ql=fr||!Vo(Ro,eu),Ro=eu}),fr&&B.fireImmediately&&at(Ro,Ms),!fr&&!0===ql&&at(Ro,Ms),fr&&(fr=!1)}}return Ms.schedule(),Ms.getDisposer()}function Eo(I,z,B){return ba("onBecomeUnobserved",I,z,B)}function ba(I,z,B,be){var at="function"==typeof be?hr(z,B):hr(z),$t="function"==typeof be?be:B,jn=at[I];return"function"!=typeof jn?K(!1):(at[I]=function(){jn.call(this),$t.call(this)},function(){at[I]=jn})}function bc(I,z,B,be){var $t=(be=Gn(be)).defaultDecorator||(!1===be.deep?br:_i);Br(I),ut(I,be.name,$t.enhancer),ta();try{for(var at in z){var jn=Object.getOwnPropertyDescriptor(z,at),ui=(B&&at in B?B[at]:jn.get?Vn:$t)(I,at,jn,!0);ui&&Object.defineProperty(I,at,ui)}}finally{Is()}return I}function qc(I){var z={name:I.name};return I.observing&&I.observing.length>0&&(z.dependencies=function(I){var z=[];return I.forEach(function(B){-1===z.indexOf(B)&&z.push(B)}),z}(I.observing).map(qc)),z}function Me(I,z){if(null==I)return!1;if(void 0!==z){if(Cr(I)){var B=I.$mobx;return B.values&&!!B.values[z]}return!1}return Cr(I)||!!I.$mobx||Xt(I)||Ra(I)||Ar(I)}function xe(I){return 1!==arguments.length&&K(!1),Me(I)}function Fc(I){switch(I.length){case 0:return on.trackingDerivation;case 1:return hr(I[0]);case 2:return hr(I[0],I[1])}}function Cu(I,z){void 0===z&&(z=void 0),ta();try{return I.apply(z)}finally{Is()}}function Li(I){return void 0!==I.interceptors&&I.interceptors.length>0}function sa(I,z){var B=I.interceptors||(I.interceptors=[]);return B.push(z),H(function(){var be=B.indexOf(z);-1!==be&&B.splice(be,1)})}function Aa(I,z){var B=lo();try{var be=I.interceptors;if(be)for(var at=0,$t=be.length;at<$t&&(ee(!(z=be[at](z))||z.type,"Intercept handlers should return nothing or a change object"),z);at++);return z}finally{vo(B)}}function Ss(I){return void 0!==I.changeListeners&&I.changeListeners.length>0}function $u(I,z){var B=I.changeListeners||(I.changeListeners=[]);return B.push(z),H(function(){var be=B.indexOf(z);-1!==be&&B.splice(be,1)})}function yl(I,z){var B=lo(),be=I.changeListeners;if(be){for(var at=0,$t=(be=be.slice()).length;at<$t;at++)be[at](z);vo(B)}}var yu=function(){var I=!1,z={};return Object.defineProperty(z,"0",{set:function(){I=!0}}),Object.create(z)[0]=1,!1===I}(),lu=0,js=function(){};(function(I,z){void 0!==Object.setPrototypeOf?Object.setPrototypeOf(I.prototype,z):void 0!==I.prototype.__proto__?I.prototype.__proto__=z:I.prototype=z})(js,Array.prototype),Object.isFrozen(Array)&&["constructor","push","shift","concat","pop","unshift","replace","find","findIndex","splice","reverse","sort"].forEach(function(I){Object.defineProperty(js.prototype,I,{configurable:!0,writable:!0,value:Array.prototype[I]})});var Uu=function(){function I(z,B,be,at){this.array=be,this.owned=at,this.values=[],this.lastKnownLength=0,this.atom=new xt(z||"ObservableArray@"+J()),this.enhancer=function($t,jn){return B($t,jn,z+"[..]")}}return I.prototype.dehanceValue=function(z){return void 0!==this.dehancer?this.dehancer(z):z},I.prototype.dehanceValues=function(z){return void 0!==this.dehancer&&z.length>0?z.map(this.dehancer):z},I.prototype.intercept=function(z){return sa(this,z)},I.prototype.observe=function(z,B){return void 0===B&&(B=!1),B&&z({object:this.array,type:"splice",index:0,added:this.values.slice(),addedCount:this.values.length,removed:[],removedCount:0}),$u(this,z)},I.prototype.getArrayLength=function(){return this.atom.reportObserved(),this.values.length},I.prototype.setArrayLength=function(z){if("number"!=typeof z||z<0)throw new Error("[mobx.array] Out of range: "+z);var B=this.values.length;if(z!==B)if(z>B){for(var be=new Array(z-B),at=0;at0&&z+B+1>lu&&pu(z+B+1)},I.prototype.spliceWithArray=function(z,B,be){var at=this;An(this.atom);var $t=this.values.length;if(void 0===z?z=0:z>$t?z=$t:z<0&&(z=Math.max(0,$t+z)),B=1===arguments.length?$t-z:null==B?0:Math.max(0,Math.min(B,$t-z)),void 0===be&&(be=A),Li(this)){var jn=Aa(this,{object:this.array,type:"splice",index:z,removedCount:B,added:be});if(!jn)return A;B=jn.removedCount,be=jn.added}var fr=(be=0===be.length?be:be.map(function(Ro){return at.enhancer(Ro,void 0)})).length-B;this.updateArrayLength($t,fr);var ui=this.spliceItemsIntoValues(z,B,be);return(0!==B||0!==be.length)&&this.notifyArraySplice(z,be,ui),this.dehanceValues(ui)},I.prototype.spliceItemsIntoValues=function(z,B,be){var at;if(be.length<1e4)return(at=this.values).splice.apply(at,y([z,B],be));var $t=this.values.slice(z,z+B);return this.values=this.values.slice(0,z).concat(be,this.values.slice(z+B)),$t},I.prototype.notifyArrayChildUpdate=function(z,B,be){var at=!this.owned&&Ws(),$t=Ss(this),jn=$t||at?{object:this.array,type:"update",index:z,newValue:B,oldValue:be}:null;at&&bo(e({},jn,{name:this.atom.name})),this.atom.reportChanged(),$t&&yl(this,jn),at&&ps()},I.prototype.notifyArraySplice=function(z,B,be){var at=!this.owned&&Ws(),$t=Ss(this),jn=$t||at?{object:this.array,type:"splice",index:z,removed:be,added:B,removedCount:be.length,addedCount:B.length}:null;at&&bo(e({},jn,{name:this.atom.name})),this.atom.reportChanged(),$t&&yl(this,jn),at&&ps()},I}(),Bo=function(I){function z(B,be,at,$t){void 0===at&&(at="ObservableArray@"+J()),void 0===$t&&($t=!1);var jn=I.call(this)||this,fr=new Uu(at,be,jn,$t);if(lt(jn,"$mobx",fr),B&&B.length){var ui=er(!0);jn.spliceWithArray(0,0,B),ri(ui)}return yu&&Object.defineProperty(fr.array,"0",uu),jn}return d(z,I),z.prototype.intercept=function(B){return this.$mobx.intercept(B)},z.prototype.observe=function(B,be){return void 0===be&&(be=!1),this.$mobx.observe(B,be)},z.prototype.clear=function(){return this.splice(0)},z.prototype.concat=function(){for(var B=[],be=0;be-1&&(this.splice(be,1),!0)},z.prototype.move=function(B,be){function at(fr){if(fr<0)throw new Error("[mobx.array] Index out of bounds: "+fr+" is negative");var ui=this.$mobx.values.length;if(fr>=ui)throw new Error("[mobx.array] Index out of bounds: "+fr+" is not smaller than "+ui)}if(at.call(this,B),at.call(this,be),B!==be){var jn,$t=this.$mobx.values;jn=B0){if(++z>=800)return arguments[0]}else z=0;return I.apply(void 0,arguments)}}(ss.Z?function(I,z){return(0,ss.Z)(I,"toString",{configurable:!0,enumerable:!1,value:zd(z),writable:!0})}:Js),ot=function(I,z){return Ie(Nl(I,z,Js),I+"")};var Et=i(7475),qt=i(2951),Hn=i(66224);const Ei=function(I,z,B){(void 0!==B&&!(0,Hn.Z)(I[z],B)||void 0===B&&!(z in I))&&(0,qt.Z)(I,z,B)},Oa=function(z,B,be){for(var at=-1,$t=Object(z),jn=be(z),fr=jn.length;fr--;){var ui=jn[++at];if(!1===B($t[ui],ui,$t))break}return z};var Hs=i(27672),fc=i(1044),ga=i(36889),Ol=i(22595),ll=i(2172),Za=i(34654),ja=i(18402),ul=i(6539);var Qd=i(12434),Sc=i(58209),Wu=i(4214),Cc=i(46382),Xd=i(11595),Sl=Function.prototype.toString,wl=Object.prototype.hasOwnProperty,kl=Sl.call(Object);const Ja=function(I){if(!(0,ul.Z)(I)||"[object Object]"!=(0,Cc.Z)(I))return!1;var z=(0,Xd.Z)(I);if(null===z)return!0;var B=wl.call(z,"constructor")&&z.constructor;return"function"==typeof B&&B instanceof B&&Sl.call(B)==kl};var Qu=i(99436);const Oc=function(I,z){if(("constructor"!==z||"function"!=typeof I[z])&&"__proto__"!=z)return I[z]};var Au=i(57640),Iu=i(76212);const vd=function(I,z,B,be,at,$t,jn){var fr=Oc(I,B),ui=Oc(z,B),Ro=jn.get(ui);if(Ro)Ei(I,B,Ro);else{var Vo=$t?$t(fr,ui,B+"",I,z,jn):void 0,Ms=void 0===Vo;if(Ms){var Dl=(0,Za.Z)(ui),ql=!Dl&&(0,Qd.Z)(ui),eu=!Dl&&!ql&&(0,Qu.Z)(ui);Vo=ui,Dl||ql||eu?(0,Za.Z)(fr)?Vo=fr:function(I){return(0,ul.Z)(I)&&(0,ja.Z)(I)}(fr)?Vo=(0,ga.Z)(fr):ql?(Ms=!1,Vo=(0,Hs.Z)(ui,!0)):eu?(Ms=!1,Vo=(0,fc.Z)(ui,!0)):Vo=[]:Ja(ui)||(0,ll.Z)(ui)?(Vo=fr,(0,ll.Z)(fr)?Vo=function(I){return(0,Au.Z)(I,(0,Iu.Z)(I))}(fr):(!(0,Wu.Z)(fr)||(0,Sc.Z)(fr))&&(Vo=(0,Ol.Z)(ui))):Ms=!1}Ms&&(jn.set(ui,Vo),at(Vo,ui,be,$t,jn),jn.delete(ui)),Ei(I,B,Vo)}},Gc=function wc(I,z,B,be,at){I!==z&&Oa(z,function($t,jn){if(at||(at=new Et.Z),(0,Wu.Z)($t))vd(I,z,jn,B,wc,be,at);else{var fr=be?be(Oc(I,jn),$t,jn+"",I,z,at):void 0;void 0===fr&&(fr=$t),Ei(I,jn,fr)}},Iu.Z)},xc=function pf(I,z,B,be,at,$t){return(0,Wu.Z)(I)&&(0,Wu.Z)(z)&&($t.set(z,I),Gc(I,z,void 0,pf,$t),$t.delete(z)),I};var hf=i(28078);const hu=function(I,z,B){if(!(0,Wu.Z)(B))return!1;var be=typeof z;return!!("number"==be?(0,ja.Z)(B)&&(0,hf.Z)(z,B.length):"string"==be&&z in B)&&(0,Hn.Z)(B[z],I)},_f=function(I){return ot(function(z,B){var be=-1,at=B.length,$t=at>1?B[at-1]:void 0,jn=at>2?B[2]:void 0;for($t=I.length>3&&"function"==typeof $t?(at--,$t):void 0,jn&&hu(B[0],B[1],jn)&&($t=at<3?void 0:$t,at=1),z=Object(z);++beat?0:at+z),(B=B>at?at:B)<0&&(B+=at),at=z>B?0:B-z>>>0,z>>>=0;for(var $t=Array(at);++be0&&B(fr)?z>1?Pa(fr,z-1,B,be,at):(0,Xs.Z)(at,fr):be||(at[at.length]=fr)}return at},wa=function(I){return null!=I&&I.length?il(I,1):[]},gu=function(I){return Ie(Nl(I,void 0,wa),I+"")};var Nc=i(23359);const ve=gu(function(I,z){var B={};if(null==I)return B;var be=!1;z=Mc(z,function($t){return $t=R($t,I),be||(be=$t.length>1),$t}),(0,Au.Z)(I,(0,Nc.Z)(I),B),be&&(B=(0,gt.Z)(B,7,Gs));for(var at=z.length;at--;)Zo(B,z[at]);return B}),bi=function(I,z){for(var B=-1,be=null==I?0:I.length;++Bfr))return!1;var Ro=$t.get(I),Vo=$t.get(z);if(Ro&&Vo)return Ro==z&&Vo==I;var Ms=-1,Dl=!0,ql=2&B?new qf:void 0;for($t.set(I,z),$t.set(z,I);++Ms-1?at[$t?z[jn]:jn]:void 0},Rf=function(I){return"string"==typeof I||!(0,Za.Z)(I)&&(0,ul.Z)(I)&&"[object String]"==(0,Cc.Z)(I)};var ph=i(40309);const Bh=function(){return ph.Z.Date.now()};var ym=Math.max,bm=Math.min;const gg=function(I,z,B){var be=!0,at=!0;if("function"!=typeof I)throw new TypeError("Expected a function");return(0,Wu.Z)(B)&&(be="leading"in B?!!B.leading:be,at="trailing"in B?!!B.trailing:at),function(I,z,B){var be,at,$t,jn,fr,ui,Ro=0,Vo=!1,Ms=!1,Dl=!0;if("function"!=typeof I)throw new TypeError("Expected a function");function ql(Qc){var wp=be,ju=at;return be=at=void 0,Ro=Qc,jn=I.apply(ju,wp)}function eu(Qc){return Ro=Qc,fr=setTimeout(Cd,z),Vo?ql(Qc):jn}function Zd(Qc){var wp=Qc-ui;return void 0===ui||wp>=z||wp<0||Ms&&Qc-Ro>=$t}function Cd(){var Qc=Bh();if(Zd(Qc))return Op(Qc);fr=setTimeout(Cd,function(Qc){var Ic=z-(Qc-ui);return Ms?bm(Ic,$t-(Qc-Ro)):Ic}(Qc))}function Op(Qc){return fr=void 0,Dl&&be?ql(Qc):(be=at=void 0,jn)}function tf(){var Qc=Bh(),wp=Zd(Qc);if(be=arguments,at=this,ui=Qc,wp){if(void 0===fr)return eu(ui);if(Ms)return clearTimeout(fr),fr=setTimeout(Cd,z),ql(ui)}return void 0===fr&&(fr=setTimeout(Cd,z)),jn}return z=bp(z)||0,(0,Wu.Z)(B)&&(Vo=!!B.leading,$t=(Ms="maxWait"in B)?ym(bp(B.maxWait)||0,z):$t,Dl="trailing"in B?!!B.trailing:Dl),tf.cancel=function(){void 0!==fr&&clearTimeout(fr),Ro=0,be=ui=at=fr=void 0},tf.flush=function(){return void 0===fr?jn:Op(Bh())},tf}(I,z,{leading:be,maxWait:z,trailing:at})},mg=function(I){return I!=I},Em=function(I){return null==I?[]:function(I,z){return Mc(z,function(B){return I[B]})}(I,(0,ma.Z)(I))};var Yd=Math.max;const Yg=function(I,z,B,be){I=(0,ja.Z)(I)?I:Em(I),B=B&&!be?od(B):0;var at=I.length;return B<0&&(B=Yd(at+B,0)),Rf(I)?B<=at&&I.indexOf(z,B)>-1:!!at&&function(I,z,B){return z==z?function(I,z,B){for(var be=B-1,at=I.length;++be-1};var Sp=i(15427);const Jc=function(I,z,B,be){if(!(0,Wu.Z)(I))return I;for(var at=-1,$t=(z=R(z,I)).length,jn=$t-1,fr=I;null!=fr&&++at<$t;){var ui=de(z[at]),Ro=B;if("__proto__"===ui||"constructor"===ui||"prototype"===ui)return I;if(at!=jn){var Vo=fr[ui];void 0===(Ro=be?be(Vo,ui,fr):void 0)&&(Ro=(0,Wu.Z)(Vo)?Vo:(0,hf.Z)(z[at+1])?[]:{})}(0,Sp.Z)(fr,ui,Ro),fr=fr[ui]}return I},bg=function(I,z){return function(I,z,B){for(var be=-1,at=z.length,$t={};++be{class I{constructor(B,be){this.templateRef=B,this.viewContainer=be,this.templateBindings={}}ngOnInit(){this.view=this.viewContainer.createEmbeddedView(this.templateRef),this.dispose&&this.dispose(),this.shouldDetach()&&this.view.detach(),this.autoDetect(this.view)}shouldDetach(){return this.treeMobxAutorun&&this.treeMobxAutorun.detach}autoDetect(B){this.dispose=ti(()=>B.detectChanges())}ngOnDestroy(){this.dispose&&this.dispose()}}return I.\u0275fac=function(B){return new(B||I)(r.Y36(r.Rgc),r.Y36(r.s_b))},I.\u0275dir=r.lG2({type:I,selectors:[["","treeMobxAutorun",""]],inputs:{treeMobxAutorun:"treeMobxAutorun"}}),I})();const rp={TOGGLE_ACTIVE:(I,z,B)=>z&&z.toggleActivated(),TOGGLE_ACTIVE_MULTI:(I,z,B)=>z&&z.toggleActivated(!0),TOGGLE_SELECTED:(I,z,B)=>z&&z.toggleSelected(),ACTIVATE:(I,z,B)=>z.setIsActive(!0),DEACTIVATE:(I,z,B)=>z.setIsActive(!1),SELECT:(I,z,B)=>z.setIsSelected(!0),DESELECT:(I,z,B)=>z.setIsSelected(!1),FOCUS:(I,z,B)=>z.focus(),TOGGLE_EXPANDED:(I,z,B)=>z.hasChildren&&z.toggleExpanded(),EXPAND:(I,z,B)=>z.expand(),COLLAPSE:(I,z,B)=>z.collapse(),DRILL_DOWN:(I,z,B)=>I.focusDrillDown(),DRILL_UP:(I,z,B)=>I.focusDrillUp(),NEXT_NODE:(I,z,B)=>I.focusNextNode(),PREVIOUS_NODE:(I,z,B)=>I.focusPreviousNode(),MOVE_NODE:(I,z,B,{from:be,to:at})=>{B.ctrlKey?I.copyNode(be,at):I.moveNode(be,at)}},sv={mouse:{click:rp.TOGGLE_ACTIVE,dblClick:null,contextMenu:null,expanderClick:rp.TOGGLE_EXPANDED,checkboxClick:rp.TOGGLE_SELECTED,drop:rp.MOVE_NODE},keys:{39:rp.DRILL_DOWN,37:rp.DRILL_UP,40:rp.NEXT_NODE,38:rp.PREVIOUS_NODE,32:rp.TOGGLE_ACTIVE,13:rp.TOGGLE_ACTIVE}};class Mm{constructor(z={}){this.options=z,this.actionMapping=Ql({},this.options.actionMapping,sv),z.rtl&&(this.actionMapping.keys[39]=Xe(z,["actionMapping","keys",39])||rp.DRILL_UP,this.actionMapping.keys[37]=Xe(z,["actionMapping","keys",37])||rp.DRILL_DOWN)}get hasChildrenField(){return this.options.hasChildrenField||"hasChildren"}get childrenField(){return this.options.childrenField||"children"}get displayField(){return this.options.displayField||"name"}get idField(){return this.options.idField||"id"}get isExpandedField(){return this.options.isExpandedField||"isExpanded"}get getChildren(){return this.options.getChildren}get levelPadding(){return this.options.levelPadding||0}get useVirtualScroll(){return this.options.useVirtualScroll}get animateExpand(){return this.options.animateExpand}get animateSpeed(){return this.options.animateSpeed||1}get animateAcceleration(){return this.options.animateAcceleration||1.2}get scrollOnActivate(){return void 0===this.options.scrollOnActivate||this.options.scrollOnActivate}get rtl(){return!!this.options.rtl}get rootId(){return this.options.rootId}get useCheckbox(){return this.options.useCheckbox}get useTriState(){return void 0===this.options.useTriState||this.options.useTriState}get scrollContainer(){return this.options.scrollContainer}get allowDragoverStyling(){return void 0===this.options.allowDragoverStyling||this.options.allowDragoverStyling}getNodeClone(z){return this.options.getNodeClone?this.options.getNodeClone(z):ve(Object.assign({},z.data),["id"])}allowDrop(z,B,be){return this.options.allowDrop instanceof Function?this.options.allowDrop(z,B,be):void 0===this.options.allowDrop||this.options.allowDrop}allowDrag(z){return this.options.allowDrag instanceof Function?this.options.allowDrag(z):this.options.allowDrag}nodeClass(z){return this.options.nodeClass?this.options.nodeClass(z):""}nodeHeight(z){if(z.data.virtual)return 0;let B=this.options.nodeHeight||22;return"function"==typeof B&&(B=B(z)),B+(0===z.index?2:1)*this.dropSlotHeight}get dropSlotHeight(){return function(I){return"number"==typeof I||(0,ul.Z)(I)&&"[object Number]"==(0,Cc.Z)(I)}(this.options.dropSlotHeight)?this.options.dropSlotHeight:2}}const mc={toggleExpanded:"toggleExpanded",activate:"activate",deactivate:"deactivate",nodeActivate:"nodeActivate",nodeDeactivate:"nodeDeactivate",select:"select",deselect:"deselect",focus:"focus",blur:"blur",initialized:"initialized",updateData:"updateData",moveNode:"moveNode",copyNode:"copyNode",event:"event",loadNodeChildren:"loadNodeChildren",changeFilter:"changeFilter",stateChange:"stateChange"};var ef=function(I,z,B,be){var jn,at=arguments.length,$t=at<3?z:null===be?be=Object.getOwnPropertyDescriptor(z,B):be;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)$t=Reflect.decorate(I,z,B,be);else for(var fr=I.length-1;fr>=0;fr--)(jn=I[fr])&&($t=(at<3?jn($t):at>3?jn(z,B,$t):jn(z,B))||$t);return at>3&&$t&&Object.defineProperty(z,B,$t),$t},Fu=function(I,z){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(I,z)};let s1=(()=>{class I{constructor(B,be,at,$t){this.data=B,this.parent=be,this.treeModel=at,this.position=0,this.allowDrop=(jn,fr)=>this.options.allowDrop(jn,{parent:this,index:0},fr),this.allowDragoverStyling=()=>this.options.allowDragoverStyling,null==this.id&&(this.id=Math.floor(1e13*Math.random())),this.index=$t,this.getField("children")&&this._initChildren(),this.autoLoadChildren()}get isHidden(){return this.treeModel.isHidden(this)}get isExpanded(){return this.treeModel.isExpanded(this)}get isActive(){return this.treeModel.isActive(this)}get isFocused(){return this.treeModel.isNodeFocused(this)}get isSelected(){return this.isSelectable()?this.treeModel.isSelected(this):function(I,z,B){var be=(0,Za.Z)(I)?bi:Xu;return B&&hu(I,z,B)&&(z=void 0),be(I,rd(z))}(this.children,B=>B.isSelected)}get isAllSelected(){return this.isSelectable()?this.treeModel.isSelected(this):function(I,z,B){var be=(0,Za.Z)(I)?Zu:Yp;return B&&hu(I,z,B)&&(z=void 0),be(I,rd(z))}(this.children,B=>B.isAllSelected)}get isPartiallySelected(){return this.isSelected&&!this.isAllSelected}get level(){return this.parent?this.parent.level+1:0}get path(){return this.parent?[...this.parent.path,this.id]:[]}get elementRef(){throw"Element Ref is no longer supported since introducing virtual scroll\n\n You may use a template to obtain a reference to the element"}get originalNode(){return this._originalNode}get hasChildren(){return!!(this.getField("hasChildren")||this.children&&this.children.length>0)}get isCollapsed(){return!this.isExpanded}get isLeaf(){return!this.hasChildren}get isRoot(){return this.parent.data.virtual}get realParent(){return this.isRoot?null:this.parent}get options(){return this.treeModel.options}fireEvent(B){this.treeModel.fireEvent(B)}get displayField(){return this.getField("display")}get id(){return this.getField("id")}set id(B){this.setField("id",B)}getField(B){return this.data[this.options[`${B}Field`]]}setField(B,be){this.data[this.options[`${B}Field`]]=be}_findAdjacentSibling(B,be=!1){const at=this._getParentsChildren(be),$t=at.indexOf(this);return at.length>$t+B?at[$t+B]:null}findNextSibling(B=!1){return this._findAdjacentSibling(1,B)}findPreviousSibling(B=!1){return this._findAdjacentSibling(-1,B)}getVisibleChildren(){return this.visibleChildren}get visibleChildren(){return(this.children||[]).filter(B=>!B.isHidden)}getFirstChild(B=!1){return Z_((B?this.visibleChildren:this.children)||[])}getLastChild(B=!1){return nn((B?this.visibleChildren:this.children)||[])}findNextNode(B=!0,be=!1){return B&&this.isExpanded&&this.getFirstChild(be)||this.findNextSibling(be)||this.parent&&this.parent.findNextNode(!1,be)}findPreviousNode(B=!1){let be=this.findPreviousSibling(B);return be?be._getLastOpenDescendant(B):this.realParent}_getLastOpenDescendant(B=!1){const be=this.getLastChild(B);return this.isCollapsed||!be?this:be._getLastOpenDescendant(B)}_getParentsChildren(B=!1){return this.parent&&(B?this.parent.getVisibleChildren():this.parent.children)||[]}getIndexInParent(B=!1){return this._getParentsChildren(B).indexOf(this)}isDescendantOf(B){return this===B||this.parent&&this.parent.isDescendantOf(B)}getNodePadding(){return this.options.levelPadding*(this.level-1)+"px"}getClass(){return[this.options.nodeClass(this),`tree-node-level-${this.level}`].join(" ")}onDrop(B){this.mouseAction("drop",B.event,{from:B.element,to:{parent:this,index:0,dropOnNode:!0}})}allowDrag(){return this.options.allowDrag(this)}loadNodeChildren(){return this.options.getChildren?Promise.resolve(this.options.getChildren(this)).then(B=>{B&&(this.setField("children",B),this._initChildren(),this.options.useTriState&&this.treeModel.isSelected(this)&&this.setIsSelected(!0),this.children.forEach(be=>{be.getField("isExpanded")&&be.hasChildren&&be.expand()}))}).then(()=>{this.fireEvent({eventName:mc.loadNodeChildren,node:this})}):Promise.resolve()}expand(){return this.isExpanded||this.toggleExpanded(),this}collapse(){return this.isExpanded&&this.toggleExpanded(),this}doForAll(B){Promise.resolve(B(this)).then(()=>{this.children&&this.children.forEach(be=>be.doForAll(B))})}expandAll(){this.doForAll(B=>B.expand())}collapseAll(){this.doForAll(B=>B.collapse())}ensureVisible(){return this.realParent&&(this.realParent.expand(),this.realParent.ensureVisible()),this}toggleExpanded(){return this.setIsExpanded(!this.isExpanded),this}setIsExpanded(B){return this.hasChildren&&this.treeModel.setExpandedNode(this,B),this}autoLoadChildren(){this.handler=ts(()=>this.isExpanded,B=>{!this.children&&this.hasChildren&&B&&this.loadNodeChildren()},{fireImmediately:!0})}dispose(){this.children&&this.children.forEach(B=>B.dispose()),this.handler&&this.handler(),this.parent=null,this.children=null}setIsActive(B,be=!1){return this.treeModel.setActiveNode(this,B,be),B&&this.focus(this.options.scrollOnActivate),this}isSelectable(){return this.isLeaf||!this.children||!this.options.useTriState}setIsSelected(B){return this.isSelectable()?this.treeModel.setSelectedNode(this,B):this.visibleChildren.forEach(be=>be.setIsSelected(B)),this}toggleSelected(){return this.setIsSelected(!this.isSelected),this}toggleActivated(B=!1){return this.setIsActive(!this.isActive,B),this}setActiveAndVisible(B=!1){return this.setIsActive(!0,B).ensureVisible(),setTimeout(this.scrollIntoView.bind(this)),this}scrollIntoView(B=!1){this.treeModel.virtualScroll.scrollIntoView(this,B)}focus(B=!0){let be=this.treeModel.getFocusedNode();return this.treeModel.setFocusedNode(this),B&&this.scrollIntoView(),be&&this.fireEvent({eventName:mc.blur,node:be}),this.fireEvent({eventName:mc.focus,node:this}),this}blur(){let B=this.treeModel.getFocusedNode();return this.treeModel.setFocusedNode(null),B&&this.fireEvent({eventName:mc.blur,node:this}),this}setIsHidden(B){this.treeModel.setIsHidden(this,B)}hide(){this.setIsHidden(!0)}show(){this.setIsHidden(!1)}mouseAction(B,be,at=null){this.treeModel.setFocus(!0);const jn=this.options.actionMapping.mouse[B];jn&&jn(this.treeModel,this,be,at)}getSelfHeight(){return this.options.nodeHeight(this)}_initChildren(){this.children=this.getField("children").map((B,be)=>new I(B,this,this.treeModel,be))}}return ef([Dn,Fu("design:type",Object),Fu("design:paramtypes",[])],I.prototype,"isHidden",null),ef([Dn,Fu("design:type",Object),Fu("design:paramtypes",[])],I.prototype,"isExpanded",null),ef([Dn,Fu("design:type",Object),Fu("design:paramtypes",[])],I.prototype,"isActive",null),ef([Dn,Fu("design:type",Object),Fu("design:paramtypes",[])],I.prototype,"isFocused",null),ef([Dn,Fu("design:type",Object),Fu("design:paramtypes",[])],I.prototype,"isSelected",null),ef([Dn,Fu("design:type",Object),Fu("design:paramtypes",[])],I.prototype,"isAllSelected",null),ef([Dn,Fu("design:type",Object),Fu("design:paramtypes",[])],I.prototype,"isPartiallySelected",null),ef([gr,Fu("design:type",Array)],I.prototype,"children",void 0),ef([gr,Fu("design:type",Number)],I.prototype,"index",void 0),ef([gr,Fu("design:type",Object)],I.prototype,"position",void 0),ef([gr,Fu("design:type",Number)],I.prototype,"height",void 0),ef([Dn,Fu("design:type",Number),Fu("design:paramtypes",[])],I.prototype,"level",null),ef([Dn,Fu("design:type",Array),Fu("design:paramtypes",[])],I.prototype,"path",null),ef([Dn,Fu("design:type",Object),Fu("design:paramtypes",[])],I.prototype,"visibleChildren",null),ef([ne,Fu("design:type",Function),Fu("design:paramtypes",[Object]),Fu("design:returntype",void 0)],I.prototype,"setIsSelected",null),ef([ne,Fu("design:type",Function),Fu("design:paramtypes",[]),Fu("design:returntype",void 0)],I.prototype,"_initChildren",null),I})();var mu=function(I,z,B,be){var jn,at=arguments.length,$t=at<3?z:null===be?be=Object.getOwnPropertyDescriptor(z,B):be;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)$t=Reflect.decorate(I,z,B,be);else for(var fr=I.length-1;fr>=0;fr--)(jn=I[fr])&&($t=(at<3?jn($t):at>3?jn(z,B,$t):jn(z,B))||$t);return at>3&&$t&&Object.defineProperty(z,B,$t),$t},Qo=function(I,z){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(I,z)};let Kl=(()=>{class I{constructor(){this.options=new Mm,this.eventNames=Object.keys(mc),this.expandedNodeIds={},this.selectedLeafNodeIds={},this.activeNodeIds={},this.hiddenNodeIds={},this.focusedNodeId=null,this.firstUpdate=!0,this.subscriptions=[]}fireEvent(B){B.treeModel=this,this.events[B.eventName].emit(B),this.events.event.emit(B)}subscribe(B,be){const at=this.events[B].subscribe(be);this.subscriptions.push(at)}getFocusedNode(){return this.focusedNode}getActiveNode(){return this.activeNodes[0]}getActiveNodes(){return this.activeNodes}getVisibleRoots(){return this.virtualRoot.visibleChildren}getFirstRoot(B=!1){return Z_(B?this.getVisibleRoots():this.roots)}getLastRoot(B=!1){return nn(B?this.getVisibleRoots():this.roots)}get isFocused(){return I.focusedTree===this}isNodeFocused(B){return this.focusedNode===B}isEmptyTree(){return this.roots&&0===this.roots.length}get focusedNode(){return this.focusedNodeId?this.getNodeById(this.focusedNodeId):null}get expandedNodes(){const B=Object.keys(this.expandedNodeIds).filter(be=>this.expandedNodeIds[be]).map(be=>this.getNodeById(be));return kh(B)}get activeNodes(){const B=Object.keys(this.activeNodeIds).filter(be=>this.activeNodeIds[be]).map(be=>this.getNodeById(be));return kh(B)}get hiddenNodes(){const B=Object.keys(this.hiddenNodeIds).filter(be=>this.hiddenNodeIds[be]).map(be=>this.getNodeById(be));return kh(B)}get selectedLeafNodes(){const B=Object.keys(this.selectedLeafNodeIds).filter(be=>this.selectedLeafNodeIds[be]).map(be=>this.getNodeById(be));return kh(B)}getNodeByPath(B,be=null){if(!B)return null;if(be=be||this.virtualRoot,0===B.length)return be;if(!be.children)return null;const at=B.shift(),$t=fh(be.children,{id:at});return $t?this.getNodeByPath(B,$t):null}getNodeById(B){const be=B.toString();return this.getNodeBy(at=>at.id.toString()===be)}getNodeBy(B,be=null){if(!(be=be||this.virtualRoot).children)return null;const at=fh(be.children,B);if(at)return at;for(let $t of be.children){const jn=this.getNodeBy(B,$t);if(jn)return jn}}isExpanded(B){return this.expandedNodeIds[B.id]}isHidden(B){return this.hiddenNodeIds[B.id]}isActive(B){return this.activeNodeIds[B.id]}isSelected(B){return this.selectedLeafNodeIds[B.id]}ngOnDestroy(){this.dispose(),this.unsubscribeAll()}dispose(){this.virtualRoot&&this.virtualRoot.dispose()}unsubscribeAll(){this.subscriptions.forEach(B=>B.unsubscribe()),this.subscriptions=[]}setData({nodes:B,options:be=null,events:at=null}){be&&(this.options=new Mm(be)),at&&(this.events=at),B&&(this.nodes=B),this.update()}update(){let B={id:this.options.rootId,virtual:!0,[this.options.childrenField]:this.nodes};this.dispose(),this.virtualRoot=new s1(B,null,this,0),this.roots=this.virtualRoot.children,this.firstUpdate?this.roots&&(this.firstUpdate=!1,this._calculateExpandedNodes()):this.fireEvent({eventName:mc.updateData})}setFocusedNode(B){this.focusedNodeId=B?B.id:null}setFocus(B){I.focusedTree=B?this:null}doForAll(B){this.roots.forEach(be=>be.doForAll(B))}focusNextNode(){let B=this.getFocusedNode(),be=B?B.findNextNode(!0,!0):this.getFirstRoot(!0);be&&be.focus()}focusPreviousNode(){let B=this.getFocusedNode(),be=B?B.findPreviousNode(!0):this.getLastRoot(!0);be&&be.focus()}focusDrillDown(){let B=this.getFocusedNode();if(B&&B.isCollapsed&&B.hasChildren)B.toggleExpanded();else{let be=B?B.getFirstChild(!0):this.getFirstRoot(!0);be&&be.focus()}}focusDrillUp(){let B=this.getFocusedNode();if(B)if(B.isExpanded)B.toggleExpanded();else{let be=B.realParent;be&&be.focus()}}setActiveNode(B,be,at=!1){at?this._setActiveNodeMulti(B,be):this._setActiveNodeSingle(B,be),be?(B.focus(this.options.scrollOnActivate),this.fireEvent({eventName:mc.activate,node:B}),this.fireEvent({eventName:mc.nodeActivate,node:B})):(this.fireEvent({eventName:mc.deactivate,node:B}),this.fireEvent({eventName:mc.nodeDeactivate,node:B}))}setSelectedNode(B,be){this.selectedLeafNodeIds=Object.assign({},this.selectedLeafNodeIds,{[B.id]:be}),be?(B.focus(),this.fireEvent({eventName:mc.select,node:B})):this.fireEvent({eventName:mc.deselect,node:B})}setExpandedNode(B,be){this.expandedNodeIds=Object.assign({},this.expandedNodeIds,{[B.id]:be}),this.fireEvent({eventName:mc.toggleExpanded,node:B,isExpanded:be})}expandAll(){this.roots.forEach(B=>B.expandAll())}collapseAll(){this.roots.forEach(B=>B.collapseAll())}setIsHidden(B,be){this.hiddenNodeIds=Object.assign({},this.hiddenNodeIds,{[B.id]:be})}setHiddenNodeIds(B){this.hiddenNodeIds=B.reduce((be,at)=>Object.assign(be,{[at]:!0}),{})}performKeyAction(B,be){const at=this.options.actionMapping.keys[be.keyCode];return!!at&&(be.preventDefault(),at(this,B,be),!0)}filterNodes(B,be=!0){let at;if(!B)return this.clearFilter();if(Rf(B))at=jn=>-1!==jn.displayField.toLowerCase().indexOf(B.toLowerCase());else{if(!(0,Sc.Z)(B))return console.error("Don't know what to do with filter",B),void console.error("Should be either a string or function");at=B}const $t={};this.roots.forEach(jn=>this._filterNode($t,jn,at,be)),this.hiddenNodeIds=$t,this.fireEvent({eventName:mc.changeFilter})}clearFilter(){this.hiddenNodeIds={},this.fireEvent({eventName:mc.changeFilter})}moveNode(B,be){const at=B.getIndexInParent(),$t=B.parent;if(!this.canMoveNode(B,be,at))return;const jn=$t.getField("children");be.parent.getField("children")||be.parent.setField("children",[]);const fr=be.parent.getField("children"),ui=jn.splice(at,1)[0];let Ro=$t===be.parent&&be.index>at?be.index-1:be.index;fr.splice(Ro,0,ui),$t.treeModel.update(),be.parent.treeModel!==$t.treeModel&&be.parent.treeModel.update(),this.fireEvent({eventName:mc.moveNode,node:ui,to:{parent:be.parent.data,index:Ro},from:{parent:$t.data,index:at}})}copyNode(B,be){const at=B.getIndexInParent();if(!this.canMoveNode(B,be,at))return;be.parent.getField("children")||be.parent.setField("children",[]);const $t=be.parent.getField("children"),jn=this.options.getNodeClone(B);$t.splice(be.index,0,jn),B.treeModel.update(),be.parent.treeModel!==B.treeModel&&be.parent.treeModel.update(),this.fireEvent({eventName:mc.copyNode,node:jn,to:{parent:be.parent.data,index:be.index}})}getState(){return{expandedNodeIds:this.expandedNodeIds,selectedLeafNodeIds:this.selectedLeafNodeIds,activeNodeIds:this.activeNodeIds,hiddenNodeIds:this.hiddenNodeIds,focusedNodeId:this.focusedNodeId}}setState(B){!B||Object.assign(this,{expandedNodeIds:B.expandedNodeIds||{},selectedLeafNodeIds:B.selectedLeafNodeIds||{},activeNodeIds:B.activeNodeIds||{},hiddenNodeIds:B.hiddenNodeIds||{},focusedNodeId:B.focusedNodeId})}subscribeToState(B){ti(()=>B(this.getState()))}canMoveNode(B,be,at){return at||B.getIndexInParent(),(B.parent!==be.parent||at!==be.index)&&!be.parent.isDescendantOf(B)}calculateExpandedNodes(){this._calculateExpandedNodes()}_filterNode(B,be,at,$t){let jn=at(be);return be.children&&be.children.forEach(fr=>{this._filterNode(B,fr,at,$t)&&(jn=!0)}),jn||(B[be.id]=!0),$t&&jn&&be.ensureVisible(),jn}_calculateExpandedNodes(B=null){(B=B||this.virtualRoot).data[this.options.isExpandedField]&&(this.expandedNodeIds=Object.assign({},this.expandedNodeIds,{[B.id]:!0})),B.children&&B.children.forEach(be=>this._calculateExpandedNodes(be))}_setActiveNodeSingle(B,be){this.activeNodes.filter(at=>at!==B).forEach(at=>{this.fireEvent({eventName:mc.deactivate,node:at}),this.fireEvent({eventName:mc.nodeDeactivate,node:at})}),this.activeNodeIds=be?{[B.id]:!0}:{}}_setActiveNodeMulti(B,be){this.activeNodeIds=Object.assign({},this.activeNodeIds,{[B.id]:be})}}return I.\u0275fac=function(B){return new(B||I)},I.\u0275prov=r.Yz7({token:I,factory:I.\u0275fac}),I.focusedTree=null,I})();mu([gr,Qo("design:type",Array)],Kl.prototype,"roots",void 0),mu([gr,Qo("design:type",Object)],Kl.prototype,"expandedNodeIds",void 0),mu([gr,Qo("design:type",Object)],Kl.prototype,"selectedLeafNodeIds",void 0),mu([gr,Qo("design:type",Object)],Kl.prototype,"activeNodeIds",void 0),mu([gr,Qo("design:type",Object)],Kl.prototype,"hiddenNodeIds",void 0),mu([gr,Qo("design:type",Object)],Kl.prototype,"focusedNodeId",void 0),mu([gr,Qo("design:type",s1)],Kl.prototype,"virtualRoot",void 0),mu([Dn,Qo("design:type",Object),Qo("design:paramtypes",[])],Kl.prototype,"focusedNode",null),mu([Dn,Qo("design:type",Object),Qo("design:paramtypes",[])],Kl.prototype,"expandedNodes",null),mu([Dn,Qo("design:type",Object),Qo("design:paramtypes",[])],Kl.prototype,"activeNodes",null),mu([Dn,Qo("design:type",Object),Qo("design:paramtypes",[])],Kl.prototype,"hiddenNodes",null),mu([Dn,Qo("design:type",Object),Qo("design:paramtypes",[])],Kl.prototype,"selectedLeafNodes",null),mu([ne,Qo("design:type",Function),Qo("design:paramtypes",[Object]),Qo("design:returntype",void 0)],Kl.prototype,"setData",null),mu([ne,Qo("design:type",Function),Qo("design:paramtypes",[]),Qo("design:returntype",void 0)],Kl.prototype,"update",null),mu([ne,Qo("design:type",Function),Qo("design:paramtypes",[Object]),Qo("design:returntype",void 0)],Kl.prototype,"setFocusedNode",null),mu([ne,Qo("design:type",Function),Qo("design:paramtypes",[Object]),Qo("design:returntype",void 0)],Kl.prototype,"setFocus",null),mu([ne,Qo("design:type",Function),Qo("design:paramtypes",[Object]),Qo("design:returntype",void 0)],Kl.prototype,"doForAll",null),mu([ne,Qo("design:type",Function),Qo("design:paramtypes",[]),Qo("design:returntype",void 0)],Kl.prototype,"focusNextNode",null),mu([ne,Qo("design:type",Function),Qo("design:paramtypes",[]),Qo("design:returntype",void 0)],Kl.prototype,"focusPreviousNode",null),mu([ne,Qo("design:type",Function),Qo("design:paramtypes",[]),Qo("design:returntype",void 0)],Kl.prototype,"focusDrillDown",null),mu([ne,Qo("design:type",Function),Qo("design:paramtypes",[]),Qo("design:returntype",void 0)],Kl.prototype,"focusDrillUp",null),mu([ne,Qo("design:type",Function),Qo("design:paramtypes",[Object,Object,Object]),Qo("design:returntype",void 0)],Kl.prototype,"setActiveNode",null),mu([ne,Qo("design:type",Function),Qo("design:paramtypes",[Object,Object]),Qo("design:returntype",void 0)],Kl.prototype,"setSelectedNode",null),mu([ne,Qo("design:type",Function),Qo("design:paramtypes",[Object,Object]),Qo("design:returntype",void 0)],Kl.prototype,"setExpandedNode",null),mu([ne,Qo("design:type",Function),Qo("design:paramtypes",[]),Qo("design:returntype",void 0)],Kl.prototype,"expandAll",null),mu([ne,Qo("design:type",Function),Qo("design:paramtypes",[]),Qo("design:returntype",void 0)],Kl.prototype,"collapseAll",null),mu([ne,Qo("design:type",Function),Qo("design:paramtypes",[Object,Object]),Qo("design:returntype",void 0)],Kl.prototype,"setIsHidden",null),mu([ne,Qo("design:type",Function),Qo("design:paramtypes",[Object]),Qo("design:returntype",void 0)],Kl.prototype,"setHiddenNodeIds",null),mu([ne,Qo("design:type",Function),Qo("design:paramtypes",[Object,Object]),Qo("design:returntype",void 0)],Kl.prototype,"filterNodes",null),mu([ne,Qo("design:type",Function),Qo("design:paramtypes",[]),Qo("design:returntype",void 0)],Kl.prototype,"clearFilter",null),mu([ne,Qo("design:type",Function),Qo("design:paramtypes",[Object,Object]),Qo("design:returntype",void 0)],Kl.prototype,"moveNode",null),mu([ne,Qo("design:type",Function),Qo("design:paramtypes",[Object,Object]),Qo("design:returntype",void 0)],Kl.prototype,"copyNode",null),mu([ne,Qo("design:type",Function),Qo("design:paramtypes",[Object]),Qo("design:returntype",void 0)],Kl.prototype,"setState",null);let lv=(()=>{class I{constructor(){this._draggedElement=null}set(B){this._draggedElement=B}get(){return this._draggedElement}isDragging(){return!!this.get()}}return I.\u0275fac=function(B){return new(B||I)},I.\u0275prov=(0,r.Yz7)({factory:function(){return new I},token:I,providedIn:"root"}),I})();var Wh=function(I,z,B,be){var jn,at=arguments.length,$t=at<3?z:null===be?be=Object.getOwnPropertyDescriptor(z,B):be;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)$t=Reflect.decorate(I,z,B,be);else for(var fr=I.length-1;fr>=0;fr--)(jn=I[fr])&&($t=(at<3?jn($t):at>3?jn(z,B,$t):jn(z,B))||$t);return at>3&&$t&&Object.defineProperty(z,B,$t),$t},Sd=function(I,z){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(I,z)};let Wf=(()=>{class I{constructor(B){this.treeModel=B,this.yBlocks=0,this.x=0,this.viewportHeight=null,this.viewport=null,B.virtualScroll=this,this._dispose=[ti(()=>this.fixScroll())]}get y(){return 150*this.yBlocks}get totalHeight(){return this.treeModel.virtualRoot?this.treeModel.virtualRoot.height:0}fireEvent(B){this.treeModel.fireEvent(B)}init(){const B=this.recalcPositions.bind(this);B(),this._dispose=[...this._dispose,ts(()=>this.treeModel.roots,B),ts(()=>this.treeModel.expandedNodeIds,B),ts(()=>this.treeModel.hiddenNodeIds,B)],this.treeModel.subscribe(mc.loadNodeChildren,B)}isEnabled(){return this.treeModel.options.useVirtualScroll}_setYBlocks(B){this.yBlocks=B}recalcPositions(){this.treeModel.virtualRoot.height=this._getPositionAfter(this.treeModel.getVisibleRoots(),0)}_getPositionAfter(B,be){let at=be;return B.forEach($t=>{$t.position=at,at=this._getPositionAfterNode($t,at)}),at}_getPositionAfterNode(B,be){let at=B.getSelfHeight()+be;return B.children&&B.isExpanded&&(at=this._getPositionAfter(B.visibleChildren,at)),B.height=at-be,at}clear(){this._dispose.forEach(B=>B())}setViewport(B){Object.assign(this,{viewport:B,x:B.scrollLeft,yBlocks:Math.round(B.scrollTop/150),viewportHeight:B.getBoundingClientRect?B.getBoundingClientRect().height:0})}scrollIntoView(B,be,at=!0){if(B.options.scrollContainer){const $t=B.options.scrollContainer,jn=$t.getBoundingClientRect().height,fr=$t.getBoundingClientRect().top,ui=this.viewport.getBoundingClientRect().top+B.position-fr;(be||ui<$t.scrollTop||ui+B.getSelfHeight()>$t.scrollTop+jn)&&($t.scrollTop=at?ui-jn/2:ui)}else(be||B.positionthis.y+this.viewportHeight)&&this.viewport&&(this.viewport.scrollTop=at?B.position-this.viewportHeight/2:B.position,this._setYBlocks(Math.floor(this.viewport.scrollTop/150)))}getViewportNodes(B){if(!B)return[];const be=B.filter(Ro=>!Ro.isHidden);if(!this.isEnabled())return be;if(!this.viewportHeight||!be.length)return[];const at=cv(be,Ro=>Ro.position+500>this.y||Ro.position+Ro.height>this.y),$t=cv(be,Ro=>Ro.position-500>this.y+this.viewportHeight,at),jn=[];if($t-at>(1e3+this.viewportHeight)/be[0].treeModel.options.options.nodeHeight)return[];for(let Ro=at;Ro<=$t;Ro++)jn.push(be[Ro]);return jn}fixScroll(){const B=Math.max(0,this.totalHeight-this.viewportHeight);this.y<0&&this._setYBlocks(0),this.y>B&&this._setYBlocks(B/150)}}return I.\u0275fac=function(B){return new(B||I)(r.LFG(Kl))},I.\u0275prov=r.Yz7({token:I,factory:I.\u0275fac}),I})();function cv(I,z,B=0){let be=B,at=I.length-1;for(;be!==at;){let $t=Math.floor((be+at)/2);z(I[$t])?at=$t:be=be===$t?at:$t}return be}Wh([gr,Sd("design:type",Object)],Wf.prototype,"yBlocks",void 0),Wh([gr,Sd("design:type",Object)],Wf.prototype,"x",void 0),Wh([gr,Sd("design:type",Object)],Wf.prototype,"viewportHeight",void 0),Wh([Dn,Sd("design:type",Object),Sd("design:paramtypes",[])],Wf.prototype,"y",null),Wh([Dn,Sd("design:type",Object),Sd("design:paramtypes",[])],Wf.prototype,"totalHeight",null),Wh([ne,Sd("design:type",Function),Sd("design:paramtypes",[Object]),Sd("design:returntype",void 0)],Wf.prototype,"_setYBlocks",null),Wh([ne,Sd("design:type",Function),Sd("design:paramtypes",[]),Sd("design:returntype",void 0)],Wf.prototype,"recalcPositions",null),Wh([ne,Sd("design:type",Function),Sd("design:paramtypes",[Object]),Sd("design:returntype",void 0)],Wf.prototype,"setViewport",null),Wh([ne,Sd("design:type",Function),Sd("design:paramtypes",[Object,Object,Object]),Sd("design:returntype",void 0)],Wf.prototype,"scrollIntoView",null);let dv=(()=>{class I{}return I.\u0275fac=function(B){return new(B||I)},I.\u0275cmp=r.Xpm({type:I,selectors:[["tree-loading-component"]],inputs:{template:"template",node:"node"},decls:2,vars:5,consts:[[4,"ngIf"],[3,"ngTemplateOutlet","ngTemplateOutletContext"]],template:function(B,be){1&B&&(r.YNc(0,b_,2,0,"span",0),r.GkF(1,1)),2&B&&(r.Q6J("ngIf",!be.template),r.xp6(1),r.Q6J("ngTemplateOutlet",be.template)("ngTemplateOutletContext",r.VKq(3,jh,be.node)))},directives:[u.O5,u.tP],encapsulation:2}),I})(),Q_=(()=>{class I{constructor(B,be,at){this.elementRef=B,this.ngZone=be,this.virtualScroll=at,this.setViewport=gg(()=>{this.virtualScroll.setViewport(this.elementRef.nativeElement)},17),this.scrollEventHandler=this.setViewport.bind(this)}ngOnInit(){this.virtualScroll.init()}ngAfterViewInit(){setTimeout(()=>{this.setViewport(),this.virtualScroll.fireEvent({eventName:mc.initialized})});let B=this.elementRef.nativeElement;this.ngZone.runOutsideAngular(()=>{B.addEventListener("scroll",this.scrollEventHandler)})}ngOnDestroy(){this.virtualScroll.clear(),this.elementRef.nativeElement.removeEventListener("scroll",this.scrollEventHandler)}getTotalHeight(){return this.virtualScroll.isEnabled()&&this.virtualScroll.totalHeight+"px"||"auto"}}return I.\u0275fac=function(B){return new(B||I)(r.Y36(r.SBq),r.Y36(r.R0b),r.Y36(Wf))},I.\u0275cmp=r.Xpm({type:I,selectors:[["tree-viewport"]],features:[r._Bn([Wf])],ngContentSelectors:Tg,decls:1,vars:2,consts:[[4,"treeMobxAutorun"]],template:function(B,be){1&B&&(r.F$t(),r.YNc(0,E_,3,2,"ng-container",0)),2&B&&r.Q6J("treeMobxAutorun",r.DdM(1,jp))},directives:[Qr],encapsulation:2}),I})(),Om=(()=>{class I{constructor(B,be){this.treeModel=B,this.treeDraggedElement=be,B.eventNames.forEach(at=>this[at]=new r.vpe),B.subscribeToState(at=>this.stateChange.emit(at))}set nodes(B){}set options(B){}set focused(B){this.treeModel.setFocus(B)}set state(B){this.treeModel.setState(B)}onKeydown(B){if(!this.treeModel.isFocused||Yg(["input","textarea"],document.activeElement.tagName.toLowerCase()))return;const be=this.treeModel.getFocusedNode();this.treeModel.performKeyAction(be,B)}onMousedown(B){(function be(at,$t){return!at||at.localName!==$t&&be(at.parentElement,$t)})(B.target,"tree-root")&&this.treeModel.setFocus(!1)}ngOnChanges(B){(B.options||B.nodes)&&this.treeModel.setData({options:B.options&&B.options.currentValue,nodes:B.nodes&&B.nodes.currentValue,events:y_(this,this.treeModel.eventNames)})}sizeChanged(){this.viewportComponent.setViewport()}}return I.\u0275fac=function(B){return new(B||I)(r.Y36(Kl),r.Y36(lv))},I.\u0275cmp=r.Xpm({type:I,selectors:[["Tree"],["tree-root"]],contentQueries:function(B,be,at){if(1&B&&(r.Suo(at,T_,5),r.Suo(at,Zg,5),r.Suo(at,Gh,5),r.Suo(at,Sg,5)),2&B){let $t;r.iGM($t=r.CRH())&&(be.loadingTemplate=$t.first),r.iGM($t=r.CRH())&&(be.treeNodeTemplate=$t.first),r.iGM($t=r.CRH())&&(be.treeNodeWrapperTemplate=$t.first),r.iGM($t=r.CRH())&&(be.treeNodeFullTemplate=$t.first)}},viewQuery:function(B,be){if(1&B&&r.Gf(rv,5),2&B){let at;r.iGM(at=r.CRH())&&(be.viewportComponent=at.first)}},hostBindings:function(B,be){1&B&&r.NdJ("keydown",function($t){return be.onKeydown($t)},!1,r.pYS)("mousedown",function($t){return be.onMousedown($t)},!1,r.pYS)},inputs:{nodes:"nodes",options:"options",focused:"focused",state:"state"},outputs:{toggleExpanded:"toggleExpanded",activate:"activate",deactivate:"deactivate",nodeActivate:"nodeActivate",nodeDeactivate:"nodeDeactivate",select:"select",deselect:"deselect",focus:"focus",blur:"blur",updateData:"updateData",initialized:"initialized",moveNode:"moveNode",copyNode:"copyNode",loadNodeChildren:"loadNodeChildren",changeFilter:"changeFilter",event:"event",stateChange:"stateChange"},features:[r._Bn([Kl]),r.TTD],decls:5,vars:6,consts:[["viewport",""],[1,"angular-tree-component"],[3,"nodes","treeModel","templates",4,"ngIf"],["class","empty-tree-drop-slot",3,"dropIndex","node",4,"ngIf"],[3,"nodes","treeModel","templates"],[1,"empty-tree-drop-slot",3,"dropIndex","node"]],template:function(B,be){1&B&&(r.TgZ(0,"tree-viewport",null,0),r.TgZ(2,"div",1),r.YNc(3,Tm,1,8,"tree-node-collection",2),r.YNc(4,S_,1,2,"tree-node-drop-slot",3),r.qZA(),r.qZA()),2&B&&(r.xp6(2),r.ekj("node-dragging",be.treeDraggedElement.isDragging())("angular-tree-component-rtl",be.treeModel.options.rtl),r.xp6(1),r.Q6J("ngIf",be.treeModel.roots),r.xp6(1),r.Q6J("ngIf",be.treeModel.isEmptyTree()))},directives:function(){return[Q_,u.O5,Jh,Mp]},encapsulation:2}),I})(),jg=(()=>{class I{}return I.\u0275fac=function(B){return new(B||I)},I.\u0275cmp=r.Xpm({type:I,selectors:[["TreeNode"],["tree-node"]],inputs:{node:"node",index:"index",templates:"templates"},decls:1,vars:2,consts:[[4,"treeMobxAutorun"],[3,"class","tree-node","tree-node-expanded","tree-node-collapsed","tree-node-leaf","tree-node-active","tree-node-focused",4,"ngIf"],[3,"ngTemplateOutlet","ngTemplateOutletContext"],[3,"dropIndex","node",4,"ngIf"],[3,"node","index","templates"],[3,"node","templates"],[3,"dropIndex","node"]],template:function(B,be){1&B&&r.YNc(0,Se,3,8,"ng-container",0),2&B&&r.Q6J("treeMobxAutorun",r.DdM(1,jp))},directives:function(){return[Qr,u.O5,u.tP,Vp,pv,Mp]},encapsulation:2}),I})(),fv=(()=>{class I{}return I.\u0275fac=function(B){return new(B||I)},I.\u0275cmp=r.Xpm({type:I,selectors:[["tree-node-content"]],inputs:{node:"node",index:"index",template:"template"},decls:2,vars:7,consts:[[4,"ngIf"],[3,"ngTemplateOutlet","ngTemplateOutletContext"]],template:function(B,be){1&B&&(r.YNc(0,We,2,1,"span",0),r.GkF(1,1)),2&B&&(r.Q6J("ngIf",!be.template),r.xp6(1),r.Q6J("ngTemplateOutlet",be.template)("ngTemplateOutletContext",r.kEZ(3,Nt,be.node,be.node,be.index)))},directives:[u.O5,u.tP],encapsulation:2}),I})(),Mp=(()=>{class I{onDrop(B){this.node.mouseAction("drop",B.event,{from:B.element,to:{parent:this.node,index:this.dropIndex}})}allowDrop(B,be){return this.node.options.allowDrop(B,{parent:this.node,index:this.dropIndex},be)}}return I.\u0275fac=function(B){return new(B||I)},I.\u0275cmp=r.Xpm({type:I,selectors:[["TreeNodeDropSlot"],["tree-node-drop-slot"]],inputs:{node:"node",dropIndex:"dropIndex"},decls:1,vars:2,consts:[[1,"node-drop-slot",3,"treeAllowDrop","allowDragoverStyling","treeDrop"]],template:function(B,be){1&B&&(r.TgZ(0,"div",0),r.NdJ("treeDrop",function($t){return be.onDrop($t)}),r.qZA()),2&B&&r.Q6J("treeAllowDrop",be.allowDrop.bind(be))("allowDragoverStyling",!0)},directives:function(){return[Dm]},encapsulation:2}),I})(),wm=(()=>{class I{}return I.\u0275fac=function(B){return new(B||I)},I.\u0275cmp=r.Xpm({type:I,selectors:[["tree-node-expander"]],inputs:{node:"node"},decls:1,vars:2,consts:[[4,"treeMobxAutorun"],["class","toggle-children-wrapper",3,"toggle-children-wrapper-expanded","toggle-children-wrapper-collapsed","click",4,"ngIf"],["class","toggle-children-placeholder",4,"ngIf"],[1,"toggle-children-wrapper",3,"click"],[1,"toggle-children"],[1,"toggle-children-placeholder"]],template:function(B,be){1&B&&r.YNc(0,yr,3,2,"ng-container",0),2&B&&r.Q6J("treeMobxAutorun",r.DdM(1,jp))},directives:[Qr,u.O5],encapsulation:2}),I})(),pv=(()=>{class I{}return I.\u0275fac=function(B){return new(B||I)},I.\u0275cmp=r.Xpm({type:I,selectors:[["tree-node-children"]],inputs:{node:"node",templates:"templates"},decls:1,vars:2,consts:[[4,"treeMobxAutorun"],[3,"tree-children","tree-children-no-padding",4,"treeAnimateOpen","treeAnimateOpenSpeed","treeAnimateOpenAcceleration","treeAnimateOpenEnabled"],[3,"nodes","templates","treeModel",4,"ngIf"],["class","tree-node-loading",3,"padding-left","template","node",4,"ngIf"],[3,"nodes","templates","treeModel"],[1,"tree-node-loading",3,"template","node"]],template:function(B,be){1&B&&r.YNc(0,go,2,4,"ng-container",0),2&B&&r.Q6J("treeMobxAutorun",r.DdM(1,jp))},directives:function(){return[Qr,_v,u.O5,Jh,dv]},encapsulation:2}),I})();const Gg=Object.assign(function(...I){return ne(...I)},ne),hv=Object.assign(function(...I){return Dn(...I)},Dn),Cg=Object.assign(function(...I){return gr(...I)},gr);var Ap=function(I,z,B,be){var jn,at=arguments.length,$t=at<3?z:null===be?be=Object.getOwnPropertyDescriptor(z,B):be;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)$t=Reflect.decorate(I,z,B,be);else for(var fr=I.length-1;fr>=0;fr--)(jn=I[fr])&&($t=(at<3?jn($t):at>3?jn(z,B,$t):jn(z,B))||$t);return at>3&&$t&&Object.defineProperty(z,B,$t),$t},ip=function(I,z){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(I,z)};let Jh=(()=>{class I{constructor(){this._dispose=[]}get nodes(){return this._nodes}set nodes(B){this.setNodes(B)}get marginTop(){const B=this.viewportNodes&&this.viewportNodes.length&&this.viewportNodes[0];return(B&&B.parent?B.position-B.parent.position-B.parent.getSelfHeight():0)+"px"}setNodes(B){this._nodes=B}ngOnInit(){this.virtualScroll=this.treeModel.virtualScroll,this._dispose=[ts(()=>this.virtualScroll.getViewportNodes(this.nodes).map(B=>B.index),B=>{this.viewportNodes=B.map(be=>this.nodes[be])},{compareStructural:!0,fireImmediately:!0}),ts(()=>this.nodes,B=>{this.viewportNodes=this.virtualScroll.getViewportNodes(B)})]}ngOnDestroy(){this._dispose.forEach(B=>B())}trackNode(B,be){return be.id}}return I.\u0275fac=function(B){return new(B||I)},I.\u0275cmp=r.Xpm({type:I,selectors:[["tree-node-collection"]],inputs:{nodes:"nodes",treeModel:"treeModel",templates:"templates"},decls:1,vars:2,consts:[[4,"treeMobxAutorun"],[3,"node","index","templates",4,"ngFor","ngForOf","ngForTrackBy"],[3,"node","index","templates"]],template:function(B,be){1&B&&r.YNc(0,Fa,3,4,"ng-container",0),2&B&&r.Q6J("treeMobxAutorun",r.DdM(1,jp))},directives:[Qr,u.sg,jg],encapsulation:2}),I})();Ap([Cg,ip("design:type",Object)],Jh.prototype,"_nodes",void 0),Ap([Cg,ip("design:type",Array)],Jh.prototype,"viewportNodes",void 0),Ap([hv,ip("design:type",String),ip("design:paramtypes",[])],Jh.prototype,"marginTop",null),Ap([Gg,ip("design:type",Function),ip("design:paramtypes",[Object]),ip("design:returntype",void 0)],Jh.prototype,"setNodes",null);let Vp=(()=>{class I{constructor(){}}return I.\u0275fac=function(B){return new(B||I)},I.\u0275cmp=r.Xpm({type:I,selectors:[["tree-node-wrapper"]],inputs:{node:"node",index:"index",templates:"templates"},decls:2,vars:8,consts:[["class","node-wrapper",3,"padding-left",4,"ngIf"],[3,"ngTemplateOutlet","ngTemplateOutletContext"],[1,"node-wrapper"],[3,"node",4,"ngIf"],[3,"node"],[1,"node-content-wrapper",3,"treeAllowDrop","allowDragoverStyling","treeDrag","treeDragEnabled","click","dblclick","mouseover","mouseout","contextmenu","treeDrop","treeDropDragOver","treeDropDragLeave","treeDropDragEnter"],[3,"node","index","template"]],template:function(B,be){1&B&&(r.YNc(0,ni,5,15,"div",0),r.GkF(1,1)),2&B&&(r.Q6J("ngIf",!be.templates.treeNodeWrapperTemplate),r.xp6(1),r.Q6J("ngTemplateOutlet",be.templates.treeNodeWrapperTemplate)("ngTemplateOutletContext",r.l5B(3,G,be.node,be.node,be.index,be.templates)))},directives:function(){return[u.O5,u.tP,wm,Rm,Dm,fv,X_]},encapsulation:2}),I})(),X_=(()=>{class I{}return I.\u0275fac=function(B){return new(B||I)},I.\u0275cmp=r.Xpm({type:I,selectors:[["tree-node-checkbox"]],inputs:{node:"node"},decls:1,vars:2,consts:[[4,"treeMobxAutorun"],["type","checkbox",1,"tree-node-checkbox",3,"checked","indeterminate","click"]],template:function(B,be){1&B&&r.YNc(0,Tr,2,2,"ng-container",0),2&B&&r.Q6J("treeMobxAutorun",r.DdM(1,jp))},directives:[Qr],encapsulation:2}),I})();const C_="is-dragging-over",xm="is-dragging-over-disabled";let Dm=(()=>{class I{constructor(B,be,at,$t){this.el=B,this.renderer=be,this.treeDraggedElement=at,this.ngZone=$t,this.allowDragoverStyling=!0,this.onDropCallback=new r.vpe,this.onDragOverCallback=new r.vpe,this.onDragLeaveCallback=new r.vpe,this.onDragEnterCallback=new r.vpe,this._allowDrop=(jn,fr)=>!0,this.dragOverEventHandler=this.onDragOver.bind(this),this.dragEnterEventHandler=this.onDragEnter.bind(this),this.dragLeaveEventHandler=this.onDragLeave.bind(this)}set treeAllowDrop(B){this._allowDrop=B instanceof Function?B:(be,at)=>B}allowDrop(B){return this._allowDrop(this.treeDraggedElement.get(),B)}ngAfterViewInit(){let B=this.el.nativeElement;this.ngZone.runOutsideAngular(()=>{B.addEventListener("dragover",this.dragOverEventHandler),B.addEventListener("dragenter",this.dragEnterEventHandler),B.addEventListener("dragleave",this.dragLeaveEventHandler)})}ngOnDestroy(){let B=this.el.nativeElement;B.removeEventListener("dragover",this.dragOverEventHandler),B.removeEventListener("dragenter",this.dragEnterEventHandler),B.removeEventListener("dragleave",this.dragLeaveEventHandler)}onDragOver(B){if(!this.allowDrop(B))return this.allowDragoverStyling?this.addDisabledClass():void 0;this.onDragOverCallback.emit({event:B,element:this.treeDraggedElement.get()}),B.preventDefault(),this.allowDragoverStyling&&this.addClass()}onDragEnter(B){!this.allowDrop(B)||(B.preventDefault(),this.onDragEnterCallback.emit({event:B,element:this.treeDraggedElement.get()}))}onDragLeave(B){if(!this.allowDrop(B))return this.allowDragoverStyling?this.removeDisabledClass():void 0;this.onDragLeaveCallback.emit({event:B,element:this.treeDraggedElement.get()}),this.allowDragoverStyling&&this.removeClass()}onDrop(B){!this.allowDrop(B)||(B.preventDefault(),this.onDropCallback.emit({event:B,element:this.treeDraggedElement.get()}),this.allowDragoverStyling&&this.removeClass(),this.treeDraggedElement.set(null))}addClass(){this.renderer.addClass(this.el.nativeElement,C_)}removeClass(){this.renderer.removeClass(this.el.nativeElement,C_)}addDisabledClass(){this.renderer.addClass(this.el.nativeElement,xm)}removeDisabledClass(){this.renderer.removeClass(this.el.nativeElement,xm)}}return I.\u0275fac=function(B){return new(B||I)(r.Y36(r.SBq),r.Y36(r.Qsj),r.Y36(lv),r.Y36(r.R0b))},I.\u0275dir=r.lG2({type:I,selectors:[["","treeDrop",""]],hostBindings:function(B,be){1&B&&r.NdJ("drop",function($t){return be.onDrop($t)})},inputs:{allowDragoverStyling:"allowDragoverStyling",treeAllowDrop:"treeAllowDrop"},outputs:{onDropCallback:"treeDrop",onDragOverCallback:"treeDropDragOver",onDragLeaveCallback:"treeDropDragLeave",onDragEnterCallback:"treeDropDragEnter"}}),I})(),Rm=(()=>{class I{constructor(B,be,at,$t){this.el=B,this.renderer=be,this.treeDraggedElement=at,this.ngZone=$t,this.dragEventHandler=this.onDrag.bind(this)}ngAfterViewInit(){let B=this.el.nativeElement;this.ngZone.runOutsideAngular(()=>{B.addEventListener("drag",this.dragEventHandler)})}ngDoCheck(){this.renderer.setAttribute(this.el.nativeElement,"draggable",this.treeDragEnabled?"true":"false")}ngOnDestroy(){this.el.nativeElement.removeEventListener("drag",this.dragEventHandler)}onDragStart(B){B.dataTransfer.setData("text",B.target.id),this.treeDraggedElement.set(this.draggedElement),this.draggedElement.mouseAction&&this.draggedElement.mouseAction("dragStart",B)}onDrag(B){this.draggedElement.mouseAction&&this.draggedElement.mouseAction("drag",B)}onDragEnd(){this.draggedElement.mouseAction&&this.draggedElement.mouseAction("dragEnd"),this.treeDraggedElement.set(null)}}return I.\u0275fac=function(B){return new(B||I)(r.Y36(r.SBq),r.Y36(r.Qsj),r.Y36(lv),r.Y36(r.R0b))},I.\u0275dir=r.lG2({type:I,selectors:[["","treeDrag",""]],hostBindings:function(B,be){1&B&&r.NdJ("dragstart",function($t){return be.onDragStart($t)})("dragend",function(){return be.onDragEnd()})},inputs:{draggedElement:["treeDrag","draggedElement"],treeDragEnabled:"treeDragEnabled"}}),I})(),_v=(()=>{class I{constructor(B,be,at){this.renderer=B,this.templateRef=be,this.viewContainerRef=at}set isOpen(B){B?(this._show(),this.isEnabled&&!1===this._isOpen&&this._animateOpen()):this.isEnabled?this._animateClose():this._hide(),this._isOpen=!!B}_show(){this.innerElement||(this.innerElement=this.viewContainerRef.createEmbeddedView(this.templateRef).rootNodes[0])}_hide(){this.viewContainerRef.clear(),this.innerElement=null}_animateOpen(){let B=this.animateSpeed,be=this.animateAcceleration,at=0;this.renderer.setStyle(this.innerElement,"max-height","0"),setTimeout(()=>{const $t=setInterval(()=>{if(!this._isOpen||!this.innerElement)return clearInterval($t);at+=B;const jn=Math.round(at);this.renderer.setStyle(this.innerElement,"max-height",`${jn}px`);const fr=this.innerElement.getBoundingClientRect?this.innerElement.getBoundingClientRect().height:0;B*=be,be*=1.005,fr{if(this._isOpen||!this.innerElement)return clearInterval($t);at-=B,this.renderer.setStyle(this.innerElement,"max-height",`${at}px`),B*=be,be*=1.005,at<=0&&(this.viewContainerRef.clear(),this.innerElement=null,clearInterval($t))},17)}}return I.\u0275fac=function(B){return new(B||I)(r.Y36(r.Qsj),r.Y36(r.Rgc),r.Y36(r.s_b))},I.\u0275dir=r.lG2({type:I,selectors:[["","treeAnimateOpen",""]],inputs:{isOpen:["treeAnimateOpen","isOpen"],animateSpeed:["treeAnimateOpenSpeed","animateSpeed"],animateAcceleration:["treeAnimateOpenAcceleration","animateAcceleration"],isEnabled:["treeAnimateOpenEnabled","isEnabled"]}}),I})(),zg=(()=>{class I{}return I.\u0275fac=function(B){return new(B||I)},I.\u0275mod=r.oAB({type:I}),I.\u0275inj=r.cJS({providers:[],imports:[[u.ez]]}),I})()},38549:(v,T,i)=>{"use strict";i.d(T,{Kz:()=>E,xm:()=>Rl,_A:()=>nc,vL:()=>Ul,_D:()=>fa,lQ:()=>pu,VL:()=>Gl,M:()=>Yu,jt:()=>hc,TH:()=>wf,Vi:()=>Vl,XC:()=>Mc,iD:()=>oc,J4:()=>qt,FF:()=>ye,Pz:()=>xn,uN:()=>nn,nv:()=>Pn,Vx:()=>ir,Oz:()=>Xs,tO:()=>Lo,M2:()=>Kd,o8:()=>na,AX:()=>bi,dT:()=>_l,Ly:()=>zc,ZQ:()=>qf,Pm:()=>kd,UL:()=>Yf,_L:()=>Ph,xI:()=>rh,HK:()=>Bp,dR:()=>ac,ZS:()=>oh});var r=i(74788),u=i(12057),p=i(59796),d=i(56693),e=i(55197),_=i(20509);function y(...Y){if(1===Y.length){if(!(0,p.k)(Y[0]))return Y[0];Y=Y[0]}return(0,d.n)(Y,void 0).lift(new S)}class S{call(fe,w){return w.subscribe(new A(fe))}}class A extends e.L{constructor(fe){super(fe),this.hasFirst=!1,this.observables=[],this.subscriptions=[]}_next(fe){this.observables.push(fe)}_complete(){const fe=this.observables,w=fe.length;if(0===w)this.destination.complete();else{for(let X=0;Xthis.index}hasCompleted(){return this.array.length===this.index}}class H extends Z.Ds{constructor(fe,w,X){super(fe),this.parent=w,this.observable=X,this.stillUnsubscribed=!0,this.buffer=[],this.isComplete=!1}[L.hZ](){return this}next(){const fe=this.buffer;return 0===fe.length&&this.isComplete?{value:null,done:!0}:{value:fe.shift(),done:!1}}hasValue(){return this.buffer.length>0}hasCompleted(){return 0===this.buffer.length&&this.isComplete}notifyComplete(){this.buffer.length>0?(this.isComplete=!0,this.parent.notifyInactive()):this.destination.complete()}notifyNext(fe){this.buffer.push(fe),this.parent.checkIterators()}subscribe(){return(0,Z.ft)(this.observable,new Z.IY(this))}}var se=i(18891),Ee=i(59193),ie=i(25917),he=i(79765),ge=i(22759),De=i(46797),ce=i(26215),ze=(i(9112),i(17757),i(66682)),Be=i(58071);function je(...Y){return fe=>{let w;return"function"==typeof Y[Y.length-1]&&(w=Y.pop()),fe.lift(new He(Y,w))}}class He{constructor(fe,w){this.observables=fe,this.project=w}call(fe,w){return w.subscribe(new Vt(fe,this.observables,this.project))}}class Vt extends e.L{constructor(fe,w,X){super(fe),this.observables=w,this.project=X,this.toRespond=[];const ke=w.length;this.values=new Array(ke);for(let ct=0;ct0){const ct=ke.indexOf(X);-1!==ct&&ke.splice(ct,1)}}notifyComplete(){}_next(fe){if(0===this.toRespond.length){const w=[fe,...this.values];this.project?this._tryProject(w):this.destination.next(w)}}_tryProject(fe){let w;try{w=this.project.apply(this,fe)}catch(X){return void this.destination.error(X)}this.destination.next(w)}}class tn{constructor(fe){this.total=fe}call(fe,w){return w.subscribe(new It(fe,this.total))}}class It extends N.L{constructor(fe,w){super(fe),this.total=w,this.count=0}_next(fe){++this.count>this.total&&this.destination.next(fe)}}var Zt=i(46782),Ut=i(45435),Bt=i(15257),bt=i(88002),Gt=i(39761),xt=i(87519),Xt=i(43190),Zn=i(68307),Ur=i(71289),di=i(19773),Lr=i(18819),Mr=i(24751);function ki(Y,fe){if(1&Y){const w=r.EpF();r.TgZ(0,"button",1),r.NdJ("click",function(){return r.CHM(w),r.oxw().close()}),r.TgZ(1,"span",2),r._uU(2,"\xd7"),r.qZA(),r.qZA()}}const Hi=["*"],Fr=["defaultDayTemplate"],Gn=["content"];function Jr(Y,fe){if(1&Y&&r._UZ(0,"div",7),2&Y){const X=fe.currentMonth,ke=fe.selected,ct=fe.disabled,Jn=fe.focused;r.Q6J("date",fe.date)("currentMonth",X)("selected",ke)("disabled",ct)("focused",Jn)}}function _i(Y,fe){if(1&Y&&(r.TgZ(0,"div",12),r._uU(1),r.qZA()),2&Y){const w=r.oxw().$implicit,X=r.oxw(2);r.xp6(1),r.hij(" ",X.i18n.getMonthLabel(w.firstDate)," ")}}function wi(Y,fe){if(1&Y&&(r.TgZ(0,"div",9),r.YNc(1,_i,2,1,"div",10),r._UZ(2,"ngb-datepicker-month",11),r.qZA()),2&Y){const w=fe.$implicit,X=r.oxw(2);r.xp6(1),r.Q6J("ngIf","none"===X.navigation||X.displayMonths>1&&"select"===X.navigation),r.xp6(1),r.Q6J("month",w.firstDate)}}function br(Y,fe){if(1&Y&&r.YNc(0,wi,3,2,"div",8),2&Y){const w=r.oxw();r.Q6J("ngForOf",w.model.months)}}function Dr(Y,fe){if(1&Y){const w=r.EpF();r.TgZ(0,"ngb-datepicker-navigation",13),r.NdJ("navigate",function(ke){return r.CHM(w),r.oxw().onNavigateEvent(ke)})("select",function(ke){return r.CHM(w),r.oxw().onNavigateDateSelect(ke)}),r.qZA()}if(2&Y){const w=r.oxw();r.Q6J("date",w.model.firstDate)("months",w.model.months)("disabled",w.model.disabled)("showSelect","select"===w.model.navigation)("prevDisabled",w.model.prevDisabled)("nextDisabled",w.model.nextDisabled)("selectBoxes",w.model.selectBoxes)}}function gn(Y,fe){}function yn(Y,fe){}function gr(Y,fe){if(1&Y&&(r.TgZ(0,"div",5),r._uU(1),r.qZA()),2&Y){const w=r.oxw(2);r.xp6(1),r.Oqu(w.i18n.getWeekLabel())}}function Jt(Y,fe){if(1&Y&&(r.TgZ(0,"div",6),r._uU(1),r.qZA()),2&Y){const w=fe.$implicit;r.xp6(1),r.Oqu(w)}}function Vn(Y,fe){if(1&Y&&(r.TgZ(0,"div",2),r.YNc(1,gr,2,1,"div",3),r.YNc(2,Jt,2,1,"div",4),r.qZA()),2&Y){const w=r.oxw();r.xp6(1),r.Q6J("ngIf",w.datepicker.showWeekNumbers),r.xp6(1),r.Q6J("ngForOf",w.viewModel.weekdays)}}function mr(Y,fe){if(1&Y&&(r.TgZ(0,"div",11),r._uU(1),r.qZA()),2&Y){const w=r.oxw(2).$implicit,X=r.oxw();r.xp6(1),r.Oqu(X.i18n.getWeekNumerals(w.number))}}function Dn(Y,fe){}function Pr(Y,fe){if(1&Y&&r.YNc(0,Dn,0,0,"ng-template",14),2&Y){const w=r.oxw().$implicit,X=r.oxw(3);r.Q6J("ngTemplateOutlet",X.datepicker.dayTemplate)("ngTemplateOutletContext",w.context)}}function Yt(Y,fe){if(1&Y){const w=r.EpF();r.TgZ(0,"div",12),r.NdJ("click",function(ke){const Jn=r.CHM(w).$implicit;return r.oxw(3).doSelect(Jn),ke.preventDefault()}),r.YNc(1,Pr,1,2,"ng-template",13),r.qZA()}if(2&Y){const w=fe.$implicit;r.ekj("disabled",w.context.disabled)("hidden",w.hidden)("ngb-dp-today",w.context.today),r.Q6J("tabindex",w.tabindex),r.uIk("aria-label",w.ariaLabel),r.xp6(1),r.Q6J("ngIf",!w.hidden)}}function _n(Y,fe){if(1&Y&&(r.TgZ(0,"div",8),r.YNc(1,mr,2,1,"div",9),r.YNc(2,Yt,2,9,"div",10),r.qZA()),2&Y){const w=r.oxw().$implicit,X=r.oxw();r.xp6(1),r.Q6J("ngIf",X.datepicker.showWeekNumbers),r.xp6(1),r.Q6J("ngForOf",w.days)}}function Ge(Y,fe){1&Y&&r.YNc(0,_n,3,2,"div",7),2&Y&&r.Q6J("ngIf",!fe.$implicit.collapsed)}function kr(Y,fe){if(1&Y){const w=r.EpF();r.TgZ(0,"ngb-datepicker-navigation-select",7),r.NdJ("select",function(ke){return r.CHM(w),r.oxw().select.emit(ke)}),r.qZA()}if(2&Y){const w=r.oxw();r.Q6J("date",w.date)("disabled",w.disabled)("months",w.selectBoxes.months)("years",w.selectBoxes.years)}}function mi(Y,fe){1&Y&&r._UZ(0,"div",0)}function An(Y,fe){1&Y&&r._UZ(0,"div",0)}function Er(Y,fe){if(1&Y&&(r.YNc(0,mi,1,0,"div",9),r.TgZ(1,"div",10),r._uU(2),r.qZA(),r.YNc(3,An,1,0,"div",9)),2&Y){const w=fe.$implicit,X=fe.index,ke=r.oxw(2);r.Q6J("ngIf",X>0),r.xp6(2),r.hij(" ",ke.i18n.getMonthLabel(w.firstDate)," "),r.xp6(1),r.Q6J("ngIf",X!==ke.months.length-1)}}function Wr(Y,fe){if(1&Y&&r.YNc(0,Er,4,3,"ng-template",8),2&Y){const w=r.oxw();r.Q6J("ngForOf",w.months)}}const dr=["ngbDatepickerDayView",""],Fn=["month"],ar=["year"];function Wi(Y,fe){if(1&Y&&(r.TgZ(0,"option",5),r._uU(1),r.qZA()),2&Y){const w=fe.$implicit,X=r.oxw();r.Q6J("value",w),r.uIk("aria-label",X.i18n.getMonthFullName(w,null==X.date?null:X.date.year)),r.xp6(1),r.Oqu(X.i18n.getMonthShortName(w,null==X.date?null:X.date.year))}}function lo(Y,fe){if(1&Y&&(r.TgZ(0,"option",5),r._uU(1),r.qZA()),2&Y){const w=fe.$implicit,X=r.oxw();r.Q6J("value",w),r.xp6(1),r.Oqu(X.i18n.getYearNumerals(w))}}const vo=["dialog"],Co=["ngbNavOutlet",""];function Gi(Y,fe){}const os=function(Y){return{$implicit:Y}};function jo(Y,fe){if(1&Y&&(r.TgZ(0,"div",2),r.YNc(1,Gi,0,0,"ng-template",3),r.qZA()),2&Y){const w=r.oxw().$implicit,X=r.oxw();r.Q6J("item",w)("nav",X.nav)("role",X.paneRole),r.xp6(1),r.Q6J("ngTemplateOutlet",(null==w.contentTpl?null:w.contentTpl.templateRef)||null)("ngTemplateOutletContext",r.VKq(5,os,w.active||X.isPanelTransitioning(w)))}}function To(Y,fe){if(1&Y&&r.YNc(0,jo,2,7,"div",1),2&Y){const w=fe.$implicit,X=r.oxw();r.Q6J("ngIf",w.isPanelInDom()||X.isPanelTransitioning(w))}}function us(Y,fe){if(1&Y&&r._uU(0),2&Y){const w=r.oxw(2);r.Oqu(w.title)}}function ya(Y,fe){}function el(Y,fe){if(1&Y&&(r.TgZ(0,"h3",3),r.YNc(1,us,1,1,"ng-template",null,4,r.W1O),r.YNc(3,ya,0,0,"ng-template",5),r.qZA()),2&Y){const w=r.MAs(2),X=r.oxw();r.xp6(3),r.Q6J("ngTemplateOutlet",X.isTitleTemplate()?X.title:w)("ngTemplateOutletContext",X.context)}}function ca(Y,fe){if(1&Y&&(r.TgZ(0,"span"),r.SDv(1,2),r.ALo(2,"percent"),r.qZA()),2&Y){const w=r.oxw();r.xp6(2),r.pQV(r.lcZ(2,1,w.getValue()/w.max)),r.QtT(1)}}function fo(Y,fe){if(1&Y){const w=r.EpF();r.TgZ(0,"button",11),r.NdJ("click",function(){r.CHM(w);const ke=r.oxw();return ke.changeHour(ke.hourStep)}),r._UZ(1,"span",12),r.TgZ(2,"span",13),r.SDv(3,14),r.qZA(),r.qZA()}if(2&Y){const w=r.oxw();r.ekj("btn-sm",w.isSmallSize)("btn-lg",w.isLargeSize)("disabled",w.disabled),r.Q6J("disabled",w.disabled)}}function Ya(Y,fe){if(1&Y){const w=r.EpF();r.TgZ(0,"button",11),r.NdJ("click",function(){r.CHM(w);const ke=r.oxw();return ke.changeHour(-ke.hourStep)}),r._UZ(1,"span",15),r.TgZ(2,"span",13),r.SDv(3,16),r.qZA(),r.qZA()}if(2&Y){const w=r.oxw();r.ekj("btn-sm",w.isSmallSize)("btn-lg",w.isLargeSize)("disabled",w.disabled),r.Q6J("disabled",w.disabled)}}function Ao(Y,fe){if(1&Y){const w=r.EpF();r.TgZ(0,"button",11),r.NdJ("click",function(){r.CHM(w);const ke=r.oxw();return ke.changeMinute(ke.minuteStep)}),r._UZ(1,"span",12),r.TgZ(2,"span",13),r.SDv(3,17),r.qZA(),r.qZA()}if(2&Y){const w=r.oxw();r.ekj("btn-sm",w.isSmallSize)("btn-lg",w.isLargeSize)("disabled",w.disabled),r.Q6J("disabled",w.disabled)}}function fs(Y,fe){if(1&Y){const w=r.EpF();r.TgZ(0,"button",11),r.NdJ("click",function(){r.CHM(w);const ke=r.oxw();return ke.changeMinute(-ke.minuteStep)}),r._UZ(1,"span",15),r.TgZ(2,"span",13),r.SDv(3,18),r.qZA(),r.qZA()}if(2&Y){const w=r.oxw();r.ekj("btn-sm",w.isSmallSize)("btn-lg",w.isLargeSize)("disabled",w.disabled),r.Q6J("disabled",w.disabled)}}function Ca(Y,fe){1&Y&&(r.TgZ(0,"div",5),r._uU(1,":"),r.qZA())}function Ra(Y,fe){if(1&Y){const w=r.EpF();r.TgZ(0,"button",11),r.NdJ("click",function(){r.CHM(w);const ke=r.oxw(2);return ke.changeSecond(ke.secondStep)}),r._UZ(1,"span",12),r.TgZ(2,"span",13),r.SDv(3,21),r.qZA(),r.qZA()}if(2&Y){const w=r.oxw(2);r.ekj("btn-sm",w.isSmallSize)("btn-lg",w.isLargeSize)("disabled",w.disabled),r.Q6J("disabled",w.disabled)}}function pl(Y,fe){if(1&Y){const w=r.EpF();r.TgZ(0,"button",11),r.NdJ("click",function(){r.CHM(w);const ke=r.oxw(2);return ke.changeSecond(-ke.secondStep)}),r._UZ(1,"span",15),r.TgZ(2,"span",13),r.SDv(3,22),r.qZA(),r.qZA()}if(2&Y){const w=r.oxw(2);r.ekj("btn-sm",w.isSmallSize)("btn-lg",w.isLargeSize)("disabled",w.disabled),r.Q6J("disabled",w.disabled)}}function Ws(Y,fe){if(1&Y){const w=r.EpF();r.TgZ(0,"div",19),r.YNc(1,Ra,4,7,"button",3),r.TgZ(2,"input",20),r.NdJ("change",function(ke){return r.CHM(w),r.oxw().updateSecond(ke.target.value)})("blur",function(){return r.CHM(w),r.oxw().handleBlur()})("input",function(ke){return r.CHM(w),r.oxw().formatInput(ke.target)})("keydown.ArrowUp",function(ke){r.CHM(w);const ct=r.oxw();return ct.changeSecond(ct.secondStep),ke.preventDefault()})("keydown.ArrowDown",function(ke){r.CHM(w);const ct=r.oxw();return ct.changeSecond(-ct.secondStep),ke.preventDefault()}),r.qZA(),r.YNc(3,pl,4,7,"button",3),r.qZA()}if(2&Y){const w=r.oxw();r.xp6(1),r.Q6J("ngIf",w.spinners),r.xp6(1),r.ekj("form-control-sm",w.isSmallSize)("form-control-lg",w.isLargeSize),r.Q6J("value",w.formatMinSec(null==w.model?null:w.model.second))("readOnly",w.readonlyInputs)("disabled",w.disabled),r.xp6(1),r.Q6J("ngIf",w.spinners)}}function Po(Y,fe){1&Y&&r._UZ(0,"div",5)}function bo(Y,fe){if(1&Y&&(r.ynx(0),r.SDv(1,27),r.BQk()),2&Y){const w=r.oxw(2);r.xp6(1),r.pQV(w.i18n.getAfternoonPeriod()),r.QtT(1)}}function Ls(Y,fe){if(1&Y&&r.SDv(0,28),2&Y){const w=r.oxw(2);r.pQV(w.i18n.getMorningPeriod()),r.QtT(0)}}function ps(Y,fe){if(1&Y){const w=r.EpF();r.TgZ(0,"div",23),r.TgZ(1,"button",24),r.NdJ("click",function(){return r.CHM(w),r.oxw().toggleMeridian()}),r.YNc(2,bo,2,1,"ng-container",25),r.YNc(3,Ls,1,1,"ng-template",null,26,r.W1O),r.qZA(),r.qZA()}if(2&Y){const w=r.MAs(4),X=r.oxw();r.xp6(1),r.ekj("btn-sm",X.isSmallSize)("btn-lg",X.isLargeSize)("disabled",X.disabled),r.Q6J("disabled",X.disabled),r.xp6(1),r.Q6J("ngIf",X.model&&X.model.hour>=12)("ngIfElse",w)}}function tt(Y,fe){if(1&Y&&(r.TgZ(0,"span"),r._uU(1),r.qZA()),2&Y){const w=r.oxw().$implicit,X=r.oxw();r.Tol(X.highlightClass),r.xp6(1),r.Oqu(w)}}function sn(Y,fe){if(1&Y&&r._uU(0),2&Y){const w=r.oxw().$implicit;r.Oqu(w)}}function ne(Y,fe){if(1&Y&&(r.YNc(0,tt,2,3,"span",1),r.YNc(1,sn,1,1,"ng-template",null,2,r.W1O)),2&Y){const w=fe.odd,X=r.MAs(2);r.Q6J("ngIf",w)("ngIfElse",X)}}function $e(Y,fe){if(1&Y&&r._UZ(0,"ngb-highlight",2),2&Y){const X=fe.term;r.Q6J("result",(0,fe.formatter)(fe.result))("term",X)}}function Lt(Y,fe){}const an=function(Y,fe,w){return{result:Y,term:fe,formatter:w}};function ti(Y,fe){if(1&Y){const w=r.EpF();r.TgZ(0,"button",3),r.NdJ("mouseenter",function(){const ct=r.CHM(w).index;return r.oxw().markActive(ct)})("click",function(){const ct=r.CHM(w).$implicit;return r.oxw().select(ct)}),r.YNc(1,Lt,0,0,"ng-template",4),r.qZA()}if(2&Y){const w=fe.$implicit,X=fe.index,ke=r.oxw(),ct=r.MAs(1);r.ekj("active",X===ke.activeIdx),r.Q6J("id",ke.id+"-"+X),r.xp6(1),r.Q6J("ngTemplateOutlet",ke.resultTemplate||ct)("ngTemplateOutletContext",r.kEZ(5,an,w,ke.term,ke.formatter))}}function pi(Y){return parseInt(`${Y}`,10)}function xi(Y){return null!=Y?`${Y}`:""}function wo(Y){return"string"==typeof Y}function ko(Y){return!isNaN(pi(Y))}function Eo(Y){return"number"==typeof Y&&isFinite(Y)&&Math.floor(Y)===Y}function ba(Y){return null!=Y}function sl(Y){return ko(Y)?`0${Y}`.slice(-2):""}function Nu(Y,fe){return Y&&Y.className&&Y.className.split&&Y.className.split(/\s+/).indexOf(fe)>=0}function Ec(Y){return(Y||document.body).getBoundingClientRect()}function Hl(Y){return Y.normalize("NFD").replace(/[\u0300-\u036f]/g,"")}"undefined"!=typeof Element&&!Element.prototype.closest&&(Element.prototype.matches||(Element.prototype.matches=Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector),Element.prototype.closest=function(Y){let fe=this;if(!document.documentElement.contains(fe))return null;do{if(fe.matches(Y))return fe;fe=fe.parentElement||fe.parentNode}while(null!==fe&&1===fe.nodeType);return null});const Yl={animation:!0,transitionTimerDelayMs:5};let Fs=(()=>{class Y{constructor(){this.animation=Yl.animation}}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275prov=r.Yz7({factory:function(){return new Y},token:Y,providedIn:"root"}),Y})();const tl=()=>{},{transitionTimerDelayMs:Zl}=Yl,rt=new Map,Pt=(Y,fe,w,X)=>{let ke=X.context||{};const ct=rt.get(fe);if(ct)switch(X.runningTransition){case"continue":return Ee.E;case"stop":Y.run(()=>ct.transition$.complete()),ke=Object.assign(ct.context,ke),rt.delete(fe)}const Jn=w(fe,X.animation,ke)||tl;if(!X.animation||"none"===window.getComputedStyle(fe).transitionProperty)return Y.run(()=>Jn()),(0,ie.of)(void 0).pipe(function(Y){return fe=>new se.y(w=>fe.subscribe(Jn=>Y.run(()=>w.next(Jn)),Jn=>Y.run(()=>w.error(Jn)),()=>Y.run(()=>w.complete())))}(Y));const Ir=new he.xQ,vi=new he.xQ,zi=Ir.pipe(function(...Y){return fe=>(0,Be.z)(fe,(0,ie.of)(...Y))}(!0));rt.set(fe,{transition$:Ir,complete:()=>{vi.next(),vi.complete()},context:ke});const Do=function(Y){const{transitionDelay:fe,transitionDuration:w}=window.getComputedStyle(Y);return 1e3*(parseFloat(fe)+parseFloat(w))}(fe);return Y.runOutsideAngular(()=>{const xs=(0,ge.R)(fe,"transitionend").pipe((0,Zt.R)(zi),(0,Ut.h)(({target:Rs})=>Rs===fe));y((0,De.H)(Do+Zl).pipe((0,Zt.R)(zi)),xs,vi).pipe((0,Zt.R)(zi)).subscribe(()=>{rt.delete(fe),Y.run(()=>{Jn(),Ir.next(),Ir.complete()})})}),Ir.asObservable()},Me=(Y,fe,w)=>{let{direction:X,maxHeight:ke}=w;const{classList:ct}=Y;function Jn(){ct.add("collapse"),"show"===X?ct.add("show"):ct.remove("show")}if(fe)return ke||(ke=function(Y){if("undefined"==typeof navigator)return"0px";const{classList:fe}=Y,w=fe.contains("show");w||fe.add("show"),Y.style.height="";const X=Y.getBoundingClientRect().height+"px";return w||fe.remove("show"),X}(Y),w.maxHeight=ke,Y.style.height="show"!==X?ke:"0px",ct.remove("collapse"),ct.remove("collapsing"),ct.remove("show"),Ec(Y),ct.add("collapsing")),Y.style.height="show"===X?ke:"0px",()=>{Jn(),ct.remove("collapsing"),Y.style.height=""};Jn()};let au=(()=>{class Y{constructor(w){this._ngbConfig=w,this.dismissible=!0,this.type="warning"}get animation(){return void 0===this._animation?this._ngbConfig.animation:this._animation}set animation(w){this._animation=w}}return Y.\u0275fac=function(w){return new(w||Y)(r.LFG(Fs))},Y.\u0275prov=r.Yz7({factory:function(){return new Y(r.LFG(Fs))},token:Y,providedIn:"root"}),Y})();const Wa=({classList:Y})=>{Y.remove("show")};let Rl=(()=>{class Y{constructor(w,X,ke,ct){this._renderer=X,this._element=ke,this._zone=ct,this.closed=new r.vpe,this.dismissible=w.dismissible,this.type=w.type,this.animation=w.animation}close(){const w=Pt(this._zone,this._element.nativeElement,Wa,{animation:this.animation,runningTransition:"continue"});return w.subscribe(()=>this.closed.emit()),w}ngOnChanges(w){const X=w.type;X&&!X.firstChange&&(this._renderer.removeClass(this._element.nativeElement,`alert-${X.previousValue}`),this._renderer.addClass(this._element.nativeElement,`alert-${X.currentValue}`))}ngOnInit(){this._renderer.addClass(this._element.nativeElement,`alert-${this.type}`)}}return Y.\u0275fac=function(w){return new(w||Y)(r.Y36(au),r.Y36(r.Qsj),r.Y36(r.SBq),r.Y36(r.R0b))},Y.\u0275cmp=r.Xpm({type:Y,selectors:[["ngb-alert"]],hostAttrs:["role","alert",1,"alert","show"],hostVars:4,hostBindings:function(w,X){2&w&&r.ekj("fade",X.animation)("alert-dismissible",X.dismissible)},inputs:{dismissible:"dismissible",type:"type",animation:"animation"},outputs:{closed:"closed"},exportAs:["ngbAlert"],features:[r.TTD],ngContentSelectors:Hi,decls:2,vars:1,consts:function(){let fe;return fe="Close",[["type","button","class","close","aria-label",fe,3,"click",4,"ngIf"],["type","button","aria-label",fe,1,"close",3,"click"],["aria-hidden","true"]]},template:function(w,X){1&w&&(r.F$t(),r.Hsn(0),r.YNc(1,ki,3,0,"button",0)),2&w&&(r.xp6(1),r.Q6J("ngIf",X.dismissible))},directives:[u.O5],styles:["ngb-alert{display:block}"],encapsulation:2,changeDetection:0}),Y})(),nc=(()=>{class Y{}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275mod=r.oAB({type:Y}),Y.\u0275inj=r.cJS({imports:[[u.ez]]}),Y})(),wr=(()=>{class Y{constructor(w){this._ngbConfig=w}get animation(){return void 0===this._animation?this._ngbConfig.animation:this._animation}set animation(w){this._animation=w}}return Y.\u0275fac=function(w){return new(w||Y)(r.LFG(Fs))},Y.\u0275prov=r.Yz7({factory:function(){return new Y(r.LFG(Fs))},token:Y,providedIn:"root"}),Y})(),fa=(()=>{class Y{constructor(w,X,ke){this._element=w,this._zone=ke,this.collapsed=!1,this.ngbCollapseChange=new r.vpe,this.shown=new r.vpe,this.hidden=new r.vpe,this.animation=X.animation}ngOnInit(){this._runTransition(this.collapsed,!1)}ngOnChanges({collapsed:w}){w.firstChange||this._runTransitionWithEvents(this.collapsed,this.animation)}toggle(w=this.collapsed){this.collapsed=!w,this.ngbCollapseChange.next(this.collapsed),this._runTransitionWithEvents(this.collapsed,this.animation)}_runTransition(w,X){return Pt(this._zone,this._element.nativeElement,Me,{animation:X,runningTransition:"stop",context:{direction:w?"hide":"show"}})}_runTransitionWithEvents(w,X){this._runTransition(w,X).subscribe(()=>{w?this.hidden.emit():this.shown.emit()})}}return Y.\u0275fac=function(w){return new(w||Y)(r.Y36(r.SBq),r.Y36(wr),r.Y36(r.R0b))},Y.\u0275dir=r.lG2({type:Y,selectors:[["","ngbCollapse",""]],inputs:{collapsed:["ngbCollapse","collapsed"],animation:"animation"},outputs:{ngbCollapseChange:"ngbCollapseChange",shown:"shown",hidden:"hidden"},exportAs:["ngbCollapse"],features:[r.TTD]}),Y})(),pu=(()=>{class Y{}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275mod=r.oAB({type:Y}),Y.\u0275inj=r.cJS({}),Y})();class aa{constructor(fe,w,X){this.year=Eo(fe)?fe:null,this.month=Eo(w)?w:null,this.day=Eo(X)?X:null}static from(fe){return fe instanceof aa?fe:fe?new aa(fe.year,fe.month,fe.day):null}equals(fe){return null!=fe&&this.year===fe.year&&this.month===fe.month&&this.day===fe.day}before(fe){return!!fe&&(this.year===fe.year?this.month===fe.month?this.day!==fe.day&&this.dayfe.day:this.month>fe.month:this.year>fe.year)}}function jl(Y){return new aa(Y.getFullYear(),Y.getMonth()+1,Y.getDate())}function bl(Y){const fe=new Date(Y.year,Y.month-1,Y.day,12);return isNaN(fe.getTime())||fe.setFullYear(Y.year),fe}function El(){return new ks}let Ul=(()=>{class Y{}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275prov=r.Yz7({factory:El,token:Y,providedIn:"root"}),Y})(),ks=(()=>{class Y extends Ul{getDaysPerWeek(){return 7}getMonths(){return[1,2,3,4,5,6,7,8,9,10,11,12]}getWeeksPerMonth(){return 6}getNext(w,X="d",ke=1){let ct=bl(w),Jn=!0,Ir=ct.getMonth();switch(X){case"y":ct.setFullYear(ct.getFullYear()+ke);break;case"m":Ir+=ke,ct.setMonth(Ir),Ir%=12,Ir<0&&(Ir+=12);break;case"d":ct.setDate(ct.getDate()+ke),Jn=!1;break;default:return w}return Jn&&ct.getMonth()!==Ir&&ct.setDate(0),jl(ct)}getPrev(w,X="d",ke=1){return this.getNext(w,X,-ke)}getWeekday(w){let ke=bl(w).getDay();return 0===ke?7:ke}getWeekNumber(w,X){7===X&&(X=0);const Jn=bl(w[(11-X)%7]);Jn.setDate(Jn.getDate()+4-(Jn.getDay()||7));const Ir=Jn.getTime();return Jn.setMonth(0),Jn.setDate(1),Math.floor(Math.round((Ir-Jn.getTime())/864e5)/7)+1}getToday(){return jl(new Date)}isValid(w){if(!(w&&Eo(w.year)&&Eo(w.month)&&Eo(w.day)&&0!==w.year))return!1;const X=bl(w);return!isNaN(X.getTime())&&X.getFullYear()===w.year&&X.getMonth()+1===w.month&&X.getDate()===w.day}}return Y.\u0275fac=function(){let fe;return function(X){return(fe||(fe=r.n5z(Y)))(X||Y)}}(),Y.\u0275prov=r.Yz7({token:Y,factory:Y.\u0275fac}),Y})();function V(Y,fe){return!function(Y,fe){return!Y&&!fe||!!Y&&!!fe&&Y.equals(fe)}(Y,fe)}function Ae(Y,fe){return!(!Y&&!fe||Y&&fe&&Y.year===fe.year&&Y.month===fe.month)}function ut(Y,fe,w){return Y&&fe&&Y.before(fe)?fe:Y&&w&&Y.after(w)?w:Y||null}function un(Y,fe){const{minDate:w,maxDate:X,disabled:ke,markDisabled:ct}=fe;return!(null==Y||ke||ct&&ct(Y,{year:Y.year,month:Y.month})||w&&Y.before(w)||X&&Y.after(X))}let Cr=(()=>{class Y{getMonthLabel(w){return`${this.getMonthFullName(w.month,w.year)} ${this.getYearNumerals(w.year)}`}getDayNumerals(w){return`${w.day}`}getWeekNumerals(w){return`${w}`}getYearNumerals(w){return`${w}`}getWeekLabel(){return""}}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275prov=r.Yz7({factory:function(){return function(Y){return new hr(Y)}(r.LFG(r.soG))},token:Y,providedIn:"root"}),Y})(),hr=(()=>{class Y extends Cr{constructor(w){super(),this._locale=w,this._monthsShort=(0,u.UT)(w,u.x.Standalone,u.Tn.Abbreviated),this._monthsFull=(0,u.UT)(w,u.x.Standalone,u.Tn.Wide)}getWeekdayLabel(w,X){const ke=(0,u.Mn)(this._locale,u.x.Standalone,void 0===X?u.Tn.Short:X);return ke.map((Jn,Ir)=>ke[(Ir+1)%7])[w-1]||""}getMonthShortName(w){return this._monthsShort[w-1]||""}getMonthFullName(w){return this._monthsFull[w-1]||""}getDayAriaLabel(w){const X=new Date(w.year,w.month-1,w.day);return(0,u.p6)(X,"fullDate",this._locale)}}return Y.\u0275fac=function(w){return new(w||Y)(r.LFG(r.soG))},Y.\u0275prov=r.Yz7({token:Y,factory:Y.\u0275fac}),Y})(),ao=(()=>{class Y{constructor(w,X){this._calendar=w,this._i18n=X,this._VALIDATORS={dayTemplateData:ke=>{if(this._state.dayTemplateData!==ke)return{dayTemplateData:ke}},displayMonths:ke=>{if(Eo(ke=pi(ke))&&ke>0&&this._state.displayMonths!==ke)return{displayMonths:ke}},disabled:ke=>{if(this._state.disabled!==ke)return{disabled:ke}},firstDayOfWeek:ke=>{if(Eo(ke=pi(ke))&&ke>=0&&this._state.firstDayOfWeek!==ke)return{firstDayOfWeek:ke}},focusVisible:ke=>{if(this._state.focusVisible!==ke&&!this._state.disabled)return{focusVisible:ke}},markDisabled:ke=>{if(this._state.markDisabled!==ke)return{markDisabled:ke}},maxDate:ke=>{const ct=this.toValidDate(ke,null);if(V(this._state.maxDate,ct))return{maxDate:ct}},minDate:ke=>{const ct=this.toValidDate(ke,null);if(V(this._state.minDate,ct))return{minDate:ct}},navigation:ke=>{if(this._state.navigation!==ke)return{navigation:ke}},outsideDays:ke=>{if(this._state.outsideDays!==ke)return{outsideDays:ke}},weekdays:ke=>{const ct=!0===ke||!1===ke?u.Tn.Short:ke,Jn=!0!==ke&&!1!==ke||ke;if(this._state.weekdayWidth!==ct||this._state.weekdaysVisible!==Jn)return{weekdayWidth:ct,weekdaysVisible:Jn}}},this._model$=new he.xQ,this._dateSelect$=new he.xQ,this._state={dayTemplateData:null,markDisabled:null,maxDate:null,minDate:null,disabled:!1,displayMonths:1,firstDate:null,firstDayOfWeek:1,lastDate:null,focusDate:null,focusVisible:!1,months:[],navigation:"select",outsideDays:"visible",prevDisabled:!1,nextDisabled:!1,selectedDate:null,selectBoxes:{years:[],months:[]},weekdayWidth:u.Tn.Short,weekdaysVisible:!0}}get model$(){return this._model$.pipe((0,Ut.h)(w=>w.months.length>0))}get dateSelect$(){return this._dateSelect$.pipe((0,Ut.h)(w=>null!==w))}set(w){let X=Object.keys(w).map(ke=>this._VALIDATORS[ke](w[ke])).reduce((ke,ct)=>Object.assign(Object.assign({},ke),ct),{});Object.keys(X).length>0&&this._nextState(X)}focus(w){const X=this.toValidDate(w,null);null!=X&&!this._state.disabled&&V(this._state.focusDate,X)&&this._nextState({focusDate:w})}focusSelect(){un(this._state.focusDate,this._state)&&this.select(this._state.focusDate,{emitEvent:!0})}open(w){const X=this.toValidDate(w,this._calendar.getToday());null!=X&&!this._state.disabled&&(!this._state.firstDate||Ae(this._state.firstDate,X))&&this._nextState({firstDate:X})}select(w,X={}){const ke=this.toValidDate(w,null);null!=ke&&!this._state.disabled&&(V(this._state.selectedDate,ke)&&this._nextState({selectedDate:ke}),X.emitEvent&&un(ke,this._state)&&this._dateSelect$.next(ke))}toValidDate(w,X){const ke=aa.from(w);return void 0===X&&(X=this._calendar.getToday()),this._calendar.isValid(ke)?ke:X}getMonth(w){for(let X of this._state.months)if(w.month===X.number&&w.year===X.year)return X;throw new Error(`month ${w.month} of year ${w.year} not found`)}_nextState(w){const X=this._updateState(w);this._patchContexts(X),this._state=X,this._model$.next(this._state)}_patchContexts(w){const{months:X,displayMonths:ke,selectedDate:ct,focusDate:Jn,focusVisible:Ir,disabled:vi,outsideDays:zi}=w;w.months.forEach(Do=>{Do.weeks.forEach(xs=>{xs.days.forEach(Xo=>{Jn&&(Xo.context.focused=Jn.equals(Xo.date)&&Ir),Xo.tabindex=!vi&&Jn&&Xo.date.equals(Jn)&&Jn.month===Do.number?0:-1,!0===vi&&(Xo.context.disabled=!0),void 0!==ct&&(Xo.context.selected=null!==ct&&ct.equals(Xo.date)),Do.number!==Xo.date.month&&(Xo.hidden="hidden"===zi||"collapsed"===zi||ke>1&&Xo.date.after(X[0].firstDate)&&Xo.date.before(X[ke-1].lastDate))})})})}_updateState(w){const X=Object.assign({},this._state,w);let ke=X.firstDate;if(("minDate"in w||"maxDate"in w)&&(function(Y,fe){if(fe&&Y&&fe.before(Y))throw new Error(`'maxDate' ${fe} should be greater than 'minDate' ${Y}`)}(X.minDate,X.maxDate),X.focusDate=ut(X.focusDate,X.minDate,X.maxDate),X.firstDate=ut(X.firstDate,X.minDate,X.maxDate),ke=X.focusDate),"disabled"in w&&(X.focusVisible=!1),"selectedDate"in w&&0===this._state.months.length&&(ke=X.selectedDate),"focusVisible"in w||"focusDate"in w&&(X.focusDate=ut(X.focusDate,X.minDate,X.maxDate),ke=X.focusDate,0!==X.months.length&&X.focusDate&&!X.focusDate.before(X.firstDate)&&!X.focusDate.after(X.lastDate)))return X;if("firstDate"in w&&(X.firstDate=ut(X.firstDate,X.minDate,X.maxDate),ke=X.firstDate),ke){const Jn=function(Y,fe,w,X,ke){const{displayMonths:ct,months:Jn}=w,Ir=Jn.splice(0,Jn.length);return Array.from({length:ct},(zi,Do)=>{const xs=Object.assign(Y.getNext(fe,"m",Do),{day:1});if(Jn[Do]=null,!ke){const Xo=Ir.findIndex(Rs=>Rs.firstDate.equals(xs));-1!==Xo&&(Jn[Do]=Ir.splice(Xo,1)[0])}return xs}).forEach((zi,Do)=>{null===Jn[Do]&&(Jn[Do]=function(Y,fe,w,X,ke={}){const{dayTemplateData:ct,minDate:Jn,maxDate:Ir,firstDayOfWeek:vi,markDisabled:zi,outsideDays:Do,weekdayWidth:xs,weekdaysVisible:Xo}=w,Rs=Y.getToday();ke.firstDate=null,ke.lastDate=null,ke.number=fe.month,ke.year=fe.year,ke.weeks=ke.weeks||[],ke.weekdays=ke.weekdays||[],fe=function(Y,fe,w){const X=Y.getDaysPerWeek(),ke=new aa(fe.year,fe.month,1),ct=Y.getWeekday(ke)%X;return Y.getPrev(ke,"d",(X+ct-w)%X)}(Y,fe,vi),Xo||(ke.weekdays.length=0);for(let ma=0;mand.date),vi),Vs.collapsed="collapsed"===Do&&_c[0].date.month!==ke.number&&_c[_c.length-1].date.month!==ke.number}return ke}(Y,zi,w,X,Ir.shift()||{}))}),Jn}(this._calendar,ke,X,this._i18n,"dayTemplateData"in w||"firstDayOfWeek"in w||"markDisabled"in w||"minDate"in w||"maxDate"in w||"disabled"in w||"outsideDays"in w||"weekdaysVisible"in w);X.months=Jn,X.firstDate=Jn[0].firstDate,X.lastDate=Jn[Jn.length-1].lastDate,"selectedDate"in w&&!un(X.selectedDate,X)&&(X.selectedDate=null),"firstDate"in w&&(!X.focusDate||X.focusDate.before(X.firstDate)||X.focusDate.after(X.lastDate))&&(X.focusDate=ke);const Ir=!this._state.firstDate||this._state.firstDate.year!==X.firstDate.year,vi=!this._state.firstDate||this._state.firstDate.month!==X.firstDate.month;"select"===X.navigation?(("minDate"in w||"maxDate"in w||0===X.selectBoxes.years.length||Ir)&&(X.selectBoxes.years=function(Y,fe,w){if(!Y)return[];const X=fe?Math.max(fe.year,Y.year-500):Y.year-10,ct=(w?Math.min(w.year,Y.year+500):Y.year+10)-X+1,Jn=Array(ct);for(let Ir=0;IrJn===w.month);ke=ke.slice(ct)}if(X&&fe.year===X.year){const ct=ke.findIndex(Jn=>Jn===X.month);ke=ke.slice(0,ct+1)}return ke}(this._calendar,X.firstDate,X.minDate,X.maxDate))):X.selectBoxes={years:[],months:[]},("arrows"===X.navigation||"select"===X.navigation)&&(vi||Ir||"minDate"in w||"maxDate"in w||"disabled"in w)&&(X.prevDisabled=X.disabled||function(Y,fe,w){const X=Object.assign(Y.getPrev(fe,"m"),{day:1});return null!=w&&(X.year===w.year&&X.month{return(Y=ys||(ys={}))[Y.PREV=0]="PREV",Y[Y.NEXT=1]="NEXT",ys;var Y})();let Na=(()=>{class Y{constructor(){this.displayMonths=1,this.firstDayOfWeek=1,this.navigation="select",this.outsideDays="visible",this.showWeekNumbers=!1,this.weekdays=u.Tn.Short}}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275prov=r.Yz7({factory:function(){return new Y},token:Y,providedIn:"root"}),Y})();function Tl(){return new ed}let Qs=(()=>{class Y{}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275prov=r.Yz7({factory:Tl,token:Y,providedIn:"root"}),Y})(),ed=(()=>{class Y extends Qs{fromModel(w){return w&&Eo(w.year)&&Eo(w.month)&&Eo(w.day)?{year:w.year,month:w.month,day:w.day}:null}toModel(w){return w&&Eo(w.year)&&Eo(w.month)&&Eo(w.day)?{year:w.year,month:w.month,day:w.day}:null}}return Y.\u0275fac=function(){let fe;return function(X){return(fe||(fe=r.n5z(Y)))(X||Y)}}(),Y.\u0275prov=r.Yz7({token:Y,factory:Y.\u0275fac}),Y})(),$c=(()=>{class Y{constructor(w){this.templateRef=w}}return Y.\u0275fac=function(w){return new(w||Y)(r.Y36(r.Rgc))},Y.\u0275dir=r.lG2({type:Y,selectors:[["ng-template","ngbDatepickerContent",""]]}),Y})(),Gl=(()=>{class Y{constructor(w,X,ke,ct,Jn,Ir,vi,zi){this._service=w,this._calendar=X,this.i18n=ke,this._elementRef=Ir,this._ngbDateAdapter=vi,this._ngZone=zi,this._controlValue=null,this._destroyed$=new he.xQ,this._publicState={},this.navigate=new r.vpe,this.dateSelect=new r.vpe,this.onChange=Do=>{},this.onTouched=()=>{},["dayTemplate","dayTemplateData","displayMonths","firstDayOfWeek","footerTemplate","markDisabled","minDate","maxDate","navigation","outsideDays","showWeekNumbers","startDate","weekdays"].forEach(Do=>this[Do]=ct[Do]),w.dateSelect$.pipe((0,Zt.R)(this._destroyed$)).subscribe(Do=>{this.dateSelect.emit(Do)}),w.model$.pipe((0,Zt.R)(this._destroyed$)).subscribe(Do=>{const xs=Do.firstDate,Xo=this.model?this.model.firstDate:null;this._publicState={maxDate:Do.maxDate,minDate:Do.minDate,firstDate:Do.firstDate,lastDate:Do.lastDate,focusedDate:Do.focusDate,months:Do.months.map(nd=>nd.firstDate)};let Rs=!1;if(!xs.equals(Xo)&&(this.navigate.emit({current:Xo?{year:Xo.year,month:Xo.month}:null,next:{year:xs.year,month:xs.month},preventDefault:()=>Rs=!0}),Rs&&null!==Xo))return void this._service.open(Xo);const ma=Do.selectedDate,Vs=Do.focusDate,_c=this.model?this.model.focusDate:null;this.model=Do,V(ma,this._controlValue)&&(this._controlValue=ma,this.onTouched(),this.onChange(this._ngbDateAdapter.toModel(ma))),V(Vs,_c)&&_c&&Do.focusVisible&&this.focus(),Jn.markForCheck()})}get state(){return this._publicState}get calendar(){return this._calendar}focusDate(w){this._service.focus(aa.from(w))}focusSelect(){this._service.focusSelect()}focus(){this._ngZone.onStable.asObservable().pipe((0,Bt.q)(1)).subscribe(()=>{const w=this._elementRef.nativeElement.querySelector('div.ngb-dp-day[tabindex="0"]');w&&w.focus()})}navigateTo(w){this._service.open(aa.from(w?w.day?w:Object.assign(Object.assign({},w),{day:1}):null))}ngAfterViewInit(){this._ngZone.runOutsideAngular(()=>{const w=(0,ge.R)(this._contentEl.nativeElement,"focusin"),X=(0,ge.R)(this._contentEl.nativeElement,"focusout"),{nativeElement:ke}=this._elementRef;(0,ze.T)(w,X).pipe((0,Ut.h)(({target:ct,relatedTarget:Jn})=>!(Nu(ct,"ngb-dp-day")&&Nu(Jn,"ngb-dp-day")&&ke.contains(ct)&&ke.contains(Jn))),(0,Zt.R)(this._destroyed$)).subscribe(({type:ct})=>this._ngZone.run(()=>this._service.set({focusVisible:"focusin"===ct})))})}ngOnDestroy(){this._destroyed$.next()}ngOnInit(){if(void 0===this.model){const w={};["dayTemplateData","displayMonths","markDisabled","firstDayOfWeek","navigation","minDate","maxDate","outsideDays","weekdays"].forEach(X=>w[X]=this[X]),this._service.set(w),this.navigateTo(this.startDate)}this.dayTemplate||(this.dayTemplate=this._defaultDayTemplate)}ngOnChanges(w){const X={};if(["dayTemplateData","displayMonths","markDisabled","firstDayOfWeek","navigation","minDate","maxDate","outsideDays","weekdays"].filter(ke=>ke in w).forEach(ke=>X[ke]=this[ke]),this._service.set(X),"startDate"in w){const{currentValue:ke,previousValue:ct}=w.startDate;Ae(ct,ke)&&this.navigateTo(this.startDate)}}onDateSelect(w){this._service.focus(w),this._service.select(w,{emitEvent:!0})}onNavigateDateSelect(w){this._service.open(w)}onNavigateEvent(w){switch(w){case ys.PREV:this._service.open(this._calendar.getPrev(this.model.firstDate,"m",1));break;case ys.NEXT:this._service.open(this._calendar.getNext(this.model.firstDate,"m",1))}}registerOnChange(w){this.onChange=w}registerOnTouched(w){this.onTouched=w}setDisabledState(w){this._service.set({disabled:w})}writeValue(w){this._controlValue=aa.from(this._ngbDateAdapter.fromModel(w)),this._service.select(this._controlValue)}}return Y.\u0275fac=function(w){return new(w||Y)(r.Y36(ao),r.Y36(Ul),r.Y36(Cr),r.Y36(Na),r.Y36(r.sBO),r.Y36(r.SBq),r.Y36(Qs),r.Y36(r.R0b))},Y.\u0275cmp=r.Xpm({type:Y,selectors:[["ngb-datepicker"]],contentQueries:function(w,X,ke){if(1&w&&r.Suo(ke,$c,7),2&w){let ct;r.iGM(ct=r.CRH())&&(X.contentTemplate=ct.first)}},viewQuery:function(w,X){if(1&w&&(r.Gf(Fr,7),r.Gf(Gn,7)),2&w){let ke;r.iGM(ke=r.CRH())&&(X._defaultDayTemplate=ke.first),r.iGM(ke=r.CRH())&&(X._contentEl=ke.first)}},inputs:{dayTemplate:"dayTemplate",dayTemplateData:"dayTemplateData",displayMonths:"displayMonths",firstDayOfWeek:"firstDayOfWeek",footerTemplate:"footerTemplate",markDisabled:"markDisabled",maxDate:"maxDate",minDate:"minDate",navigation:"navigation",outsideDays:"outsideDays",showWeekNumbers:"showWeekNumbers",startDate:"startDate",weekdays:"weekdays"},outputs:{navigate:"navigate",dateSelect:"dateSelect"},exportAs:["ngbDatepicker"],features:[r._Bn([{provide:Mr.JU,useExisting:(0,r.Gpc)(()=>Y),multi:!0},ao]),r.TTD],decls:10,vars:5,consts:[["defaultDayTemplate",""],["defaultContentTemplate",""],[1,"ngb-dp-header"],[3,"date","months","disabled","showSelect","prevDisabled","nextDisabled","selectBoxes","navigate","select",4,"ngIf"],[1,"ngb-dp-content"],["content",""],[3,"ngTemplateOutlet"],["ngbDatepickerDayView","",3,"date","currentMonth","selected","disabled","focused"],["class","ngb-dp-month",4,"ngFor","ngForOf"],[1,"ngb-dp-month"],["class","ngb-dp-month-name",4,"ngIf"],[3,"month"],[1,"ngb-dp-month-name"],[3,"date","months","disabled","showSelect","prevDisabled","nextDisabled","selectBoxes","navigate","select"]],template:function(w,X){if(1&w&&(r.YNc(0,Jr,1,5,"ng-template",null,0,r.W1O),r.YNc(2,br,1,1,"ng-template",null,1,r.W1O),r.TgZ(4,"div",2),r.YNc(5,Dr,1,7,"ngb-datepicker-navigation",3),r.qZA(),r.TgZ(6,"div",4,5),r.YNc(8,gn,0,0,"ng-template",6),r.qZA(),r.YNc(9,yn,0,0,"ng-template",6)),2&w){const ke=r.MAs(3);r.xp6(5),r.Q6J("ngIf","none"!==X.navigation),r.xp6(1),r.ekj("ngb-dp-months",!X.contentTemplate),r.xp6(2),r.Q6J("ngTemplateOutlet",(null==X.contentTemplate?null:X.contentTemplate.templateRef)||ke),r.xp6(1),r.Q6J("ngTemplateOutlet",X.footerTemplate)}},directives:function(){return[u.O5,u.tP,Hn,u.sg,pa,hl]},styles:["ngb-datepicker{border:1px solid #dfdfdf;border-radius:.25rem;display:inline-block}ngb-datepicker-month{pointer-events:auto}ngb-datepicker.dropdown-menu{padding:0}.ngb-dp-body{z-index:1050}.ngb-dp-header{border-bottom:0;border-radius:.25rem .25rem 0 0;padding-top:.25rem;background-color:#f8f9fa;background-color:var(--light)}.ngb-dp-months{display:flex}.ngb-dp-month{pointer-events:none}.ngb-dp-month-name{font-size:larger;height:2rem;line-height:2rem;text-align:center;background-color:#f8f9fa;background-color:var(--light)}.ngb-dp-month+.ngb-dp-month .ngb-dp-month-name,.ngb-dp-month+.ngb-dp-month .ngb-dp-week{padding-left:1rem}.ngb-dp-month:last-child .ngb-dp-week{padding-right:.25rem}.ngb-dp-month:first-child .ngb-dp-week{padding-left:.25rem}.ngb-dp-month .ngb-dp-week:last-child{padding-bottom:.25rem}"],encapsulation:2,changeDetection:0}),Y})();var $i=(()=>{return(Y=$i||($i={}))[Y.Tab=9]="Tab",Y[Y.Enter=13]="Enter",Y[Y.Escape=27]="Escape",Y[Y.Space=32]="Space",Y[Y.PageUp=33]="PageUp",Y[Y.PageDown=34]="PageDown",Y[Y.End=35]="End",Y[Y.Home=36]="Home",Y[Y.ArrowLeft=37]="ArrowLeft",Y[Y.ArrowUp=38]="ArrowUp",Y[Y.ArrowRight=39]="ArrowRight",Y[Y.ArrowDown=40]="ArrowDown",$i;var Y})();let xo=(()=>{class Y{processKey(w,X){const{state:ke,calendar:ct}=X;switch(w.which){case $i.PageUp:X.focusDate(ct.getPrev(ke.focusedDate,w.shiftKey?"y":"m",1));break;case $i.PageDown:X.focusDate(ct.getNext(ke.focusedDate,w.shiftKey?"y":"m",1));break;case $i.End:X.focusDate(w.shiftKey?ke.maxDate:ke.lastDate);break;case $i.Home:X.focusDate(w.shiftKey?ke.minDate:ke.firstDate);break;case $i.ArrowLeft:X.focusDate(ct.getPrev(ke.focusedDate,"d",1));break;case $i.ArrowUp:X.focusDate(ct.getPrev(ke.focusedDate,"d",ct.getDaysPerWeek()));break;case $i.ArrowRight:X.focusDate(ct.getNext(ke.focusedDate,"d",1));break;case $i.ArrowDown:X.focusDate(ct.getNext(ke.focusedDate,"d",ct.getDaysPerWeek()));break;case $i.Enter:case $i.Space:X.focusSelect();break;default:return}w.preventDefault(),w.stopPropagation()}}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275prov=r.Yz7({factory:function(){return new Y},token:Y,providedIn:"root"}),Y})(),pa=(()=>{class Y{constructor(w,X,ke,ct){this.i18n=w,this.datepicker=X,this._keyboardService=ke,this._service=ct}set month(w){this.viewModel=this._service.getMonth(w)}onKeyDown(w){this._keyboardService.processKey(w,this.datepicker)}doSelect(w){!w.context.disabled&&!w.hidden&&this.datepicker.onDateSelect(w.date)}}return Y.\u0275fac=function(w){return new(w||Y)(r.Y36(Cr),r.Y36(Gl),r.Y36(xo),r.Y36(ao))},Y.\u0275cmp=r.Xpm({type:Y,selectors:[["ngb-datepicker-month"]],hostAttrs:["role","grid"],hostBindings:function(w,X){1&w&&r.NdJ("keydown",function(ct){return X.onKeyDown(ct)})},inputs:{month:"month"},decls:2,vars:2,consts:[["class","ngb-dp-week ngb-dp-weekdays","role","row",4,"ngIf"],["ngFor","",3,"ngForOf"],["role","row",1,"ngb-dp-week","ngb-dp-weekdays"],["class","ngb-dp-weekday ngb-dp-showweek small",4,"ngIf"],["class","ngb-dp-weekday small","role","columnheader",4,"ngFor","ngForOf"],[1,"ngb-dp-weekday","ngb-dp-showweek","small"],["role","columnheader",1,"ngb-dp-weekday","small"],["class","ngb-dp-week","role","row",4,"ngIf"],["role","row",1,"ngb-dp-week"],["class","ngb-dp-week-number small text-muted",4,"ngIf"],["class","ngb-dp-day","role","gridcell",3,"disabled","tabindex","hidden","ngb-dp-today","click",4,"ngFor","ngForOf"],[1,"ngb-dp-week-number","small","text-muted"],["role","gridcell",1,"ngb-dp-day",3,"tabindex","click"],[3,"ngIf"],[3,"ngTemplateOutlet","ngTemplateOutletContext"]],template:function(w,X){1&w&&(r.YNc(0,Vn,3,2,"div",0),r.YNc(1,Ge,1,1,"ng-template",1)),2&w&&(r.Q6J("ngIf",X.viewModel.weekdays.length>0),r.xp6(1),r.Q6J("ngForOf",X.viewModel.weeks))},directives:[u.O5,u.sg,u.tP],styles:['ngb-datepicker-month{display:block}.ngb-dp-week-number,.ngb-dp-weekday{line-height:2rem;text-align:center;font-style:italic}.ngb-dp-weekday{color:#5bc0de;color:var(--info)}.ngb-dp-week{border-radius:.25rem;display:flex}.ngb-dp-weekdays{border-bottom:1px solid rgba(0,0,0,.125);border-radius:0;background-color:#f8f9fa;background-color:var(--light)}.ngb-dp-day,.ngb-dp-week-number,.ngb-dp-weekday{width:2rem;height:2rem}.ngb-dp-day{cursor:pointer}.ngb-dp-day.disabled,.ngb-dp-day.hidden{cursor:default;pointer-events:none}.ngb-dp-day[tabindex="0"]{z-index:1}'],encapsulation:2}),Y})(),hl=(()=>{class Y{constructor(w){this.i18n=w,this.navigation=ys,this.months=[],this.navigate=new r.vpe,this.select=new r.vpe}onClickPrev(w){w.currentTarget.focus(),this.navigate.emit(this.navigation.PREV)}onClickNext(w){w.currentTarget.focus(),this.navigate.emit(this.navigation.NEXT)}}return Y.\u0275fac=function(w){return new(w||Y)(r.Y36(Cr))},Y.\u0275cmp=r.Xpm({type:Y,selectors:[["ngb-datepicker-navigation"]],inputs:{months:"months",date:"date",disabled:"disabled",showSelect:"showSelect",prevDisabled:"prevDisabled",nextDisabled:"nextDisabled",selectBoxes:"selectBoxes"},outputs:{navigate:"navigate",select:"select"},decls:8,vars:4,consts:function(){let fe,w,X,ke;return fe="Previous month",w="Previous month",X="Next month",ke="Next month",[[1,"ngb-dp-arrow"],["type","button","aria-label",fe,"title",w,1,"btn","btn-link","ngb-dp-arrow-btn",3,"disabled","click"],[1,"ngb-dp-navigation-chevron"],["class","ngb-dp-navigation-select",3,"date","disabled","months","years","select",4,"ngIf"],[4,"ngIf"],[1,"ngb-dp-arrow","right"],["type","button","aria-label",X,"title",ke,1,"btn","btn-link","ngb-dp-arrow-btn",3,"disabled","click"],[1,"ngb-dp-navigation-select",3,"date","disabled","months","years","select"],["ngFor","",3,"ngForOf"],["class","ngb-dp-arrow",4,"ngIf"],[1,"ngb-dp-month-name"]]},template:function(w,X){1&w&&(r.TgZ(0,"div",0),r.TgZ(1,"button",1),r.NdJ("click",function(ct){return X.onClickPrev(ct)}),r._UZ(2,"span",2),r.qZA(),r.qZA(),r.YNc(3,kr,1,4,"ngb-datepicker-navigation-select",3),r.YNc(4,Wr,1,1,void 0,4),r.TgZ(5,"div",5),r.TgZ(6,"button",6),r.NdJ("click",function(ct){return X.onClickNext(ct)}),r._UZ(7,"span",2),r.qZA(),r.qZA()),2&w&&(r.xp6(1),r.Q6J("disabled",X.prevDisabled),r.xp6(2),r.Q6J("ngIf",X.showSelect),r.xp6(1),r.Q6J("ngIf",!X.showSelect),r.xp6(2),r.Q6J("disabled",X.nextDisabled))},directives:function(){return[u.O5,Xn,u.sg]},styles:["ngb-datepicker-navigation{display:flex;align-items:center}.ngb-dp-navigation-chevron{border-style:solid;border-width:.2em .2em 0 0;display:inline-block;width:.75em;height:.75em;margin-left:.25em;margin-right:.15em;transform:rotate(-135deg)}.ngb-dp-arrow{display:flex;flex:1 1 auto;padding-right:0;padding-left:0;margin:0;width:2rem;height:2rem}.ngb-dp-arrow.right{justify-content:flex-end}.ngb-dp-arrow.right .ngb-dp-navigation-chevron{transform:rotate(45deg);margin-left:.15em;margin-right:.25em}.ngb-dp-arrow-btn{padding:0 .25rem;margin:0 .5rem;border:none;background-color:transparent;z-index:1}.ngb-dp-arrow-btn:focus{outline-width:1px;outline-style:auto}@media (-ms-high-contrast:active),(-ms-high-contrast:none){.ngb-dp-arrow-btn:focus{outline-style:solid}}.ngb-dp-month-name{font-size:larger;height:2rem;line-height:2rem;text-align:center}.ngb-dp-navigation-select{display:flex;flex:1 1 9rem}"],encapsulation:2,changeDetection:0}),Y})();const Bl=(Y,fe)=>!!fe&&fe.some(w=>w.contains(Y)),md=(Y,fe)=>!fe||null!=function(Y,fe){return fe&&void 0!==Y.closest?Y.closest(fe):null}(Y,fe),td="undefined"!=typeof navigator&&!!navigator.userAgent&&(/iPad|iPhone|iPod/.test(navigator.userAgent)||/Macintosh/.test(navigator.userAgent)&&navigator.maxTouchPoints&&navigator.maxTouchPoints>2||/Android/.test(navigator.userAgent));function la(Y,fe,w,X,ke,ct,Jn,Ir){w&&Y.runOutsideAngular((Y=>td?()=>setTimeout(()=>Y(),100):Y)(()=>{const zi=(0,ge.R)(fe,"keydown").pipe((0,Zt.R)(ke),(0,Ut.h)(Xo=>Xo.which===$i.Escape),(0,Zn.b)(Xo=>Xo.preventDefault())),Do=(0,ge.R)(fe,"mousedown").pipe((0,bt.U)(Xo=>{const Rs=Xo.target;return 2!==Xo.button&&!Bl(Rs,Jn)&&("inside"===w?Bl(Rs,ct)&&md(Rs,Ir):"outside"===w?!Bl(Rs,ct):md(Rs,Ir)||!Bl(Rs,ct))}),(0,Zt.R)(ke)),xs=(0,ge.R)(fe,"mouseup").pipe(je(Do),(0,Ut.h)(([Xo,Rs])=>Rs),(0,Ur.g)(0),(0,Zt.R)(ke));y([zi.pipe((0,bt.U)(Xo=>0)),xs.pipe((0,bt.U)(Xo=>1))]).subscribe(Xo=>Y.run(()=>X(Xo)))}))}const Zc=["a[href]","button:not([disabled])",'input:not([disabled]):not([type="hidden"])',"select:not([disabled])","textarea:not([disabled])","[contenteditable]",'[tabindex]:not([tabindex="-1"])'].join(", ");function ic(Y){const fe=Array.from(Y.querySelectorAll(Zc)).filter(w=>-1!==w.tabIndex);return[fe[0],fe[fe.length-1]]}const df=(Y,fe,w,X=!1)=>{Y.runOutsideAngular(()=>{const ke=(0,ge.R)(fe,"focusin").pipe((0,Zt.R)(w),(0,bt.U)(ct=>ct.target));(0,ge.R)(fe,"keydown").pipe((0,Zt.R)(w),(0,Ut.h)(ct=>ct.which===$i.Tab),je(ke)).subscribe(([ct,Jn])=>{const[Ir,vi]=ic(fe);(Jn===Ir||Jn===fe)&&ct.shiftKey&&(vi.focus(),ct.preventDefault()),Jn===vi&&!ct.shiftKey&&(Ir.focus(),ct.preventDefault())}),X&&(0,ge.R)(fe,"click").pipe((0,Zt.R)(w),je(ke),(0,bt.U)(ct=>ct[1])).subscribe(ct=>ct.focus())})},Wd=/\s+/,Jd=new class{getAllStyles(fe){return window.getComputedStyle(fe)}getStyle(fe,w){return this.getAllStyles(fe)[w]}isStaticPositioned(fe){return"static"===(this.getStyle(fe,"position")||"static")}offsetParent(fe){let w=fe.offsetParent||document.documentElement;for(;w&&w!==document.documentElement&&this.isStaticPositioned(w);)w=w.offsetParent;return w||document.documentElement}position(fe,w=!0){let X,ke={width:0,height:0,top:0,bottom:0,left:0,right:0};if("fixed"===this.getStyle(fe,"position"))X=fe.getBoundingClientRect(),X={top:X.top,bottom:X.bottom,left:X.left,right:X.right,height:X.height,width:X.width};else{const ct=this.offsetParent(fe);X=this.offset(fe,!1),ct!==document.documentElement&&(ke=this.offset(ct,!1)),ke.top+=ct.clientTop,ke.left+=ct.clientLeft}return X.top-=ke.top,X.bottom-=ke.top,X.left-=ke.left,X.right-=ke.left,w&&(X.top=Math.round(X.top),X.bottom=Math.round(X.bottom),X.left=Math.round(X.left),X.right=Math.round(X.right)),X}offset(fe,w=!0){const X=fe.getBoundingClientRect(),ke_top=window.pageYOffset-document.documentElement.clientTop,ke_left=window.pageXOffset-document.documentElement.clientLeft;let ct={height:X.height||fe.offsetHeight,width:X.width||fe.offsetWidth,top:X.top+ke_top,bottom:X.bottom+ke_top,left:X.left+ke_left,right:X.right+ke_left};return w&&(ct.height=Math.round(ct.height),ct.width=Math.round(ct.width),ct.top=Math.round(ct.top),ct.bottom=Math.round(ct.bottom),ct.left=Math.round(ct.left),ct.right=Math.round(ct.right)),ct}positionElements(fe,w,X,ke){const[ct="top",Jn="center"]=X.split("-"),Ir=ke?this.offset(fe,!1):this.position(fe,!1),vi=this.getAllStyles(w),zi=parseFloat(vi.marginTop),Do=parseFloat(vi.marginBottom),xs=parseFloat(vi.marginLeft),Xo=parseFloat(vi.marginRight);let Rs=0,ma=0;switch(ct){case"top":Rs=Ir.top-(w.offsetHeight+zi+Do);break;case"bottom":Rs=Ir.top+Ir.height;break;case"left":ma=Ir.left-(w.offsetWidth+xs+Xo);break;case"right":ma=Ir.left+Ir.width}switch(Jn){case"top":Rs=Ir.top;break;case"bottom":Rs=Ir.top+Ir.height-w.offsetHeight;break;case"left":ma=Ir.left;break;case"right":ma=Ir.left+Ir.width-w.offsetWidth;break;case"center":"top"===ct||"bottom"===ct?ma=Ir.left+Ir.width/2-w.offsetWidth/2:Rs=Ir.top+Ir.height/2-w.offsetHeight/2}w.style.transform=`translate(${Math.round(ma)}px, ${Math.round(Rs)}px)`;const Vs=w.getBoundingClientRect(),_c=document.documentElement,nd=window.innerHeight||_c.clientHeight,wu=window.innerWidth||_c.clientWidth;return Vs.left>=0&&Vs.top>=0&&Vs.right<=wu&&Vs.bottom<=nd}};function Ye(Y,fe,w,X,ke){let ct=Array.isArray(w)?w:w.split(Wd);const Jn=["top","bottom","left","right","top-left","top-right","bottom-left","bottom-right","left-top","left-bottom","right-top","right-bottom"],Ir=fe.classList,vi=Rs=>{const[ma,Vs]=Rs.split("-"),_c=[];return ke&&(_c.push(`${ke}-${ma}`),Vs&&_c.push(`${ke}-${ma}-${Vs}`),_c.forEach(nd=>{Ir.add(nd)})),_c};ke&&Jn.forEach(Rs=>{Ir.remove(`${ke}-${Rs}`)});let zi=ct.findIndex(Rs=>"auto"===Rs);zi>=0&&Jn.forEach(function(Rs){null==ct.find(ma=>-1!==ma.search("^"+Rs))&&ct.splice(zi++,1,Rs)});const Do=fe.style;Do.position="absolute",Do.top="0",Do.left="0",Do["will-change"]="transform";let xs=null,Xo=!1;for(xs of ct){let Rs=vi(xs);if(Jd.positionElements(Y,fe,xs,X)){Xo=!0;break}ke&&Rs.forEach(ma=>{Ir.remove(ma)})}return Xo||(xs=ct[0],vi(xs),Jd.positionElements(Y,fe,xs,X)),xs}function Ie(){return new ot}let Ce=(()=>{class Y{}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275prov=r.Yz7({factory:Ie,token:Y,providedIn:"root"}),Y})(),ot=(()=>{class Y extends Ce{parse(w){if(null!=w){const X=w.trim().split("-");if(1===X.length&&ko(X[0]))return{year:pi(X[0]),month:null,day:null};if(2===X.length&&ko(X[0])&&ko(X[1]))return{year:pi(X[0]),month:pi(X[1]),day:null};if(3===X.length&&ko(X[0])&&ko(X[1])&&ko(X[2]))return{year:pi(X[0]),month:pi(X[1]),day:pi(X[2])}}return null}format(w){return w?`${w.year}-${ko(w.month)?sl(w.month):""}-${ko(w.day)?sl(w.day):""}`:""}}return Y.\u0275fac=function(){let fe;return function(X){return(fe||(fe=r.n5z(Y)))(X||Y)}}(),Y.\u0275prov=r.Yz7({token:Y,factory:Y.\u0275fac}),Y})(),Et=(()=>{class Y extends Na{constructor(){super(...arguments),this.autoClose=!0,this.placement=["bottom-left","bottom-right","top-left","top-right"],this.restoreFocus=!0}}return Y.\u0275fac=function(){let fe;return function(X){return(fe||(fe=r.n5z(Y)))(X||Y)}}(),Y.\u0275prov=r.Yz7({factory:function(){return new Y},token:Y,providedIn:"root"}),Y})(),qt=(()=>{class Y{constructor(w,X,ke,ct,Jn,Ir,vi,zi,Do,xs,Xo){this._parserFormatter=w,this._elRef=X,this._vcRef=ke,this._renderer=ct,this._cfr=Jn,this._ngZone=Ir,this._calendar=vi,this._dateAdapter=zi,this._document=Do,this._changeDetector=xs,this._cRef=null,this._disabled=!1,this._elWithFocus=null,this._model=null,this.dateSelect=new r.vpe,this.navigate=new r.vpe,this.closed=new r.vpe,this._onChange=Rs=>{},this._onTouched=()=>{},this._validatorChange=()=>{},["autoClose","container","positionTarget","placement"].forEach(Rs=>this[Rs]=Xo[Rs]),this._zoneSubscription=Ir.onStable.subscribe(()=>this._updatePopupPosition())}get disabled(){return this._disabled}set disabled(w){this._disabled=""===w||w&&"false"!==w,this.isOpen()&&this._cRef.instance.setDisabledState(this._disabled)}registerOnChange(w){this._onChange=w}registerOnTouched(w){this._onTouched=w}registerOnValidatorChange(w){this._validatorChange=w}setDisabledState(w){this.disabled=w}validate(w){const{value:X}=w;if(null!=X){const ke=this._fromDateStruct(this._dateAdapter.fromModel(X));if(!ke)return{ngbDate:{invalid:X}};if(this.minDate&&ke.before(aa.from(this.minDate)))return{ngbDate:{minDate:{minDate:this.minDate,actual:X}}};if(this.maxDate&&ke.after(aa.from(this.maxDate)))return{ngbDate:{maxDate:{maxDate:this.maxDate,actual:X}}}}return null}writeValue(w){this._model=this._fromDateStruct(this._dateAdapter.fromModel(w)),this._writeModelValue(this._model)}manualDateChange(w,X=!1){const ke=w!==this._inputValue;ke&&(this._inputValue=w,this._model=this._fromDateStruct(this._parserFormatter.parse(w))),(ke||!X)&&this._onChange(this._model?this._dateAdapter.toModel(this._model):""===w?null:w),X&&this._model&&this._writeModelValue(this._model)}isOpen(){return!!this._cRef}open(){if(!this.isOpen()){const w=this._cfr.resolveComponentFactory(Gl);this._cRef=this._vcRef.createComponent(w),this._applyPopupStyling(this._cRef.location.nativeElement),this._applyDatepickerInputs(this._cRef.instance),this._subscribeForDatepickerOutputs(this._cRef.instance),this._cRef.instance.ngOnInit(),this._cRef.instance.writeValue(this._dateAdapter.toModel(this._model)),this._cRef.instance.registerOnChange(X=>{this.writeValue(X),this._onChange(X),this._onTouched()}),this._cRef.changeDetectorRef.detectChanges(),this._cRef.instance.setDisabledState(this.disabled),"body"===this.container&&this._document.querySelector(this.container).appendChild(this._cRef.location.nativeElement),this._elWithFocus=this._document.activeElement,df(this._ngZone,this._cRef.location.nativeElement,this.closed,!0),this._cRef.instance.focus(),la(this._ngZone,this._document,this.autoClose,()=>this.close(),this.closed,[],[this._elRef.nativeElement,this._cRef.location.nativeElement])}}close(){if(this.isOpen()){this._vcRef.remove(this._vcRef.indexOf(this._cRef.hostView)),this._cRef=null,this.closed.emit(),this._changeDetector.markForCheck();let w=this._elWithFocus;wo(this.restoreFocus)?w=this._document.querySelector(this.restoreFocus):void 0!==this.restoreFocus&&(w=this.restoreFocus),w&&w.focus?w.focus():this._document.body.focus()}}toggle(){this.isOpen()?this.close():this.open()}navigateTo(w){this.isOpen()&&this._cRef.instance.navigateTo(w)}onBlur(){this._onTouched()}onFocus(){this._elWithFocus=this._elRef.nativeElement}ngOnChanges(w){if((w.minDate||w.maxDate)&&(this._validatorChange(),this.isOpen()&&(w.minDate&&(this._cRef.instance.minDate=this.minDate),w.maxDate&&(this._cRef.instance.maxDate=this.maxDate),this._cRef.instance.ngOnChanges(w))),w.datepickerClass){const{currentValue:X,previousValue:ke}=w.datepickerClass;this._applyPopupClass(X,ke)}}ngOnDestroy(){this.close(),this._zoneSubscription.unsubscribe()}_applyDatepickerInputs(w){["dayTemplate","dayTemplateData","displayMonths","firstDayOfWeek","footerTemplate","markDisabled","minDate","maxDate","navigation","outsideDays","showNavigation","showWeekNumbers","weekdays"].forEach(X=>{void 0!==this[X]&&(w[X]=this[X])}),w.startDate=this.startDate||this._model}_applyPopupClass(w,X){var ke;const ct=null===(ke=this._cRef)||void 0===ke?void 0:ke.location.nativeElement;ct&&(w&&this._renderer.addClass(ct,w),X&&this._renderer.removeClass(ct,X))}_applyPopupStyling(w){this._renderer.addClass(w,"dropdown-menu"),this._renderer.addClass(w,"show"),"body"===this.container&&this._renderer.addClass(w,"ngb-dp-body"),this._applyPopupClass(this.datepickerClass)}_subscribeForDatepickerOutputs(w){w.navigate.subscribe(X=>this.navigate.emit(X)),w.dateSelect.subscribe(X=>{this.dateSelect.emit(X),(!0===this.autoClose||"inside"===this.autoClose)&&this.close()})}_writeModelValue(w){const X=this._parserFormatter.format(w);this._inputValue=X,this._renderer.setProperty(this._elRef.nativeElement,"value",X),this.isOpen()&&(this._cRef.instance.writeValue(this._dateAdapter.toModel(w)),this._onTouched())}_fromDateStruct(w){const X=w?new aa(w.year,w.month,w.day):null;return this._calendar.isValid(X)?X:null}_updatePopupPosition(){if(!this._cRef)return;let w;if(w=wo(this.positionTarget)?this._document.querySelector(this.positionTarget):this.positionTarget instanceof HTMLElement?this.positionTarget:this._elRef.nativeElement,this.positionTarget&&!w)throw new Error("ngbDatepicker could not find element declared in [positionTarget] to position against.");Ye(w,this._cRef.location.nativeElement,this.placement,"body"===this.container)}}return Y.\u0275fac=function(w){return new(w||Y)(r.Y36(Ce),r.Y36(r.SBq),r.Y36(r.s_b),r.Y36(r.Qsj),r.Y36(r._Vd),r.Y36(r.R0b),r.Y36(Ul),r.Y36(Qs),r.Y36(u.K0),r.Y36(r.sBO),r.Y36(Et))},Y.\u0275dir=r.lG2({type:Y,selectors:[["input","ngbDatepicker",""]],hostVars:1,hostBindings:function(w,X){1&w&&r.NdJ("input",function(ct){return X.manualDateChange(ct.target.value)})("change",function(ct){return X.manualDateChange(ct.target.value,!0)})("focus",function(){return X.onFocus()})("blur",function(){return X.onBlur()}),2&w&&r.Ikx("disabled",X.disabled)},inputs:{disabled:"disabled",autoClose:"autoClose",datepickerClass:"datepickerClass",dayTemplate:"dayTemplate",dayTemplateData:"dayTemplateData",displayMonths:"displayMonths",firstDayOfWeek:"firstDayOfWeek",footerTemplate:"footerTemplate",markDisabled:"markDisabled",minDate:"minDate",maxDate:"maxDate",navigation:"navigation",outsideDays:"outsideDays",placement:"placement",restoreFocus:"restoreFocus",showWeekNumbers:"showWeekNumbers",startDate:"startDate",container:"container",positionTarget:"positionTarget",weekdays:"weekdays"},outputs:{dateSelect:"dateSelect",navigate:"navigate",closed:"closed"},exportAs:["ngbDatepicker"],features:[r._Bn([{provide:Mr.JU,useExisting:(0,r.Gpc)(()=>Y),multi:!0},{provide:Mr.Cf,useExisting:(0,r.Gpc)(()=>Y),multi:!0},{provide:Na,useExisting:Et}]),r.TTD]}),Y})(),Hn=(()=>{class Y{constructor(w){this.i18n=w}isMuted(){return!this.selected&&(this.date.month!==this.currentMonth||this.disabled)}}return Y.\u0275fac=function(w){return new(w||Y)(r.Y36(Cr))},Y.\u0275cmp=r.Xpm({type:Y,selectors:[["","ngbDatepickerDayView",""]],hostAttrs:[1,"btn-light"],hostVars:10,hostBindings:function(w,X){2&w&&r.ekj("bg-primary",X.selected)("text-white",X.selected)("text-muted",X.isMuted())("outside",X.isMuted())("active",X.focused)},inputs:{currentMonth:"currentMonth",date:"date",disabled:"disabled",focused:"focused",selected:"selected"},attrs:dr,decls:1,vars:1,template:function(w,X){1&w&&r._uU(0),2&w&&r.Oqu(X.i18n.getDayNumerals(X.date))},styles:["[ngbDatepickerDayView]{text-align:center;width:2rem;height:2rem;line-height:2rem;border-radius:.25rem;background:transparent}[ngbDatepickerDayView].outside{opacity:.5}"],encapsulation:2,changeDetection:0}),Y})(),Xn=(()=>{class Y{constructor(w,X){this.i18n=w,this._renderer=X,this.select=new r.vpe,this._month=-1,this._year=-1}changeMonth(w){this.select.emit(new aa(this.date.year,pi(w),1))}changeYear(w){this.select.emit(new aa(pi(w),this.date.month,1))}ngAfterViewChecked(){this.date&&(this.date.month!==this._month&&(this._month=this.date.month,this._renderer.setProperty(this.monthSelect.nativeElement,"value",this._month)),this.date.year!==this._year&&(this._year=this.date.year,this._renderer.setProperty(this.yearSelect.nativeElement,"value",this._year)))}}return Y.\u0275fac=function(w){return new(w||Y)(r.Y36(Cr),r.Y36(r.Qsj))},Y.\u0275cmp=r.Xpm({type:Y,selectors:[["ngb-datepicker-navigation-select"]],viewQuery:function(w,X){if(1&w&&(r.Gf(Fn,7,r.SBq),r.Gf(ar,7,r.SBq)),2&w){let ke;r.iGM(ke=r.CRH())&&(X.monthSelect=ke.first),r.iGM(ke=r.CRH())&&(X.yearSelect=ke.first)}},inputs:{date:"date",disabled:"disabled",months:"months",years:"years"},outputs:{select:"select"},decls:6,vars:4,consts:function(){let fe,w,X,ke;return fe="Select month",w="Select month",X="Select year",ke="Select year",[["aria-label",fe,"title",w,1,"custom-select",3,"disabled","change"],["month",""],[3,"value",4,"ngFor","ngForOf"],["aria-label",X,"title",ke,1,"custom-select",3,"disabled","change"],["year",""],[3,"value"]]},template:function(w,X){1&w&&(r.TgZ(0,"select",0,1),r.NdJ("change",function(ct){return X.changeMonth(ct.target.value)}),r.YNc(2,Wi,2,3,"option",2),r.qZA(),r.TgZ(3,"select",3,4),r.NdJ("change",function(ct){return X.changeYear(ct.target.value)}),r.YNc(5,lo,2,2,"option",2),r.qZA()),2&w&&(r.Q6J("disabled",X.disabled),r.xp6(2),r.Q6J("ngForOf",X.months),r.xp6(1),r.Q6J("disabled",X.disabled),r.xp6(2),r.Q6J("ngForOf",X.years))},directives:[u.sg,Mr.YN,Mr.Kr],styles:["ngb-datepicker-navigation-select>.custom-select{flex:1 1 auto;padding:0 .5rem;font-size:.875rem;height:1.85rem}ngb-datepicker-navigation-select>.custom-select:focus{z-index:1}ngb-datepicker-navigation-select>.custom-select::-ms-value{background-color:transparent!important}"],encapsulation:2,changeDetection:0}),Y})();new Date(1882,10,12),new Date(2174,10,25);let Yu=(()=>{class Y{}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275mod=r.oAB({type:Y}),Y.\u0275inj=r.cJS({imports:[[u.ez,Mr.u5]]}),Y})(),pc=(()=>{class Y{constructor(){this.autoClose=!0,this.placement=["bottom-left","bottom-right","top-left","top-right"]}}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275prov=r.Yz7({factory:function(){return new Y},token:Y,providedIn:"root"}),Y})(),Kd=(()=>{class Y{}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275dir=r.lG2({type:Y,selectors:[["",8,"navbar"]]}),Y})(),wf=(()=>{class Y{constructor(w){this.elementRef=w,this._disabled=!1}set disabled(w){this._disabled=""===w||!0===w}get disabled(){return this._disabled}}return Y.\u0275fac=function(w){return new(w||Y)(r.Y36(r.SBq))},Y.\u0275dir=r.lG2({type:Y,selectors:[["","ngbDropdownItem",""]],hostAttrs:[1,"dropdown-item"],hostVars:2,hostBindings:function(w,X){2&w&&r.ekj("disabled",X.disabled)},inputs:{disabled:"disabled"}}),Y})(),Vl=(()=>{class Y{constructor(w,X){this.dropdown=w,this.placement="bottom",this.isOpen=!1,this.nativeElement=X.nativeElement}}return Y.\u0275fac=function(w){return new(w||Y)(r.Y36((0,r.Gpc)(()=>hc)),r.Y36(r.SBq))},Y.\u0275dir=r.lG2({type:Y,selectors:[["","ngbDropdownMenu",""]],contentQueries:function(w,X,ke){if(1&w&&r.Suo(ke,wf,4),2&w){let ct;r.iGM(ct=r.CRH())&&(X.menuItems=ct)}},hostVars:5,hostBindings:function(w,X){1&w&&r.NdJ("keydown.ArrowUp",function(ct){return X.dropdown.onKeyDown(ct)})("keydown.ArrowDown",function(ct){return X.dropdown.onKeyDown(ct)})("keydown.Home",function(ct){return X.dropdown.onKeyDown(ct)})("keydown.End",function(ct){return X.dropdown.onKeyDown(ct)})("keydown.Enter",function(ct){return X.dropdown.onKeyDown(ct)})("keydown.Space",function(ct){return X.dropdown.onKeyDown(ct)})("keydown.Tab",function(ct){return X.dropdown.onKeyDown(ct)})("keydown.Shift.Tab",function(ct){return X.dropdown.onKeyDown(ct)}),2&w&&(r.uIk("x-placement",X.placement),r.ekj("dropdown-menu",!0)("show",X.dropdown.isOpen()))}}),Y})(),Id=(()=>{class Y{constructor(w,X){this.dropdown=w,this.nativeElement=X.nativeElement}}return Y.\u0275fac=function(w){return new(w||Y)(r.Y36((0,r.Gpc)(()=>hc)),r.Y36(r.SBq))},Y.\u0275dir=r.lG2({type:Y,selectors:[["","ngbDropdownAnchor",""]],hostAttrs:[1,"dropdown-toggle"],hostVars:1,hostBindings:function(w,X){2&w&&r.uIk("aria-expanded",X.dropdown.isOpen())}}),Y})(),oc=(()=>{class Y extends Id{constructor(w,X){super(w,X)}}return Y.\u0275fac=function(w){return new(w||Y)(r.Y36((0,r.Gpc)(()=>hc)),r.Y36(r.SBq))},Y.\u0275dir=r.lG2({type:Y,selectors:[["","ngbDropdownToggle",""]],hostAttrs:[1,"dropdown-toggle"],hostVars:1,hostBindings:function(w,X){1&w&&r.NdJ("click",function(){return X.dropdown.toggle()})("keydown.ArrowUp",function(ct){return X.dropdown.onKeyDown(ct)})("keydown.ArrowDown",function(ct){return X.dropdown.onKeyDown(ct)})("keydown.Home",function(ct){return X.dropdown.onKeyDown(ct)})("keydown.End",function(ct){return X.dropdown.onKeyDown(ct)})("keydown.Tab",function(ct){return X.dropdown.onKeyDown(ct)})("keydown.Shift.Tab",function(ct){return X.dropdown.onKeyDown(ct)}),2&w&&r.uIk("aria-expanded",X.dropdown.isOpen())},features:[r._Bn([{provide:Id,useExisting:(0,r.Gpc)(()=>Y)}]),r.qOj]}),Y})(),hc=(()=>{class Y{constructor(w,X,ke,ct,Jn,Ir,vi){this._changeDetector=w,this._document=ke,this._ngZone=ct,this._elementRef=Jn,this._renderer=Ir,this._closed$=new he.xQ,this._bodyContainer=null,this._open=!1,this.openChange=new r.vpe,this.placement=X.placement,this.container=X.container,this.autoClose=X.autoClose,this.display=vi?"static":"dynamic",this._zoneSubscription=ct.onStable.subscribe(()=>{this._positionMenu()})}ngAfterContentInit(){this._ngZone.onStable.pipe((0,Bt.q)(1)).subscribe(()=>{this._applyPlacementClasses(),this._open&&this._setCloseHandlers()})}ngOnChanges(w){if(w.container&&this._open&&this._applyContainer(this.container),w.placement&&!w.placement.isFirstChange&&this._applyPlacementClasses(),w.dropdownClass){const{currentValue:X,previousValue:ke}=w.dropdownClass;this._applyCustomDropdownClass(X,ke)}}isOpen(){return this._open}open(){this._open||(this._open=!0,this._applyContainer(this.container),this.openChange.emit(!0),this._setCloseHandlers(),this._anchor&&this._anchor.nativeElement.focus())}_setCloseHandlers(){la(this._ngZone,this._document,this.autoClose,w=>{this.close(),0===w&&this._anchor.nativeElement.focus()},this._closed$,this._menu?[this._menu.nativeElement]:[],this._anchor?[this._anchor.nativeElement]:[],".dropdown-item,.dropdown-divider")}close(){this._open&&(this._open=!1,this._resetContainer(),this._closed$.next(),this.openChange.emit(!1),this._changeDetector.markForCheck())}toggle(){this.isOpen()?this.close():this.open()}ngOnDestroy(){this._resetContainer(),this._closed$.next(),this._zoneSubscription.unsubscribe()}onKeyDown(w){const X=w.which,ke=this._getMenuElements();let ct=-1,Jn=null;const Ir=this._isEventFromToggle(w);if(!Ir&&ke.length&&ke.forEach((vi,zi)=>{vi.contains(w.target)&&(Jn=vi),vi===this._document.activeElement&&(ct=zi)}),X!==$i.Space&&X!==$i.Enter){if(X!==$i.Tab){if(Ir||Jn){if(this.open(),ke.length){switch(X){case $i.ArrowDown:ct=Math.min(ct+1,ke.length-1);break;case $i.ArrowUp:if(this._isDropup()&&-1===ct){ct=ke.length-1;break}ct=Math.max(ct-1,0);break;case $i.Home:ct=0;break;case $i.End:ct=ke.length-1}ke[ct].focus()}w.preventDefault()}}else if(w.target&&this.isOpen()&&this.autoClose){if(this._anchor.nativeElement===w.target)return void("body"!==this.container||w.shiftKey?w.shiftKey&&this.close():(this._renderer.setAttribute(this._menu.nativeElement,"tabindex","0"),this._menu.nativeElement.focus(),this._renderer.removeAttribute(this._menu.nativeElement,"tabindex")));if("body"===this.container){const vi=this._menu.nativeElement.querySelectorAll(Zc);w.shiftKey&&w.target===vi[0]?(this._anchor.nativeElement.focus(),w.preventDefault()):!w.shiftKey&&w.target===vi[vi.length-1]&&(this._anchor.nativeElement.focus(),this.close())}else(0,ge.R)(w.target,"focusout").pipe((0,Bt.q)(1)).subscribe(({relatedTarget:vi})=>{this._elementRef.nativeElement.contains(vi)||this.close()})}}else Jn&&(!0===this.autoClose||"inside"===this.autoClose)&&(0,ge.R)(Jn,"click").pipe((0,Bt.q)(1)).subscribe(()=>this.close())}_isDropup(){return this._elementRef.nativeElement.classList.contains("dropup")}_isEventFromToggle(w){return this._anchor.nativeElement.contains(w.target)}_getMenuElements(){const w=this._menu;return null==w?[]:w.menuItems.filter(X=>!X.disabled).map(X=>X.elementRef.nativeElement)}_positionMenu(){const w=this._menu;this.isOpen()&&w&&this._applyPlacementClasses("dynamic"===this.display?Ye(this._anchor.nativeElement,this._bodyContainer||this._menu.nativeElement,this.placement,"body"===this.container):this._getFirstPlacement(this.placement))}_getFirstPlacement(w){return Array.isArray(w)?w[0]:w.split(" ")[0]}_resetContainer(){const w=this._renderer;if(this._menu){const ke=this._menu.nativeElement;w.appendChild(this._elementRef.nativeElement,ke),w.removeStyle(ke,"position"),w.removeStyle(ke,"transform")}this._bodyContainer&&(w.removeChild(this._document.body,this._bodyContainer),this._bodyContainer=null)}_applyContainer(w=null){if(this._resetContainer(),"body"===w){const X=this._renderer,ke=this._menu.nativeElement,ct=this._bodyContainer=this._bodyContainer||X.createElement("div");X.setStyle(ct,"position","absolute"),X.setStyle(ke,"position","static"),X.setStyle(ct,"z-index","1050"),X.appendChild(ct,ke),X.appendChild(this._document.body,ct)}this._applyCustomDropdownClass(this.dropdownClass)}_applyCustomDropdownClass(w,X){const ke="body"===this.container?this._bodyContainer:this._elementRef.nativeElement;ke&&(X&&this._renderer.removeClass(ke,X),w&&this._renderer.addClass(ke,w))}_applyPlacementClasses(w){const X=this._menu;if(X){w||(w=this._getFirstPlacement(this.placement));const ke=this._renderer,ct=this._elementRef.nativeElement;ke.removeClass(ct,"dropup"),ke.removeClass(ct,"dropdown"),X.placement="static"===this.display?null:w;const Jn=-1!==w.search("^top")?"dropup":"dropdown";ke.addClass(ct,Jn);const Ir=this._bodyContainer;Ir&&(ke.removeClass(Ir,"dropup"),ke.removeClass(Ir,"dropdown"),ke.addClass(Ir,Jn))}}}return Y.\u0275fac=function(w){return new(w||Y)(r.Y36(r.sBO),r.Y36(pc),r.Y36(u.K0),r.Y36(r.R0b),r.Y36(r.SBq),r.Y36(r.Qsj),r.Y36(Kd,8))},Y.\u0275dir=r.lG2({type:Y,selectors:[["","ngbDropdown",""]],contentQueries:function(w,X,ke){if(1&w&&(r.Suo(ke,Vl,5),r.Suo(ke,Id,5)),2&w){let ct;r.iGM(ct=r.CRH())&&(X._menu=ct.first),r.iGM(ct=r.CRH())&&(X._anchor=ct.first)}},hostVars:2,hostBindings:function(w,X){2&w&&r.ekj("show",X.isOpen())},inputs:{_open:["open","_open"],placement:"placement",container:"container",autoClose:"autoClose",display:"display",dropdownClass:"dropdownClass"},outputs:{openChange:"openChange"},exportAs:["ngbDropdown"],features:[r.TTD]}),Y})(),Mc=(()=>{class Y{}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275mod=r.oAB({type:Y}),Y.\u0275inj=r.cJS({}),Y})(),Hc=(()=>{class Y{constructor(w){this._ngbConfig=w,this.backdrop=!0,this.keyboard=!0}get animation(){return void 0===this._animation?this._ngbConfig.animation:this._animation}set animation(w){this._animation=w}}return Y.\u0275fac=function(w){return new(w||Y)(r.LFG(Fs))},Y.\u0275prov=r.Yz7({factory:function(){return new Y(r.LFG(Fs))},token:Y,providedIn:"root"}),Y})();class cu{constructor(fe,w,X){this.nodes=fe,this.viewRef=w,this.componentRef=X}}class Lu{constructor(fe,w,X,ke,ct,Jn,Ir){this._type=fe,this._injector=w,this._viewContainerRef=X,this._renderer=ke,this._ngZone=ct,this._componentFactoryResolver=Jn,this._applicationRef=Ir,this._windowRef=null,this._contentRef=null}open(fe,w,X=!1){this._windowRef||(this._contentRef=this._getContentRef(fe,w),this._windowRef=this._viewContainerRef.createComponent(this._componentFactoryResolver.resolveComponentFactory(this._type),this._viewContainerRef.length,this._injector,this._contentRef.nodes));const{nativeElement:ke}=this._windowRef.location,ct=this._ngZone.onStable.pipe((0,Bt.q)(1),(0,di.zg)(()=>Pt(this._ngZone,ke,({classList:Jn})=>Jn.add("show"),{animation:X,runningTransition:"continue"})));return{windowRef:this._windowRef,transition$:ct}}close(fe=!1){return this._windowRef?Pt(this._ngZone,this._windowRef.location.nativeElement,({classList:w})=>w.remove("show"),{animation:fe,runningTransition:"stop"}).pipe((0,Zn.b)(()=>{var w;this._windowRef&&(this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._windowRef.hostView)),this._windowRef=null),(null===(w=this._contentRef)||void 0===w?void 0:w.viewRef)&&(this._applicationRef.detachView(this._contentRef.viewRef),this._contentRef.viewRef.destroy(),this._contentRef=null)})):(0,ie.of)(void 0)}_getContentRef(fe,w){if(fe){if(fe instanceof r.Rgc){const X=fe.createEmbeddedView(w);return this._applicationRef.attachView(X),new cu([X.rootNodes],X)}return new cu([[this._renderer.createText(`${fe}`)]])}return new cu([])}}const Ou=()=>{};let bu=(()=>{class Y{constructor(w){this._document=w}compensate(){const w=this._getWidth();return this._isPresent(w)?this._adjustBody(w):Ou}_adjustBody(w){const X=this._document.body,ke=X.style.paddingRight,ct=parseFloat(window.getComputedStyle(X)["padding-right"]);return X.style["padding-right"]=`${ct+w}px`,()=>X.style["padding-right"]=ke}_isPresent(w){const X=this._document.body.getBoundingClientRect();return window.innerWidth-(X.left+X.right)>=w-.1*w}_getWidth(){const w=this._document.createElement("div");w.className="modal-scrollbar-measure";const X=this._document.body;X.appendChild(w);const ke=w.getBoundingClientRect().width-w.clientWidth;return X.removeChild(w),ke}}return Y.\u0275fac=function(w){return new(w||Y)(r.LFG(u.K0))},Y.\u0275prov=r.Yz7({factory:function(){return new Y(r.LFG(u.K0))},token:Y,providedIn:"root"}),Y})(),h=(()=>{class Y{constructor(w,X){this._el=w,this._zone=X}ngOnInit(){this._zone.onStable.asObservable().pipe((0,Bt.q)(1)).subscribe(()=>{Pt(this._zone,this._el.nativeElement,(w,X)=>{X&&Ec(w),w.classList.add("show")},{animation:this.animation,runningTransition:"continue"})})}hide(){return Pt(this._zone,this._el.nativeElement,({classList:w})=>w.remove("show"),{animation:this.animation,runningTransition:"stop"})}}return Y.\u0275fac=function(w){return new(w||Y)(r.Y36(r.SBq),r.Y36(r.R0b))},Y.\u0275cmp=r.Xpm({type:Y,selectors:[["ngb-modal-backdrop"]],hostAttrs:[2,"z-index","1050"],hostVars:6,hostBindings:function(w,X){2&w&&(r.Tol("modal-backdrop"+(X.backdropClass?" "+X.backdropClass:"")),r.ekj("show",!X.animation)("fade",X.animation))},inputs:{animation:"animation",backdropClass:"backdropClass"},decls:0,vars:0,template:function(w,X){},encapsulation:2}),Y})();class E{close(fe){}dismiss(fe){}}class R{constructor(fe,w,X,ke){this._windowCmptRef=fe,this._contentRef=w,this._backdropCmptRef=X,this._beforeDismiss=ke,this._closed=new he.xQ,this._dismissed=new he.xQ,this._hidden=new he.xQ,fe.instance.dismissEvent.subscribe(ct=>{this.dismiss(ct)}),this.result=new Promise((ct,Jn)=>{this._resolve=ct,this._reject=Jn}),this.result.then(null,()=>{})}get componentInstance(){if(this._contentRef&&this._contentRef.componentRef)return this._contentRef.componentRef.instance}get closed(){return this._closed.asObservable().pipe((0,Zt.R)(this._hidden))}get dismissed(){return this._dismissed.asObservable().pipe((0,Zt.R)(this._hidden))}get hidden(){return this._hidden.asObservable()}get shown(){return this._windowCmptRef.instance.shown.asObservable()}close(fe){this._windowCmptRef&&(this._closed.next(fe),this._resolve(fe),this._removeModalElements())}_dismiss(fe){this._dismissed.next(fe),this._reject(fe),this._removeModalElements()}dismiss(fe){if(this._windowCmptRef)if(this._beforeDismiss){const w=this._beforeDismiss();w&&w.then?w.then(X=>{!1!==X&&this._dismiss(fe)},()=>{}):!1!==w&&this._dismiss(fe)}else this._dismiss(fe)}_removeModalElements(){const fe=this._windowCmptRef.instance.hide(),w=this._backdropCmptRef?this._backdropCmptRef.instance.hide():(0,ie.of)(void 0);fe.subscribe(()=>{const{nativeElement:X}=this._windowCmptRef.location;X.parentNode.removeChild(X),this._windowCmptRef.destroy(),this._contentRef&&this._contentRef.viewRef&&this._contentRef.viewRef.destroy(),this._windowCmptRef=null,this._contentRef=null}),w.subscribe(()=>{if(this._backdropCmptRef){const{nativeElement:X}=this._backdropCmptRef.location;X.parentNode.removeChild(X),this._backdropCmptRef.destroy(),this._backdropCmptRef=null}}),J(fe,w).subscribe(()=>{this._hidden.next(),this._hidden.complete()})}}var F=(()=>{return(Y=F||(F={}))[Y.BACKDROP_CLICK=0]="BACKDROP_CLICK",Y[Y.ESC=1]="ESC",F;var Y})();let q=(()=>{class Y{constructor(w,X,ke){this._document=w,this._elRef=X,this._zone=ke,this._closed$=new he.xQ,this._elWithFocus=null,this.backdrop=!0,this.keyboard=!0,this.dismissEvent=new r.vpe,this.shown=new he.xQ,this.hidden=new he.xQ}dismiss(w){this.dismissEvent.emit(w)}ngOnInit(){this._elWithFocus=this._document.activeElement,this._zone.onStable.asObservable().pipe((0,Bt.q)(1)).subscribe(()=>{this._show()})}ngOnDestroy(){this._disableEventHandling()}hide(){const{nativeElement:w}=this._elRef,X={animation:this.animation,runningTransition:"stop"},Jn=J(Pt(this._zone,w,()=>w.classList.remove("show"),X),Pt(this._zone,this._dialogEl.nativeElement,()=>{},X));return Jn.subscribe(()=>{this.hidden.next(),this.hidden.complete()}),this._disableEventHandling(),this._restoreFocus(),Jn}_show(){const w={animation:this.animation,runningTransition:"continue"};J(Pt(this._zone,this._elRef.nativeElement,(ct,Jn)=>{Jn&&Ec(ct),ct.classList.add("show")},w),Pt(this._zone,this._dialogEl.nativeElement,()=>{},w)).subscribe(()=>{this.shown.next(),this.shown.complete()}),this._enableEventHandling(),this._setFocus()}_enableEventHandling(){const{nativeElement:w}=this._elRef;this._zone.runOutsideAngular(()=>{(0,ge.R)(w,"keydown").pipe((0,Zt.R)(this._closed$),(0,Ut.h)(ke=>ke.which===$i.Escape)).subscribe(ke=>{this.keyboard?requestAnimationFrame(()=>{ke.defaultPrevented||this._zone.run(()=>this.dismiss(F.ESC))}):"static"===this.backdrop&&this._bumpBackdrop()});let X=!1;(0,ge.R)(this._dialogEl.nativeElement,"mousedown").pipe((0,Zt.R)(this._closed$),(0,Zn.b)(()=>X=!1),(0,Xt.w)(()=>(0,ge.R)(w,"mouseup").pipe((0,Zt.R)(this._closed$),(0,Bt.q)(1))),(0,Ut.h)(({target:ke})=>w===ke)).subscribe(()=>{X=!0}),(0,ge.R)(w,"click").pipe((0,Zt.R)(this._closed$)).subscribe(({target:ke})=>{w===ke&&("static"===this.backdrop?this._bumpBackdrop():!0===this.backdrop&&!X&&this._zone.run(()=>this.dismiss(F.BACKDROP_CLICK))),X=!1})})}_disableEventHandling(){this._closed$.next()}_setFocus(){const{nativeElement:w}=this._elRef;if(!w.contains(document.activeElement)){const X=w.querySelector("[ngbAutofocus]"),ke=ic(w)[0];(X||ke||w).focus()}}_restoreFocus(){const w=this._document.body,X=this._elWithFocus;let ke;ke=X&&X.focus&&w.contains(X)?X:w,this._zone.runOutsideAngular(()=>{setTimeout(()=>ke.focus()),this._elWithFocus=null})}_bumpBackdrop(){"static"===this.backdrop&&Pt(this._zone,this._elRef.nativeElement,({classList:w})=>(w.add("modal-static"),()=>w.remove("modal-static")),{animation:this.animation,runningTransition:"continue"})}}return Y.\u0275fac=function(w){return new(w||Y)(r.Y36(u.K0),r.Y36(r.SBq),r.Y36(r.R0b))},Y.\u0275cmp=r.Xpm({type:Y,selectors:[["ngb-modal-window"]],viewQuery:function(w,X){if(1&w&&r.Gf(vo,7),2&w){let ke;r.iGM(ke=r.CRH())&&(X._dialogEl=ke.first)}},hostAttrs:["role","dialog","tabindex","-1"],hostVars:7,hostBindings:function(w,X){2&w&&(r.uIk("aria-modal",!0)("aria-labelledby",X.ariaLabelledBy)("aria-describedby",X.ariaDescribedBy),r.Tol("modal d-block"+(X.windowClass?" "+X.windowClass:"")),r.ekj("fade",X.animation))},inputs:{backdrop:"backdrop",keyboard:"keyboard",animation:"animation",ariaLabelledBy:"ariaLabelledBy",ariaDescribedBy:"ariaDescribedBy",centered:"centered",scrollable:"scrollable",size:"size",windowClass:"windowClass",modalDialogClass:"modalDialogClass"},outputs:{dismissEvent:"dismiss"},ngContentSelectors:Hi,decls:4,vars:2,consts:[["role","document"],["dialog",""],[1,"modal-content"]],template:function(w,X){1&w&&(r.F$t(),r.TgZ(0,"div",0,1),r.TgZ(2,"div",2),r.Hsn(3),r.qZA(),r.qZA()),2&w&&r.Tol("modal-dialog"+(X.size?" modal-"+X.size:"")+(X.centered?" modal-dialog-centered":"")+(X.scrollable?" modal-dialog-scrollable":"")+(X.modalDialogClass?" "+X.modalDialogClass:""))},styles:["ngb-modal-window .component-host-scrollable{display:flex;flex-direction:column;overflow:hidden}"],encapsulation:2}),Y})(),de=(()=>{class Y{constructor(w,X,ke,ct,Jn,Ir){this._applicationRef=w,this._injector=X,this._document=ke,this._scrollBar=ct,this._rendererFactory=Jn,this._ngZone=Ir,this._activeWindowCmptHasChanged=new he.xQ,this._ariaHiddenValues=new Map,this._backdropAttributes=["animation","backdropClass"],this._modalRefs=[],this._windowAttributes=["animation","ariaLabelledBy","ariaDescribedBy","backdrop","centered","keyboard","scrollable","size","windowClass","modalDialogClass"],this._windowCmpts=[],this._activeInstances=new r.vpe,this._activeWindowCmptHasChanged.subscribe(()=>{if(this._windowCmpts.length){const vi=this._windowCmpts[this._windowCmpts.length-1];df(this._ngZone,vi.location.nativeElement,this._activeWindowCmptHasChanged),this._revertAriaHidden(),this._setAriaHidden(vi.location.nativeElement)}})}open(w,X,ke,ct){const Jn=ct.container instanceof HTMLElement?ct.container:ba(ct.container)?this._document.querySelector(ct.container):this._document.body,Ir=this._rendererFactory.createRenderer(null,null),vi=this._scrollBar.compensate(),zi=()=>{this._modalRefs.length||(Ir.removeClass(this._document.body,"modal-open"),this._revertAriaHidden())};if(!Jn)throw new Error(`The specified modal container "${ct.container||"body"}" was not found in the DOM.`);const Do=new E,xs=this._getContentRef(w,ct.injector||X,ke,Do,ct);let Xo=!1!==ct.backdrop?this._attachBackdrop(w,Jn):void 0,Rs=this._attachWindowComponent(w,Jn,xs),ma=new R(Rs,xs,Xo,ct.beforeDismiss);return this._registerModalRef(ma),this._registerWindowCmpt(Rs),ma.result.then(vi,vi),ma.result.then(zi,zi),Do.close=Vs=>{ma.close(Vs)},Do.dismiss=Vs=>{ma.dismiss(Vs)},this._applyWindowOptions(Rs.instance,ct),1===this._modalRefs.length&&Ir.addClass(this._document.body,"modal-open"),Xo&&Xo.instance&&(this._applyBackdropOptions(Xo.instance,ct),Xo.changeDetectorRef.detectChanges()),Rs.changeDetectorRef.detectChanges(),ma}get activeInstances(){return this._activeInstances}dismissAll(w){this._modalRefs.forEach(X=>X.dismiss(w))}hasOpenModals(){return this._modalRefs.length>0}_attachBackdrop(w,X){let ct=w.resolveComponentFactory(h).create(this._injector);return this._applicationRef.attachView(ct.hostView),X.appendChild(ct.location.nativeElement),ct}_attachWindowComponent(w,X,ke){let Jn=w.resolveComponentFactory(q).create(this._injector,ke.nodes);return this._applicationRef.attachView(Jn.hostView),X.appendChild(Jn.location.nativeElement),Jn}_applyWindowOptions(w,X){this._windowAttributes.forEach(ke=>{ba(X[ke])&&(w[ke]=X[ke])})}_applyBackdropOptions(w,X){this._backdropAttributes.forEach(ke=>{ba(X[ke])&&(w[ke]=X[ke])})}_getContentRef(w,X,ke,ct,Jn){return ke?ke instanceof r.Rgc?this._createFromTemplateRef(ke,ct):wo(ke)?this._createFromString(ke):this._createFromComponent(w,X,ke,ct,Jn):new cu([])}_createFromTemplateRef(w,X){const ct=w.createEmbeddedView({$implicit:X,close(Jn){X.close(Jn)},dismiss(Jn){X.dismiss(Jn)}});return this._applicationRef.attachView(ct),new cu([ct.rootNodes],ct)}_createFromString(w){const X=this._document.createTextNode(`${w}`);return new cu([[X]])}_createFromComponent(w,X,ke,ct,Jn){const Ir=w.resolveComponentFactory(ke),vi=r.zs3.create({providers:[{provide:E,useValue:ct}],parent:X}),zi=Ir.create(vi),Do=zi.location.nativeElement;return Jn.scrollable&&Do.classList.add("component-host-scrollable"),this._applicationRef.attachView(zi.hostView),new cu([[Do]],zi.hostView,zi)}_setAriaHidden(w){const X=w.parentElement;X&&w!==this._document.body&&(Array.from(X.children).forEach(ke=>{ke!==w&&"SCRIPT"!==ke.nodeName&&(this._ariaHiddenValues.set(ke,ke.getAttribute("aria-hidden")),ke.setAttribute("aria-hidden","true"))}),this._setAriaHidden(X))}_revertAriaHidden(){this._ariaHiddenValues.forEach((w,X)=>{w?X.setAttribute("aria-hidden",w):X.removeAttribute("aria-hidden")}),this._ariaHiddenValues.clear()}_registerModalRef(w){const X=()=>{const ke=this._modalRefs.indexOf(w);ke>-1&&(this._modalRefs.splice(ke,1),this._activeInstances.emit(this._modalRefs))};this._modalRefs.push(w),this._activeInstances.emit(this._modalRefs),w.result.then(X,X)}_registerWindowCmpt(w){this._windowCmpts.push(w),this._activeWindowCmptHasChanged.next(),w.onDestroy(()=>{const X=this._windowCmpts.indexOf(w);X>-1&&(this._windowCmpts.splice(X,1),this._activeWindowCmptHasChanged.next())})}}return Y.\u0275fac=function(w){return new(w||Y)(r.LFG(r.z2F),r.LFG(r.zs3),r.LFG(u.K0),r.LFG(bu),r.LFG(r.FYo),r.LFG(r.R0b))},Y.\u0275prov=r.Yz7({factory:function(){return new Y(r.LFG(r.z2F),r.LFG(r.gxx),r.LFG(u.K0),r.LFG(bu),r.LFG(r.FYo),r.LFG(r.R0b))},token:Y,providedIn:"root"}),Y})(),ye=(()=>{class Y{constructor(w,X,ke,ct){this._moduleCFR=w,this._injector=X,this._modalStack=ke,this._config=ct}open(w,X={}){const ke=Object.assign(Object.assign(Object.assign({},this._config),{animation:this._config.animation}),X);return this._modalStack.open(this._moduleCFR,this._injector,w,ke)}get activeInstances(){return this._modalStack.activeInstances}dismissAll(w){this._modalStack.dismissAll(w)}hasOpenModals(){return this._modalStack.hasOpenModals()}}return Y.\u0275fac=function(w){return new(w||Y)(r.LFG(r._Vd),r.LFG(r.zs3),r.LFG(de),r.LFG(Hc))},Y.\u0275prov=r.Yz7({factory:function(){return new Y(r.LFG(r._Vd),r.LFG(r.gxx),r.LFG(de),r.LFG(Hc))},token:Y,providedIn:"root"}),Y})(),Oe=(()=>{class Y{constructor(w){this._ngbConfig=w,this.destroyOnHide=!0,this.orientation="horizontal",this.roles="tablist",this.keyboard=!1}get animation(){return void 0===this._animation?this._ngbConfig.animation:this._animation}set animation(w){this._animation=w}}return Y.\u0275fac=function(w){return new(w||Y)(r.LFG(Fs))},Y.\u0275prov=r.Yz7({factory:function(){return new Y(r.LFG(Fs))},token:Y,providedIn:"root"}),Y})();const Xe=Y=>ba(Y)&&""!==Y;let yt=0,nn=(()=>{class Y{constructor(w){this.templateRef=w}}return Y.\u0275fac=function(w){return new(w||Y)(r.Y36(r.Rgc))},Y.\u0275dir=r.lG2({type:Y,selectors:[["ng-template","ngbNavContent",""]]}),Y})(),Pn=(()=>{class Y{constructor(w,X){this.elementRef=X,this.disabled=!1,this.shown=new r.vpe,this.hidden=new r.vpe,this._nav=w}ngAfterContentChecked(){this.contentTpl=this.contentTpls.first}ngOnInit(){ba(this.domId)||(this.domId="ngb-nav-"+yt++)}get active(){return this._nav.activeId===this.id}get id(){return Xe(this._id)?this._id:this.domId}get panelDomId(){return`${this.domId}-panel`}isPanelInDom(){return(ba(this.destroyOnHide)?!this.destroyOnHide:!this._nav.destroyOnHide)||this.active}}return Y.\u0275fac=function(w){return new(w||Y)(r.Y36((0,r.Gpc)(()=>xn)),r.Y36(r.SBq))},Y.\u0275dir=r.lG2({type:Y,selectors:[["","ngbNavItem",""]],contentQueries:function(w,X,ke){if(1&w&&r.Suo(ke,nn,4),2&w){let ct;r.iGM(ct=r.CRH())&&(X.contentTpls=ct)}},hostVars:2,hostBindings:function(w,X){2&w&&r.ekj("nav-item",!0)},inputs:{disabled:"disabled",domId:"domId",destroyOnHide:"destroyOnHide",_id:["ngbNavItem","_id"]},outputs:{shown:"shown",hidden:"hidden"},exportAs:["ngbNavItem"]}),Y})(),xn=(()=>{class Y{constructor(w,X,ke,ct){this.role=w,this._cd=ke,this._document=ct,this.activeIdChange=new r.vpe,this.shown=new r.vpe,this.hidden=new r.vpe,this.destroy$=new he.xQ,this.navItemChange$=new he.xQ,this.navChange=new r.vpe,this.animation=X.animation,this.destroyOnHide=X.destroyOnHide,this.orientation=X.orientation,this.roles=X.roles,this.keyboard=X.keyboard}click(w){w.disabled||this._updateActiveId(w.id)}onKeyDown(w){if("tablist"!==this.roles||!this.keyboard)return;const X=w.which,ke=this.links.filter(Ir=>!Ir.navItem.disabled),{length:ct}=ke;let Jn=-1;if(ke.forEach((Ir,vi)=>{Ir.elRef.nativeElement===this._document.activeElement&&(Jn=vi)}),ct){switch(X){case $i.ArrowLeft:if("vertical"===this.orientation)return;Jn=(Jn-1+ct)%ct;break;case $i.ArrowRight:if("vertical"===this.orientation)return;Jn=(Jn+1)%ct;break;case $i.ArrowDown:if("horizontal"===this.orientation)return;Jn=(Jn+1)%ct;break;case $i.ArrowUp:if("horizontal"===this.orientation)return;Jn=(Jn-1+ct)%ct;break;case $i.Home:Jn=0;break;case $i.End:Jn=ct-1}"changeWithArrows"===this.keyboard&&this.select(ke[Jn].navItem.id),ke[Jn].elRef.nativeElement.focus(),w.preventDefault()}}select(w){this._updateActiveId(w,!1)}ngAfterContentInit(){if(!ba(this.activeId)){const w=this.items.first?this.items.first.id:null;Xe(w)&&(this._updateActiveId(w,!1),this._cd.detectChanges())}this.items.changes.pipe((0,Zt.R)(this.destroy$)).subscribe(()=>this._notifyItemChanged(this.activeId))}ngOnChanges({activeId:w}){w&&!w.firstChange&&this._notifyItemChanged(w.currentValue)}ngOnDestroy(){this.destroy$.next()}_updateActiveId(w,X=!0){if(this.activeId!==w){let ke=!1;X&&this.navChange.emit({activeId:this.activeId,nextId:w,preventDefault:()=>{ke=!0}}),ke||(this.activeId=w,this.activeIdChange.emit(w),this._notifyItemChanged(w))}}_notifyItemChanged(w){this.navItemChange$.next(this._getItemById(w))}_getItemById(w){return this.items&&this.items.find(X=>X.id===w)||null}}return Y.\u0275fac=function(w){return new(w||Y)(r.$8M("role"),r.Y36(Oe),r.Y36(r.sBO),r.Y36(u.K0))},Y.\u0275dir=r.lG2({type:Y,selectors:[["","ngbNav",""]],contentQueries:function(w,X,ke){if(1&w&&(r.Suo(ke,Pn,4),r.Suo(ke,ir,5)),2&w){let ct;r.iGM(ct=r.CRH())&&(X.items=ct),r.iGM(ct=r.CRH())&&(X.links=ct)}},hostVars:6,hostBindings:function(w,X){1&w&&r.NdJ("keydown.arrowLeft",function(ct){return X.onKeyDown(ct)})("keydown.arrowRight",function(ct){return X.onKeyDown(ct)})("keydown.arrowDown",function(ct){return X.onKeyDown(ct)})("keydown.arrowUp",function(ct){return X.onKeyDown(ct)})("keydown.Home",function(ct){return X.onKeyDown(ct)})("keydown.End",function(ct){return X.onKeyDown(ct)}),2&w&&(r.uIk("aria-orientation","vertical"===X.orientation&&"tablist"===X.roles?"vertical":void 0)("role",X.role?X.role:X.roles?"tablist":void 0),r.ekj("nav",!0)("flex-column","vertical"===X.orientation))},inputs:{animation:"animation",destroyOnHide:"destroyOnHide",orientation:"orientation",roles:"roles",keyboard:"keyboard",activeId:"activeId"},outputs:{activeIdChange:"activeIdChange",shown:"shown",hidden:"hidden",navChange:"navChange"},exportAs:["ngbNav"],features:[r.TTD]}),Y})(),ir=(()=>{class Y{constructor(w,X,ke,ct){this.role=w,this.navItem=X,this.nav=ke,this.elRef=ct}hasNavItemClass(){return this.navItem.elementRef.nativeElement.nodeType===Node.COMMENT_NODE}}return Y.\u0275fac=function(w){return new(w||Y)(r.$8M("role"),r.Y36(Pn),r.Y36(xn),r.Y36(r.SBq))},Y.\u0275dir=r.lG2({type:Y,selectors:[["a","ngbNavLink",""]],hostAttrs:["href",""],hostVars:14,hostBindings:function(w,X){1&w&&r.NdJ("click",function(ct){return X.nav.click(X.navItem),ct.preventDefault()}),2&w&&(r.Ikx("id",X.navItem.domId),r.uIk("role",X.role?X.role:X.nav.roles?"tab":void 0)("tabindex",X.navItem.disabled?-1:void 0)("aria-controls",X.navItem.isPanelInDom()?X.navItem.panelDomId:null)("aria-selected",X.navItem.active)("aria-disabled",X.navItem.disabled),r.ekj("nav-link",!0)("nav-item",X.hasNavItemClass())("active",X.navItem.active)("disabled",X.navItem.disabled))}}),Y})();const Gr=({classList:Y})=>(Y.remove("show"),()=>Y.remove("active")),Pi=(Y,fe)=>{fe&&Ec(Y),Y.classList.add("show")};let Zo=(()=>{class Y{constructor(w){this.elRef=w}}return Y.\u0275fac=function(w){return new(w||Y)(r.Y36(r.SBq))},Y.\u0275dir=r.lG2({type:Y,selectors:[["","ngbNavPane",""]],hostAttrs:[1,"tab-pane"],hostVars:5,hostBindings:function(w,X){2&w&&(r.Ikx("id",X.item.panelDomId),r.uIk("role",X.role?X.role:X.nav.roles?"tabpanel":void 0)("aria-labelledby",X.item.domId),r.ekj("fade",X.nav.animation))},inputs:{item:"item",nav:"nav",role:"role"}}),Y})(),Lo=(()=>{class Y{constructor(w,X){this._cd=w,this._ngZone=X,this._activePane=null}isPanelTransitioning(w){var X;return(null===(X=this._activePane)||void 0===X?void 0:X.item)===w}ngAfterViewInit(){var w;this._updateActivePane(),this.nav.navItemChange$.pipe((0,Zt.R)(this.nav.destroy$),(0,Gt.O)((null===(w=this._activePane)||void 0===w?void 0:w.item)||null),(0,xt.x)(),fe=>fe.lift(new tn(1))).subscribe(X=>{const ke={animation:this.nav.animation,runningTransition:"stop"};this._cd.detectChanges(),this._activePane?Pt(this._ngZone,this._activePane.elRef.nativeElement,Gr,ke).subscribe(()=>{var ct;const Jn=null===(ct=this._activePane)||void 0===ct?void 0:ct.item;this._activePane=this._getPaneForItem(X),this._cd.markForCheck(),this._activePane&&(this._activePane.elRef.nativeElement.classList.add("active"),Pt(this._ngZone,this._activePane.elRef.nativeElement,Pi,ke).subscribe(()=>{X&&(X.shown.emit(),this.nav.shown.emit(X.id))})),Jn&&(Jn.hidden.emit(),this.nav.hidden.emit(Jn.id))}):this._updateActivePane()})}_updateActivePane(){var w,X;this._activePane=this._getActivePane(),null===(w=this._activePane)||void 0===w||w.elRef.nativeElement.classList.add("show"),null===(X=this._activePane)||void 0===X||X.elRef.nativeElement.classList.add("active")}_getPaneForItem(w){return this._panes&&this._panes.find(X=>X.item===w)||null}_getActivePane(){return this._panes&&this._panes.find(w=>w.item.active)||null}}return Y.\u0275fac=function(w){return new(w||Y)(r.Y36(r.sBO),r.Y36(r.R0b))},Y.\u0275cmp=r.Xpm({type:Y,selectors:[["","ngbNavOutlet",""]],viewQuery:function(w,X){if(1&w&&r.Gf(Zo,5),2&w){let ke;r.iGM(ke=r.CRH())&&(X._panes=ke)}},hostVars:2,hostBindings:function(w,X){2&w&&r.ekj("tab-content",!0)},inputs:{paneRole:"paneRole",nav:["ngbNavOutlet","nav"]},attrs:Co,decls:1,vars:1,consts:[["ngFor","",3,"ngForOf"],["ngbNavPane","",3,"item","nav","role",4,"ngIf"],["ngbNavPane","",3,"item","nav","role"],[3,"ngTemplateOutlet","ngTemplateOutletContext"]],template:function(w,X){1&w&&r.YNc(0,To,1,1,"ng-template",0),2&w&&r.Q6J("ngForOf",X.nav.items)},directives:[u.sg,u.O5,Zo,u.tP],encapsulation:2,changeDetection:0}),Y})(),Xs=(()=>{class Y{}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275mod=r.oAB({type:Y}),Y.\u0275inj=r.cJS({imports:[[u.ez]]}),Y})();class gf{constructor(fe,w){this.open=fe,this.close=w,w||(this.close=fe)}isManual(){return"manual"===this.open||"manual"===this.close}}const Ga={hover:["mouseenter","mouseleave"],focus:["focusin","focusout"]},nt=Y=>Y>0?(0,Ur.g)(Y):fe=>fe;function cr(Y,fe,w,X,ke,ct,Jn=0,Ir=0){const vi=function(Y,fe=Ga){const w=(Y||"").trim();if(0===w.length)return[];const X=w.split(/\s+/).map(ct=>ct.split(":")).map(ct=>{let Jn=fe[ct[0]]||ct;return new gf(Jn[0],Jn[1])}),ke=X.filter(ct=>ct.isManual());if(ke.length>1)throw"Triggers parse error: only one manual trigger is allowed";if(1===ke.length&&X.length>1)throw"Triggers parse error: manual trigger can't be mixed with other triggers";return X}(w);if(1===vi.length&&vi[0].isManual())return()=>{};const zi=function(Y,fe,w,X){return new se.y(ke=>{const ct=[],Jn=()=>ke.next(!0),Ir=()=>ke.next(!1),vi=()=>ke.next(!X());return w.forEach(zi=>{zi.open===zi.close?ct.push(Y.listen(fe,zi.open,vi)):ct.push(Y.listen(fe,zi.open,Jn),Y.listen(fe,zi.close,Ir))}),()=>{ct.forEach(zi=>zi())}})}(Y,fe,vi,X).pipe(function(Y,fe,w){return X=>{let ke=null;const ct=X.pipe((0,bt.U)(vi=>({open:vi})),(0,Ut.h)(vi=>{const zi=w();return zi===vi.open||ke&&ke.open!==zi?(ke&&ke.open!==vi.open&&(ke=null),!1):(ke=vi,!0)}),(0,Lr.B)()),Jn=ct.pipe((0,Ut.h)(vi=>vi.open),nt(Y)),Ir=ct.pipe((0,Ut.h)(vi=>!vi.open),nt(fe));return(0,ze.T)(Jn,Ir).pipe((0,Ut.h)(vi=>vi===ke&&(ke=null,vi.open!==w())),(0,bt.U)(vi=>vi.open))}}(Jn,Ir,X)).subscribe(Do=>Do?ke():ct());return()=>zi.unsubscribe()}let bi=(()=>{class Y{constructor(w){this._ngbConfig=w,this.autoClose=!0,this.placement="auto",this.triggers="click",this.disablePopover=!1,this.openDelay=0,this.closeDelay=0}get animation(){return void 0===this._animation?this._ngbConfig.animation:this._animation}set animation(w){this._animation=w}}return Y.\u0275fac=function(w){return new(w||Y)(r.LFG(Fs))},Y.\u0275prov=r.Yz7({factory:function(){return new Y(r.LFG(Fs))},token:Y,providedIn:"root"}),Y})(),cs=0,xa=(()=>{class Y{isTitleTemplate(){return this.title instanceof r.Rgc}}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275cmp=r.Xpm({type:Y,selectors:[["ngb-popover-window"]],hostAttrs:["role","tooltip"],hostVars:5,hostBindings:function(w,X){2&w&&(r.Ikx("id",X.id),r.Tol("popover"+(X.popoverClass?" "+X.popoverClass:"")),r.ekj("fade",X.animation))},inputs:{animation:"animation",title:"title",id:"id",popoverClass:"popoverClass",context:"context"},ngContentSelectors:Hi,decls:4,vars:1,consts:[[1,"arrow"],["class","popover-header",4,"ngIf"],[1,"popover-body"],[1,"popover-header"],["simpleTitle",""],[3,"ngTemplateOutlet","ngTemplateOutletContext"]],template:function(w,X){1&w&&(r.F$t(),r._UZ(0,"div",0),r.YNc(1,el,4,2,"h3",1),r.TgZ(2,"div",2),r.Hsn(3),r.qZA()),2&w&&(r.xp6(1),r.Q6J("ngIf",X.title))},directives:[u.O5,u.tP],styles:["ngb-popover-window.bs-popover-bottom>.arrow,ngb-popover-window.bs-popover-top>.arrow{left:50%;margin-left:-.5rem}ngb-popover-window.bs-popover-bottom-left>.arrow,ngb-popover-window.bs-popover-top-left>.arrow{left:2em}ngb-popover-window.bs-popover-bottom-right>.arrow,ngb-popover-window.bs-popover-top-right>.arrow{left:auto;right:2em}ngb-popover-window.bs-popover-left>.arrow,ngb-popover-window.bs-popover-right>.arrow{top:50%;margin-top:-.5rem}ngb-popover-window.bs-popover-left-top>.arrow,ngb-popover-window.bs-popover-right-top>.arrow{top:.7em}ngb-popover-window.bs-popover-left-bottom>.arrow,ngb-popover-window.bs-popover-right-bottom>.arrow{top:auto;bottom:.7em}"],encapsulation:2,changeDetection:0}),Y})(),na=(()=>{class Y{constructor(w,X,ke,ct,Jn,Ir,vi,zi,Do,xs){this._elementRef=w,this._renderer=X,this._ngZone=vi,this._document=zi,this._changeDetector=Do,this.shown=new r.vpe,this.hidden=new r.vpe,this._ngbPopoverWindowId="ngb-popover-"+cs++,this._windowRef=null,this.animation=Ir.animation,this.autoClose=Ir.autoClose,this.placement=Ir.placement,this.triggers=Ir.triggers,this.container=Ir.container,this.disablePopover=Ir.disablePopover,this.popoverClass=Ir.popoverClass,this.openDelay=Ir.openDelay,this.closeDelay=Ir.closeDelay,this._popupService=new Lu(xa,ke,Jn,X,this._ngZone,ct,xs),this._zoneSubscription=vi.onStable.subscribe(()=>{this._windowRef&&Ye(this._elementRef.nativeElement,this._windowRef.location.nativeElement,this.placement,"body"===this.container,"bs-popover")})}_isDisabled(){return!(!this.disablePopover&&(this.ngbPopover||this.popoverTitle))}open(w){if(!this._windowRef&&!this._isDisabled()){const{windowRef:X,transition$:ke}=this._popupService.open(this.ngbPopover,w,this.animation);this._windowRef=X,this._windowRef.instance.animation=this.animation,this._windowRef.instance.title=this.popoverTitle,this._windowRef.instance.context=w,this._windowRef.instance.popoverClass=this.popoverClass,this._windowRef.instance.id=this._ngbPopoverWindowId,this._renderer.setAttribute(this._elementRef.nativeElement,"aria-describedby",this._ngbPopoverWindowId),"body"===this.container&&this._document.querySelector(this.container).appendChild(this._windowRef.location.nativeElement),this._windowRef.changeDetectorRef.detectChanges(),this._windowRef.changeDetectorRef.markForCheck(),la(this._ngZone,this._document,this.autoClose,()=>this.close(),this.hidden,[this._windowRef.location.nativeElement]),ke.subscribe(()=>this.shown.emit())}}close(){this._windowRef&&(this._renderer.removeAttribute(this._elementRef.nativeElement,"aria-describedby"),this._popupService.close(this.animation).subscribe(()=>{this._windowRef=null,this.hidden.emit(),this._changeDetector.markForCheck()}))}toggle(){this._windowRef?this.close():this.open()}isOpen(){return null!=this._windowRef}ngOnInit(){this._unregisterListenersFn=cr(this._renderer,this._elementRef.nativeElement,this.triggers,this.isOpen.bind(this),this.open.bind(this),this.close.bind(this),+this.openDelay,+this.closeDelay)}ngOnChanges({ngbPopover:w,popoverTitle:X,disablePopover:ke,popoverClass:ct}){ct&&this.isOpen()&&(this._windowRef.instance.popoverClass=ct.currentValue),(w||X||ke)&&this._isDisabled()&&this.close()}ngOnDestroy(){this.close(),this._unregisterListenersFn&&this._unregisterListenersFn(),this._zoneSubscription.unsubscribe()}}return Y.\u0275fac=function(w){return new(w||Y)(r.Y36(r.SBq),r.Y36(r.Qsj),r.Y36(r.zs3),r.Y36(r._Vd),r.Y36(r.s_b),r.Y36(bi),r.Y36(r.R0b),r.Y36(u.K0),r.Y36(r.sBO),r.Y36(r.z2F))},Y.\u0275dir=r.lG2({type:Y,selectors:[["","ngbPopover",""]],inputs:{animation:"animation",autoClose:"autoClose",placement:"placement",triggers:"triggers",container:"container",disablePopover:"disablePopover",popoverClass:"popoverClass",openDelay:"openDelay",closeDelay:"closeDelay",ngbPopover:"ngbPopover",popoverTitle:"popoverTitle"},outputs:{shown:"shown",hidden:"hidden"},exportAs:["ngbPopover"],features:[r.TTD]}),Y})(),_l=(()=>{class Y{}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275mod=r.oAB({type:Y}),Y.\u0275inj=r.cJS({imports:[[u.ez]]}),Y})(),zl=(()=>{class Y{constructor(){this.max=100,this.animated=!1,this.striped=!1,this.showValue=!1}}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275prov=r.Yz7({factory:function(){return new Y},token:Y,providedIn:"root"}),Y})(),zc=(()=>{class Y{constructor(w){this.value=0,this.max=w.max,this.animated=w.animated,this.striped=w.striped,this.textType=w.textType,this.type=w.type,this.showValue=w.showValue,this.height=w.height}set max(w){this._max=!ko(w)||w<=0?100:w}get max(){return this._max}getValue(){return function(Y,fe,w=0){return Math.max(Math.min(Y,fe),w)}(this.value,this.max)}getPercentValue(){return 100*this.getValue()/this.max}}return Y.\u0275fac=function(w){return new(w||Y)(r.Y36(zl))},Y.\u0275cmp=r.Xpm({type:Y,selectors:[["ngb-progressbar"]],hostAttrs:[1,"progress"],hostVars:2,hostBindings:function(w,X){2&w&&r.Udp("height",X.height)},inputs:{value:"value",max:"max",animated:"animated",striped:"striped",textType:"textType",type:"type",showValue:"showValue",height:"height"},ngContentSelectors:Hi,decls:3,vars:11,consts:function(){let fe;return fe="" + "\ufffd0\ufffd" + "",[["role","progressbar","aria-valuemin","0"],[4,"ngIf"],fe]},template:function(w,X){1&w&&(r.F$t(),r.TgZ(0,"div",0),r.YNc(1,ca,3,3,"span",1),r.Hsn(2),r.qZA()),2&w&&(r.DjV("progress-bar",X.type?" bg-"+X.type:"","",X.textType?" text-"+X.textType:"","\n ",X.animated?" progress-bar-animated":"","",X.striped?" progress-bar-striped":"",""),r.Udp("width",X.getPercentValue(),"%"),r.uIk("aria-valuenow",X.getValue())("aria-valuemax",X.max),r.xp6(1),r.Q6J("ngIf",X.showValue))},directives:[u.O5],pipes:[u.Zx],encapsulation:2,changeDetection:0}),Y})(),qf=(()=>{class Y{}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275mod=r.oAB({type:Y}),Y.\u0275inj=r.cJS({imports:[[u.ez]]}),Y})();class mf{constructor(fe,w,X){this.hour=pi(fe),this.minute=pi(w),this.second=pi(X)}changeHour(fe=1){this.updateHour((isNaN(this.hour)?0:this.hour)+fe)}updateHour(fe){this.hour=ko(fe)?(fe<0?24+fe:fe)%24:NaN}changeMinute(fe=1){this.updateMinute((isNaN(this.minute)?0:this.minute)+fe)}updateMinute(fe){ko(fe)?(this.minute=fe%60<0?60+fe%60:fe%60,this.changeHour(Math.floor(fe/60))):this.minute=NaN}changeSecond(fe=1){this.updateSecond((isNaN(this.second)?0:this.second)+fe)}updateSecond(fe){ko(fe)?(this.second=fe<0?60+fe%60:fe%60,this.changeMinute(Math.floor(fe/60))):this.second=NaN}isValid(fe=!0){return ko(this.hour)&&ko(this.minute)&&(!fe||ko(this.second))}toString(){return`${this.hour||0}:${this.minute||0}:${this.second||0}`}}let Uf=(()=>{class Y{constructor(){this.meridian=!1,this.spinners=!0,this.seconds=!1,this.hourStep=1,this.minuteStep=1,this.secondStep=1,this.disabled=!1,this.readonlyInputs=!1,this.size="medium"}}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275prov=r.Yz7({factory:function(){return new Y},token:Y,providedIn:"root"}),Y})();function dd(){return new pp}let Fd=(()=>{class Y{}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275prov=r.Yz7({factory:dd,token:Y,providedIn:"root"}),Y})(),pp=(()=>{class Y extends Fd{fromModel(w){return w&&Eo(w.hour)&&Eo(w.minute)?{hour:w.hour,minute:w.minute,second:Eo(w.second)?w.second:null}:null}toModel(w){return w&&Eo(w.hour)&&Eo(w.minute)?{hour:w.hour,minute:w.minute,second:Eo(w.second)?w.second:null}:null}}return Y.\u0275fac=function(){let fe;return function(X){return(fe||(fe=r.n5z(Y)))(X||Y)}}(),Y.\u0275prov=r.Yz7({token:Y,factory:Y.\u0275fac}),Y})(),i_=(()=>{class Y{}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275prov=r.Yz7({factory:function(){return function(Y){return new F_(Y)}(r.LFG(r.soG))},token:Y,providedIn:"root"}),Y})(),F_=(()=>{class Y extends i_{constructor(w){super(),this._periods=(0,u.ol)(w,u.x.Standalone,u.Tn.Narrow)}getMorningPeriod(){return this._periods[0]}getAfternoonPeriod(){return this._periods[1]}}return Y.\u0275fac=function(w){return new(w||Y)(r.LFG(r.soG))},Y.\u0275prov=r.Yz7({token:Y,factory:Y.\u0275fac}),Y})();const Bf=/[^0-9]/g;let kd=(()=>{class Y{constructor(w,X,ke,ct){this._config=w,this._ngbTimeAdapter=X,this._cd=ke,this.i18n=ct,this.onChange=Jn=>{},this.onTouched=()=>{},this.meridian=w.meridian,this.spinners=w.spinners,this.seconds=w.seconds,this.hourStep=w.hourStep,this.minuteStep=w.minuteStep,this.secondStep=w.secondStep,this.disabled=w.disabled,this.readonlyInputs=w.readonlyInputs,this.size=w.size}set hourStep(w){this._hourStep=Eo(w)?w:this._config.hourStep}get hourStep(){return this._hourStep}set minuteStep(w){this._minuteStep=Eo(w)?w:this._config.minuteStep}get minuteStep(){return this._minuteStep}set secondStep(w){this._secondStep=Eo(w)?w:this._config.secondStep}get secondStep(){return this._secondStep}writeValue(w){const X=this._ngbTimeAdapter.fromModel(w);this.model=X?new mf(X.hour,X.minute,X.second):new mf,!this.seconds&&(!X||!ko(X.second))&&(this.model.second=0),this._cd.markForCheck()}registerOnChange(w){this.onChange=w}registerOnTouched(w){this.onTouched=w}setDisabledState(w){this.disabled=w}changeHour(w){this.model.changeHour(w),this.propagateModelChange()}changeMinute(w){this.model.changeMinute(w),this.propagateModelChange()}changeSecond(w){this.model.changeSecond(w),this.propagateModelChange()}updateHour(w){const X=this.model.hour>=12,ke=pi(w);this.model.updateHour(this.meridian&&(X&&ke<12||!X&&12===ke)?ke+12:ke),this.propagateModelChange()}updateMinute(w){this.model.updateMinute(pi(w)),this.propagateModelChange()}updateSecond(w){this.model.updateSecond(pi(w)),this.propagateModelChange()}toggleMeridian(){this.meridian&&this.changeHour(12)}formatInput(w){w.value=w.value.replace(Bf,"")}formatHour(w){return ko(w)?sl(this.meridian?w%12==0?12:w%12:w%24):sl(NaN)}formatMinSec(w){return sl(ko(w)?w:NaN)}handleBlur(){this.onTouched()}get isSmallSize(){return"small"===this.size}get isLargeSize(){return"large"===this.size}ngOnChanges(w){w.seconds&&!this.seconds&&this.model&&!ko(this.model.second)&&(this.model.second=0,this.propagateModelChange(!1))}propagateModelChange(w=!0){w&&this.onTouched(),this.model.isValid(this.seconds)?this.onChange(this._ngbTimeAdapter.toModel({hour:this.model.hour,minute:this.model.minute,second:this.model.second})):this.onChange(this._ngbTimeAdapter.toModel(null))}}return Y.\u0275fac=function(w){return new(w||Y)(r.Y36(Uf),r.Y36(Fd),r.Y36(r.sBO),r.Y36(i_))},Y.\u0275cmp=r.Xpm({type:Y,selectors:[["ngb-timepicker"]],inputs:{meridian:"meridian",spinners:"spinners",seconds:"seconds",hourStep:"hourStep",minuteStep:"minuteStep",secondStep:"secondStep",readonlyInputs:"readonlyInputs",size:"size"},features:[r._Bn([{provide:Mr.JU,useExisting:(0,r.Gpc)(()=>Y),multi:!0}]),r.TTD],decls:16,vars:25,consts:function(){let fe,w,X,ke,ct,Jn,Ir,vi,zi,Do,xs,Xo,Rs,ma;return fe="HH",w="Hours",X="MM",ke="Minutes",ct="Increment hours",Jn="Decrement hours",Ir="Increment minutes",vi="Decrement minutes",zi="SS",Do="Seconds",xs="Increment seconds",Xo="Decrement seconds",Rs="" + "\ufffd0\ufffd" + "",ma="" + "\ufffd0\ufffd" + "",[[3,"disabled"],[1,"ngb-tp"],[1,"ngb-tp-input-container","ngb-tp-hour"],["tabindex","-1","type","button","class","btn btn-link",3,"btn-sm","btn-lg","disabled","click",4,"ngIf"],["type","text","maxlength","2","inputmode","numeric","placeholder",fe,"aria-label",w,1,"ngb-tp-input","form-control",3,"value","readOnly","disabled","change","blur","input","keydown.ArrowUp","keydown.ArrowDown"],[1,"ngb-tp-spacer"],[1,"ngb-tp-input-container","ngb-tp-minute"],["type","text","maxlength","2","inputmode","numeric","placeholder",X,"aria-label",ke,1,"ngb-tp-input","form-control",3,"value","readOnly","disabled","change","blur","input","keydown.ArrowUp","keydown.ArrowDown"],["class","ngb-tp-spacer",4,"ngIf"],["class","ngb-tp-input-container ngb-tp-second",4,"ngIf"],["class","ngb-tp-meridian",4,"ngIf"],["tabindex","-1","type","button",1,"btn","btn-link",3,"disabled","click"],[1,"chevron","ngb-tp-chevron"],[1,"sr-only"],ct,[1,"chevron","ngb-tp-chevron","bottom"],Jn,Ir,vi,[1,"ngb-tp-input-container","ngb-tp-second"],["type","text","maxlength","2","inputmode","numeric","placeholder",zi,"aria-label",Do,1,"ngb-tp-input","form-control",3,"value","readOnly","disabled","change","blur","input","keydown.ArrowUp","keydown.ArrowDown"],xs,Xo,[1,"ngb-tp-meridian"],["type","button",1,"btn","btn-outline-primary",3,"disabled","click"],[4,"ngIf","ngIfElse"],["am",""],Rs,ma]},template:function(w,X){1&w&&(r.TgZ(0,"fieldset",0),r.TgZ(1,"div",1),r.TgZ(2,"div",2),r.YNc(3,fo,4,7,"button",3),r.TgZ(4,"input",4),r.NdJ("change",function(ct){return X.updateHour(ct.target.value)})("blur",function(){return X.handleBlur()})("input",function(ct){return X.formatInput(ct.target)})("keydown.ArrowUp",function(ct){return X.changeHour(X.hourStep),ct.preventDefault()})("keydown.ArrowDown",function(ct){return X.changeHour(-X.hourStep),ct.preventDefault()}),r.qZA(),r.YNc(5,Ya,4,7,"button",3),r.qZA(),r.TgZ(6,"div",5),r._uU(7,":"),r.qZA(),r.TgZ(8,"div",6),r.YNc(9,Ao,4,7,"button",3),r.TgZ(10,"input",7),r.NdJ("change",function(ct){return X.updateMinute(ct.target.value)})("blur",function(){return X.handleBlur()})("input",function(ct){return X.formatInput(ct.target)})("keydown.ArrowUp",function(ct){return X.changeMinute(X.minuteStep),ct.preventDefault()})("keydown.ArrowDown",function(ct){return X.changeMinute(-X.minuteStep),ct.preventDefault()}),r.qZA(),r.YNc(11,fs,4,7,"button",3),r.qZA(),r.YNc(12,Ca,2,0,"div",8),r.YNc(13,Ws,4,9,"div",9),r.YNc(14,Po,1,0,"div",8),r.YNc(15,ps,5,9,"div",10),r.qZA(),r.qZA()),2&w&&(r.ekj("disabled",X.disabled),r.Q6J("disabled",X.disabled),r.xp6(3),r.Q6J("ngIf",X.spinners),r.xp6(1),r.ekj("form-control-sm",X.isSmallSize)("form-control-lg",X.isLargeSize),r.Q6J("value",X.formatHour(null==X.model?null:X.model.hour))("readOnly",X.readonlyInputs)("disabled",X.disabled),r.xp6(1),r.Q6J("ngIf",X.spinners),r.xp6(4),r.Q6J("ngIf",X.spinners),r.xp6(1),r.ekj("form-control-sm",X.isSmallSize)("form-control-lg",X.isLargeSize),r.Q6J("value",X.formatMinSec(null==X.model?null:X.model.minute))("readOnly",X.readonlyInputs)("disabled",X.disabled),r.xp6(1),r.Q6J("ngIf",X.spinners),r.xp6(1),r.Q6J("ngIf",X.seconds),r.xp6(1),r.Q6J("ngIf",X.seconds),r.xp6(1),r.Q6J("ngIf",X.meridian),r.xp6(1),r.Q6J("ngIf",X.meridian))},directives:[u.O5],styles:['ngb-timepicker{font-size:1rem}.ngb-tp{display:flex;align-items:center}.ngb-tp-input-container{width:4em}.ngb-tp-chevron:before{border-style:solid;border-width:.29em .29em 0 0;content:"";display:inline-block;height:.69em;left:.05em;position:relative;top:.15em;transform:rotate(-45deg);vertical-align:middle;width:.69em}.ngb-tp-chevron.bottom:before{top:-.3em;transform:rotate(135deg)}.ngb-tp-input{text-align:center}.ngb-tp-hour,.ngb-tp-meridian,.ngb-tp-minute,.ngb-tp-second{display:flex;flex-direction:column;align-items:center;justify-content:space-around}.ngb-tp-spacer{width:1em;text-align:center}'],encapsulation:2}),Y})(),Yf=(()=>{class Y{}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275mod=r.oAB({type:Y}),Y.\u0275inj=r.cJS({imports:[[u.ez]]}),Y})(),rh=(()=>{class Y{constructor(w){this._ngbConfig=w,this.autoClose=!0,this.placement="auto",this.triggers="hover focus",this.disableTooltip=!1,this.openDelay=0,this.closeDelay=0}get animation(){return void 0===this._animation?this._ngbConfig.animation:this._animation}set animation(w){this._animation=w}}return Y.\u0275fac=function(w){return new(w||Y)(r.LFG(Fs))},Y.\u0275prov=r.Yz7({factory:function(){return new Y(r.LFG(Fs))},token:Y,providedIn:"root"}),Y})(),Ed=0,xf=(()=>{class Y{}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275cmp=r.Xpm({type:Y,selectors:[["ngb-tooltip-window"]],hostAttrs:["role","tooltip"],hostVars:5,hostBindings:function(w,X){2&w&&(r.Ikx("id",X.id),r.Tol("tooltip"+(X.tooltipClass?" "+X.tooltipClass:"")),r.ekj("fade",X.animation))},inputs:{animation:"animation",id:"id",tooltipClass:"tooltipClass"},ngContentSelectors:Hi,decls:3,vars:0,consts:[[1,"arrow"],[1,"tooltip-inner"]],template:function(w,X){1&w&&(r.F$t(),r._UZ(0,"div",0),r.TgZ(1,"div",1),r.Hsn(2),r.qZA())},styles:["ngb-tooltip-window{pointer-events:none}ngb-tooltip-window .tooltip-inner{pointer-events:auto}ngb-tooltip-window.bs-tooltip-bottom .arrow,ngb-tooltip-window.bs-tooltip-top .arrow{left:calc(50% - .4rem)}ngb-tooltip-window.bs-tooltip-bottom-left .arrow,ngb-tooltip-window.bs-tooltip-top-left .arrow{left:1em}ngb-tooltip-window.bs-tooltip-bottom-right .arrow,ngb-tooltip-window.bs-tooltip-top-right .arrow{left:auto;right:.8rem}ngb-tooltip-window.bs-tooltip-left .arrow,ngb-tooltip-window.bs-tooltip-right .arrow{top:calc(50% - .4rem)}ngb-tooltip-window.bs-tooltip-left-top .arrow,ngb-tooltip-window.bs-tooltip-right-top .arrow{top:.4rem}ngb-tooltip-window.bs-tooltip-left-bottom .arrow,ngb-tooltip-window.bs-tooltip-right-bottom .arrow{top:auto;bottom:.4rem}"],encapsulation:2,changeDetection:0}),Y})(),Ph=(()=>{class Y{constructor(w,X,ke,ct,Jn,Ir,vi,zi,Do,xs){this._elementRef=w,this._renderer=X,this._ngZone=vi,this._document=zi,this._changeDetector=Do,this.shown=new r.vpe,this.hidden=new r.vpe,this._ngbTooltipWindowId="ngb-tooltip-"+Ed++,this._windowRef=null,this.animation=Ir.animation,this.autoClose=Ir.autoClose,this.placement=Ir.placement,this.triggers=Ir.triggers,this.container=Ir.container,this.disableTooltip=Ir.disableTooltip,this.tooltipClass=Ir.tooltipClass,this.openDelay=Ir.openDelay,this.closeDelay=Ir.closeDelay,this._popupService=new Lu(xf,ke,Jn,X,this._ngZone,ct,xs),this._zoneSubscription=vi.onStable.subscribe(()=>{this._windowRef&&Ye(this._elementRef.nativeElement,this._windowRef.location.nativeElement,this.placement,"body"===this.container,"bs-tooltip")})}set ngbTooltip(w){this._ngbTooltip=w,!w&&this._windowRef&&this.close()}get ngbTooltip(){return this._ngbTooltip}open(w){if(!this._windowRef&&this._ngbTooltip&&!this.disableTooltip){const{windowRef:X,transition$:ke}=this._popupService.open(this._ngbTooltip,w,this.animation);this._windowRef=X,this._windowRef.instance.animation=this.animation,this._windowRef.instance.tooltipClass=this.tooltipClass,this._windowRef.instance.id=this._ngbTooltipWindowId,this._renderer.setAttribute(this._elementRef.nativeElement,"aria-describedby",this._ngbTooltipWindowId),"body"===this.container&&this._document.querySelector(this.container).appendChild(this._windowRef.location.nativeElement),this._windowRef.changeDetectorRef.detectChanges(),this._windowRef.changeDetectorRef.markForCheck(),la(this._ngZone,this._document,this.autoClose,()=>this.close(),this.hidden,[this._windowRef.location.nativeElement]),ke.subscribe(()=>this.shown.emit())}}close(){null!=this._windowRef&&(this._renderer.removeAttribute(this._elementRef.nativeElement,"aria-describedby"),this._popupService.close(this.animation).subscribe(()=>{this._windowRef=null,this.hidden.emit(),this._changeDetector.markForCheck()}))}toggle(){this._windowRef?this.close():this.open()}isOpen(){return null!=this._windowRef}ngOnInit(){this._unregisterListenersFn=cr(this._renderer,this._elementRef.nativeElement,this.triggers,this.isOpen.bind(this),this.open.bind(this),this.close.bind(this),+this.openDelay,+this.closeDelay)}ngOnChanges({tooltipClass:w}){w&&this.isOpen()&&(this._windowRef.instance.tooltipClass=w.currentValue)}ngOnDestroy(){this.close(),this._unregisterListenersFn&&this._unregisterListenersFn(),this._zoneSubscription.unsubscribe()}}return Y.\u0275fac=function(w){return new(w||Y)(r.Y36(r.SBq),r.Y36(r.Qsj),r.Y36(r.zs3),r.Y36(r._Vd),r.Y36(r.s_b),r.Y36(rh),r.Y36(r.R0b),r.Y36(u.K0),r.Y36(r.sBO),r.Y36(r.z2F))},Y.\u0275dir=r.lG2({type:Y,selectors:[["","ngbTooltip",""]],inputs:{animation:"animation",autoClose:"autoClose",placement:"placement",triggers:"triggers",container:"container",disableTooltip:"disableTooltip",tooltipClass:"tooltipClass",openDelay:"openDelay",closeDelay:"closeDelay",ngbTooltip:"ngbTooltip"},outputs:{shown:"shown",hidden:"hidden"},exportAs:["ngbTooltip"],features:[r.TTD]}),Y})(),Bp=(()=>{class Y{}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275mod=r.oAB({type:Y}),Y.\u0275inj=r.cJS({}),Y})(),ih=(()=>{class Y{constructor(){this.highlightClass="ngb-highlight",this.accentSensitive=!0}ngOnChanges(w){!this.accentSensitive&&!String.prototype.normalize&&(console.warn("The `accentSensitive` input in `ngb-highlight` cannot be set to `false` in a browser that does not implement the `String.normalize` function. You will have to include a polyfill in your application to use this feature in the current browser."),this.accentSensitive=!0);const X=xi(this.result),ke=Array.isArray(this.term)?this.term:[this.term],ct=zi=>this.accentSensitive?zi:Hl(zi),Jn=ke.map(zi=>function(Y){return Y.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&")}(ct(xi(zi)))).filter(zi=>zi),Ir=this.accentSensitive?X:Hl(X),vi=Jn.length?Ir.split(new RegExp(`(${Jn.join("|")})`,"gmi")):[X];if(this.accentSensitive)this.parts=vi;else{let zi=0;this.parts=vi.map(Do=>X.substring(zi,zi+=Do.length))}}}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275cmp=r.Xpm({type:Y,selectors:[["ngb-highlight"]],inputs:{highlightClass:"highlightClass",accentSensitive:"accentSensitive",result:"result",term:"term"},features:[r.TTD],decls:1,vars:1,consts:[["ngFor","",3,"ngForOf"],[3,"class",4,"ngIf","ngIfElse"],["even",""]],template:function(w,X){1&w&&r.YNc(0,ne,3,2,"ng-template",0),2&w&&r.Q6J("ngForOf",X.parts)},directives:[u.sg,u.O5],styles:[".ngb-highlight{font-weight:700}"],encapsulation:2,changeDetection:0}),Y})(),Ih=(()=>{class Y{constructor(){this.activeIdx=0,this.focusFirst=!0,this.formatter=xi,this.selectEvent=new r.vpe,this.activeChangeEvent=new r.vpe}hasActive(){return this.activeIdx>-1&&this.activeIdx=0?this.id+"-"+this.activeIdx:void 0)}}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275cmp=r.Xpm({type:Y,selectors:[["ngb-typeahead-window"]],hostAttrs:["role","listbox"],hostVars:3,hostBindings:function(w,X){1&w&&r.NdJ("mousedown",function(ct){return ct.preventDefault()}),2&w&&(r.Ikx("id",X.id),r.Tol("dropdown-menu show"+(X.popupClass?" "+X.popupClass:"")))},inputs:{focusFirst:"focusFirst",formatter:"formatter",id:"id",results:"results",term:"term",resultTemplate:"resultTemplate",popupClass:"popupClass"},outputs:{selectEvent:"select",activeChangeEvent:"activeChange"},exportAs:["ngbTypeaheadWindow"],decls:3,vars:1,consts:[["rt",""],["ngFor","",3,"ngForOf"],[3,"result","term"],["type","button","role","option",1,"dropdown-item",3,"id","mouseenter","click"],[3,"ngTemplateOutlet","ngTemplateOutletContext"]],template:function(w,X){1&w&&(r.YNc(0,$e,1,2,"ng-template",null,0,r.W1O),r.YNc(2,ti,2,9,"ng-template",1)),2&w&&(r.xp6(2),r.Q6J("ngForOf",X.results))},directives:[u.sg,ih,u.tP],encapsulation:2}),Y})();const vf=new r.OlP("live announcer delay",{providedIn:"root",factory:function(){return 100}});function $d(Y,fe=!1){let w=Y.body.querySelector("#ngb-live");return null==w&&fe&&(w=Y.createElement("div"),w.setAttribute("id","ngb-live"),w.setAttribute("aria-live","polite"),w.setAttribute("aria-atomic","true"),w.classList.add("sr-only"),Y.body.appendChild(w)),w}let hp=(()=>{class Y{constructor(w,X){this._document=w,this._delay=X}ngOnDestroy(){const w=$d(this._document);w&&w.parentElement.removeChild(w)}say(w){const X=$d(this._document,!0),ke=this._delay;if(null!=X){X.textContent="";const ct=()=>X.textContent=w;null===ke?ct():setTimeout(ct,ke)}}}return Y.\u0275fac=function(w){return new(w||Y)(r.LFG(u.K0),r.LFG(vf))},Y.\u0275prov=r.Yz7({factory:function(){return new Y(r.LFG(u.K0),r.LFG(vf))},token:Y,providedIn:"root"}),Y})(),Zf=(()=>{class Y{constructor(){this.editable=!0,this.focusFirst=!0,this.showHint=!1,this.placement=["bottom-left","bottom-right","top-left","top-right"]}}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275prov=r.Yz7({factory:function(){return new Y},token:Y,providedIn:"root"}),Y})(),qd=0,ac=(()=>{class Y{constructor(w,X,ke,ct,Jn,Ir,vi,zi,Do,xs,Xo,Rs){this._elementRef=w,this._renderer=ke,this._live=zi,this._document=Do,this._ngZone=xs,this._changeDetector=Xo,this._subscription=null,this._closed$=new he.xQ,this._inputValueBackup=null,this._windowRef=null,this.autocomplete="off",this.placement="bottom-left",this.selectItem=new r.vpe,this.activeDescendant=null,this.popupId="ngb-typeahead-"+qd++,this._onTouched=()=>{},this._onChange=ma=>{},this.container=Ir.container,this.editable=Ir.editable,this.focusFirst=Ir.focusFirst,this.showHint=Ir.showHint,this.placement=Ir.placement,this._valueChanges=(0,ge.R)(w.nativeElement,"input").pipe((0,bt.U)(ma=>ma.target.value)),this._resubscribeTypeahead=new ce.X(null),this._popupService=new Lu(Ih,ct,X,ke,this._ngZone,Jn,Rs),this._zoneSubscription=vi.onStable.subscribe(()=>{this.isPopupOpen()&&Ye(this._elementRef.nativeElement,this._windowRef.location.nativeElement,this.placement,"body"===this.container)})}ngOnInit(){this._subscribeToUserInput()}ngOnChanges({ngbTypeahead:w}){w&&!w.firstChange&&(this._unsubscribeFromUserInput(),this._subscribeToUserInput())}ngOnDestroy(){this._closePopup(),this._unsubscribeFromUserInput(),this._zoneSubscription.unsubscribe()}registerOnChange(w){this._onChange=w}registerOnTouched(w){this._onTouched=w}writeValue(w){this._writeInputValue(this._formatItemForInput(w)),this.showHint&&(this._inputValueBackup=w)}setDisabledState(w){this._renderer.setProperty(this._elementRef.nativeElement,"disabled",w)}dismissPopup(){this.isPopupOpen()&&(this._resubscribeTypeahead.next(null),this._closePopup(),this.showHint&&null!==this._inputValueBackup&&this._writeInputValue(this._inputValueBackup),this._changeDetector.markForCheck())}isPopupOpen(){return null!=this._windowRef}handleBlur(){this._resubscribeTypeahead.next(null),this._onTouched()}handleKeyDown(w){if(this.isPopupOpen())switch(w.which){case $i.ArrowDown:w.preventDefault(),this._windowRef.instance.next(),this._showHint();break;case $i.ArrowUp:w.preventDefault(),this._windowRef.instance.prev(),this._showHint();break;case $i.Enter:case $i.Tab:const X=this._windowRef.instance.getActive();ba(X)&&(w.preventDefault(),w.stopPropagation(),this._selectResult(X)),this._closePopup()}}_openPopup(){if(!this.isPopupOpen()){this._inputValueBackup=this._elementRef.nativeElement.value;const{windowRef:w}=this._popupService.open();this._windowRef=w,this._windowRef.instance.id=this.popupId,this._windowRef.instance.selectEvent.subscribe(X=>this._selectResultClosePopup(X)),this._windowRef.instance.activeChangeEvent.subscribe(X=>this.activeDescendant=X),this._windowRef.instance.popupClass=this.popupClass,"body"===this.container&&this._document.querySelector(this.container).appendChild(this._windowRef.location.nativeElement),this._changeDetector.markForCheck(),la(this._ngZone,this._document,"outside",()=>this.dismissPopup(),this._closed$,[this._elementRef.nativeElement,this._windowRef.location.nativeElement])}}_closePopup(){this._popupService.close().subscribe(()=>{this._closed$.next(),this._windowRef=null,this.activeDescendant=null})}_selectResult(w){let X=!1;this.selectItem.emit({item:w,preventDefault:()=>{X=!0}}),this._resubscribeTypeahead.next(null),X||(this.writeValue(w),this._onChange(w))}_selectResultClosePopup(w){this._selectResult(w),this._closePopup()}_showHint(){var w;if(this.showHint&&(null===(w=this._windowRef)||void 0===w?void 0:w.instance.hasActive())&&null!=this._inputValueBackup){const X=this._inputValueBackup.toLowerCase(),ke=this._formatItemForInput(this._windowRef.instance.getActive());X===ke.substr(0,this._inputValueBackup.length).toLowerCase()?(this._writeInputValue(this._inputValueBackup+ke.substr(this._inputValueBackup.length)),this._elementRef.nativeElement.setSelectionRange.apply(this._elementRef.nativeElement,[this._inputValueBackup.length,ke.length])):this._writeInputValue(ke)}}_formatItemForInput(w){return null!=w&&this.inputFormatter?this.inputFormatter(w):xi(w)}_writeInputValue(w){this._renderer.setProperty(this._elementRef.nativeElement,"value",xi(w))}_subscribeToUserInput(){const w=this._valueChanges.pipe((0,Zn.b)(X=>{this._inputValueBackup=this.showHint?X:null,this._onChange(this.editable?X:void 0)}),this.ngbTypeahead?this.ngbTypeahead:()=>(0,ie.of)([]));this._subscription=this._resubscribeTypeahead.pipe((0,Xt.w)(()=>w)).subscribe(X=>{X&&0!==X.length?(this._openPopup(),this._windowRef.instance.focusFirst=this.focusFirst,this._windowRef.instance.results=X,this._windowRef.instance.term=this._elementRef.nativeElement.value,this.resultFormatter&&(this._windowRef.instance.formatter=this.resultFormatter),this.resultTemplate&&(this._windowRef.instance.resultTemplate=this.resultTemplate),this._windowRef.instance.resetActive(),this._windowRef.changeDetectorRef.detectChanges(),this._showHint()):this._closePopup();const ke=X?X.length:0;this._live.say(0===ke?"No results available":`${ke} result${1===ke?"":"s"} available`)})}_unsubscribeFromUserInput(){this._subscription&&this._subscription.unsubscribe(),this._subscription=null}}return Y.\u0275fac=function(w){return new(w||Y)(r.Y36(r.SBq),r.Y36(r.s_b),r.Y36(r.Qsj),r.Y36(r.zs3),r.Y36(r._Vd),r.Y36(Zf),r.Y36(r.R0b),r.Y36(hp),r.Y36(u.K0),r.Y36(r.R0b),r.Y36(r.sBO),r.Y36(r.z2F))},Y.\u0275dir=r.lG2({type:Y,selectors:[["input","ngbTypeahead",""]],hostAttrs:["autocapitalize","off","autocorrect","off","role","combobox","aria-multiline","false"],hostVars:7,hostBindings:function(w,X){1&w&&r.NdJ("blur",function(){return X.handleBlur()})("keydown",function(ct){return X.handleKeyDown(ct)}),2&w&&(r.Ikx("autocomplete",X.autocomplete),r.uIk("aria-autocomplete",X.showHint?"both":"list")("aria-activedescendant",X.activeDescendant)("aria-owns",X.isPopupOpen()?X.popupId:null)("aria-expanded",X.isPopupOpen()),r.ekj("open",X.isPopupOpen()))},inputs:{autocomplete:"autocomplete",placement:"placement",container:"container",editable:"editable",focusFirst:"focusFirst",showHint:"showHint",inputFormatter:"inputFormatter",ngbTypeahead:"ngbTypeahead",resultFormatter:"resultFormatter",resultTemplate:"resultTemplate",popupClass:"popupClass"},outputs:{selectItem:"selectItem"},exportAs:["ngbTypeahead"],features:[r._Bn([{provide:Mr.JU,useExisting:(0,r.Gpc)(()=>Y),multi:!0}]),r.TTD]}),Y})(),oh=(()=>{class Y{}return Y.\u0275fac=function(w){return new(w||Y)},Y.\u0275mod=r.oAB({type:Y}),Y.\u0275inj=r.cJS({imports:[[u.ez]]}),Y})()},84051:(v,T,i)=>{"use strict";i.d(T,{vq:()=>lo,ii:()=>rr,Ke:()=>fo,nE:()=>$s,dX:()=>jo,$7:()=>os,AR:()=>Gi,xD:()=>So,Sr:()=>us,Hg:()=>gr});var r=i(74788),u=i(12057),p=i(79765),d=i(22759),e=i(26215),_=i(46782),y=i(64762);const S=["*"];function A(tt,sn){1&tt&&r._UZ(0,"datatable-progress")}function N(tt,sn){if(1&tt&&r._UZ(0,"datatable-summary-row",9),2&tt){const ne=r.oxw(2);r.Q6J("rowHeight",ne.summaryHeight)("offsetX",ne.offsetX)("innerWidth",ne.innerWidth)("rows",ne.rows)("columns",ne.columns)}}function L(tt,sn){if(1&tt){const ne=r.EpF();r.TgZ(0,"datatable-body-row",13),r.NdJ("treeAction",function(){r.CHM(ne);const Lt=r.oxw().$implicit;return r.oxw(2).onTreeAction(Lt)})("activate",function(Lt){r.CHM(ne);const an=r.oxw().index,ti=r.oxw(2);return r.MAs(2).onActivate(Lt,ti.indexes.first+an)}),r.qZA()}if(2&tt){const ne=r.oxw().$implicit,$e=r.oxw(2),Lt=r.MAs(2);r.Q6J("isSelected",Lt.getRowSelected(ne))("innerWidth",$e.innerWidth)("offsetX",$e.offsetX)("columns",$e.columns)("rowHeight",$e.getRowHeight(ne))("row",ne)("rowIndex",$e.getRowIndex(ne))("expanded",$e.getRowExpanded(ne))("rowClass",$e.rowClass)("displayCheck",$e.displayCheck)("treeStatus",ne&&ne.treeStatus)}}function Z(tt,sn){if(1&tt){const ne=r.EpF();r.TgZ(0,"datatable-body-row",15),r.NdJ("activate",function(Lt){const ti=r.CHM(ne).index;return r.oxw(4),r.MAs(2).onActivate(Lt,ti)}),r.qZA()}if(2&tt){const ne=sn.$implicit,$e=r.oxw(2).$implicit,Lt=r.oxw(2),an=r.MAs(2);r.Q6J("isSelected",an.getRowSelected(ne))("innerWidth",Lt.innerWidth)("offsetX",Lt.offsetX)("columns",Lt.columns)("rowHeight",Lt.getRowHeight(ne))("row",ne)("group",$e.value)("rowIndex",Lt.getRowIndex(ne))("expanded",Lt.getRowExpanded(ne))("rowClass",Lt.rowClass)}}function J(tt,sn){if(1&tt&&r.YNc(0,Z,1,10,"datatable-body-row",14),2&tt){const ne=r.oxw().$implicit,$e=r.oxw(2);r.Q6J("ngForOf",ne.value)("ngForTrackBy",$e.rowTrackingFn)}}function K(tt,sn){if(1&tt){const ne=r.EpF();r.TgZ(0,"datatable-row-wrapper",10),r.NdJ("rowContextmenu",function(Lt){return r.CHM(ne),r.oxw(2).rowContextmenu.emit(Lt)}),r.YNc(1,L,1,11,"datatable-body-row",11),r.YNc(2,J,1,2,"ng-template",null,12,r.W1O),r.qZA()}if(2&tt){const ne=sn.$implicit,$e=sn.index,Lt=r.MAs(3),an=r.oxw(2);r.Q6J("groupedRows",an.groupedRows)("innerWidth",an.innerWidth)("ngStyle",an.getRowsStyles(ne))("rowDetail",an.rowDetail)("groupHeader",an.groupHeader)("offsetX",an.offsetX)("detailRowHeight",an.getDetailRowHeight(ne&&ne[$e],$e))("row",ne)("expanded",an.getRowExpanded(ne))("rowIndex",an.getRowIndex(ne&&ne[$e])),r.xp6(1),r.Q6J("ngIf",!an.groupedRows)("ngIfElse",Lt)}}function ee(tt,sn){if(1&tt&&r._UZ(0,"datatable-summary-row",16),2&tt){const ne=r.oxw(2);r.Q6J("ngStyle",ne.getBottomSummaryRowStyles())("rowHeight",ne.summaryHeight)("offsetX",ne.offsetX)("innerWidth",ne.innerWidth)("rows",ne.rows)("columns",ne.columns)}}function ue(tt,sn){if(1&tt){const ne=r.EpF();r.TgZ(0,"datatable-scroller",5),r.NdJ("scroll",function(Lt){return r.CHM(ne),r.oxw().onBodyScroll(Lt)}),r.YNc(1,N,1,5,"datatable-summary-row",6),r.YNc(2,K,4,12,"datatable-row-wrapper",7),r.YNc(3,ee,1,6,"datatable-summary-row",8),r.qZA()}if(2&tt){const ne=r.oxw();r.Q6J("scrollbarV",ne.scrollbarV)("scrollbarH",ne.scrollbarH)("scrollHeight",ne.scrollHeight)("scrollWidth",null==ne.columnGroupWidths?null:ne.columnGroupWidths.total),r.xp6(1),r.Q6J("ngIf",ne.summaryRow&&"top"===ne.summaryPosition),r.xp6(1),r.Q6J("ngForOf",ne.temp)("ngForTrackBy",ne.rowTrackingFn),r.xp6(1),r.Q6J("ngIf",ne.summaryRow&&"bottom"===ne.summaryPosition)}}function ae(tt,sn){if(1&tt&&r._UZ(0,"div",17),2&tt){const ne=r.oxw();r.Q6J("innerHTML",ne.emptyMessage,r.oJD)}}function H(tt,sn){if(1&tt){const ne=r.EpF();r.TgZ(0,"datatable-header-cell",4),r.NdJ("resize",function(Lt){const ti=r.CHM(ne).$implicit;return r.oxw(2).onColumnResized(Lt,ti)})("longPressStart",function(Lt){return r.CHM(ne),r.oxw(2).onLongPressStart(Lt)})("longPressEnd",function(Lt){return r.CHM(ne),r.oxw(2).onLongPressEnd(Lt)})("sort",function(Lt){return r.CHM(ne),r.oxw(2).onSort(Lt)})("select",function(Lt){return r.CHM(ne),r.oxw(2).select.emit(Lt)})("columnContextmenu",function(Lt){return r.CHM(ne),r.oxw(2).columnContextmenu.emit(Lt)}),r.qZA()}if(2&tt){const ne=sn.$implicit,$e=r.oxw(2);r.Q6J("resizeEnabled",ne.resizeable)("pressModel",ne)("pressEnabled",$e.reorderable&&ne.draggable)("dragX",$e.reorderable&&ne.draggable&&ne.dragging)("dragY",!1)("dragModel",ne)("dragEventTarget",$e.dragEventTarget)("headerHeight",$e.headerHeight)("isTarget",ne.isTarget)("targetMarkerTemplate",$e.targetMarkerTemplate)("targetMarkerContext",ne.targetMarkerContext)("column",ne)("sortType",$e.sortType)("sorts",$e.sorts)("selectionType",$e.selectionType)("sortAscendingIcon",$e.sortAscendingIcon)("sortDescendingIcon",$e.sortDescendingIcon)("sortUnsetIcon",$e.sortUnsetIcon)("allRowsSelected",$e.allRowsSelected)}}function se(tt,sn){if(1&tt&&(r.TgZ(0,"div",2),r.YNc(1,H,1,19,"datatable-header-cell",3),r.qZA()),2&tt){const ne=sn.$implicit,$e=r.oxw();r.Tol("datatable-row-"+ne.type),r.Q6J("ngStyle",$e._styleByGroup[ne.type]),r.xp6(1),r.Q6J("ngForOf",ne.columns)("ngForTrackBy",$e.columnTrackingFn)}}function Ee(tt,sn){if(1&tt){const ne=r.EpF();r.TgZ(0,"datatable-header",4),r.NdJ("sort",function(Lt){return r.CHM(ne),r.oxw().onColumnSort(Lt)})("resize",function(Lt){return r.CHM(ne),r.oxw().onColumnResize(Lt)})("reorder",function(Lt){return r.CHM(ne),r.oxw().onColumnReorder(Lt)})("select",function(Lt){return r.CHM(ne),r.oxw().onHeaderSelect(Lt)})("columnContextmenu",function(Lt){return r.CHM(ne),r.oxw().onColumnContextmenu(Lt)}),r.ALo(1,"async"),r.qZA()}if(2&tt){const ne=r.oxw();r.Q6J("sorts",ne.sorts)("sortType",ne.sortType)("scrollbarH",ne.scrollbarH)("innerWidth",ne._innerWidth)("offsetX",r.lcZ(1,15,ne._offsetX))("dealsWithGroup",void 0!==ne.groupedRows)("columns",ne._internalColumns)("headerHeight",ne.headerHeight)("reorderable",ne.reorderable)("targetMarkerTemplate",ne.targetMarkerTemplate)("sortAscendingIcon",ne.cssClasses.sortAscending)("sortDescendingIcon",ne.cssClasses.sortDescending)("sortUnsetIcon",ne.cssClasses.sortUnset)("allRowsSelected",ne.allRowsSelected)("selectionType",ne.selectionType)}}function ie(tt,sn){if(1&tt){const ne=r.EpF();r.TgZ(0,"datatable-footer",5),r.NdJ("page",function(Lt){return r.CHM(ne),r.oxw().onFooterPage(Lt)}),r.qZA()}if(2&tt){const ne=r.oxw();r.Q6J("rowCount",ne.rowCount)("pageSize",ne.pageSize)("offset",ne.offset)("footerHeight",ne.footerHeight)("footerTemplate",ne.footer)("totalMessage",ne.messages.totalMessage)("pagerLeftArrowIcon",ne.cssClasses.pagerLeftArrow)("pagerRightArrowIcon",ne.cssClasses.pagerRightArrow)("pagerPreviousIcon",ne.cssClasses.pagerPrevious)("selectedCount",ne.selected.length)("selectedMessage",!!ne.selectionType&&ne.messages.selectedMessage)("pagerNextIcon",ne.cssClasses.pagerNext)}}function he(tt,sn){}function ge(tt,sn){if(1&tt&&r.YNc(0,he,0,0,"ng-template",5),2&tt){const ne=r.oxw();r.Q6J("ngTemplateOutlet",ne.targetMarkerTemplate)("ngTemplateOutletContext",ne.targetMarkerContext)}}function De(tt,sn){if(1&tt){const ne=r.EpF();r.TgZ(0,"label",6),r.TgZ(1,"input",7),r.NdJ("change",function(){r.CHM(ne);const Lt=r.oxw();return Lt.select.emit(!Lt.allRowsSelected)}),r.qZA(),r.qZA()}if(2&tt){const ne=r.oxw();r.xp6(1),r.Q6J("checked",ne.allRowsSelected)}}function ce(tt,sn){if(1&tt){const ne=r.EpF();r.TgZ(0,"span",8),r.TgZ(1,"span",9),r.NdJ("click",function(){return r.CHM(ne),r.oxw().onSort()}),r.qZA(),r.qZA()}if(2&tt){const ne=r.oxw();r.xp6(1),r.Q6J("innerHTML",ne.name,r.oJD)}}function lt(tt,sn){}function Ve(tt,sn){if(1&tt&&r.YNc(0,lt,0,0,"ng-template",5),2&tt){const ne=r.oxw();r.Q6J("ngTemplateOutlet",ne.column.headerTemplate)("ngTemplateOutletContext",ne.cellContext)}}function ze(tt,sn){}const Be=function(tt,sn,ne,$e,Lt){return{rowCount:tt,pageSize:sn,selectedCount:ne,curPage:$e,offset:Lt}};function Pe(tt,sn){if(1&tt&&r.YNc(0,ze,0,0,"ng-template",4),2&tt){const ne=r.oxw();r.Q6J("ngTemplateOutlet",ne.footerTemplate.template)("ngTemplateOutletContext",r.qbA(2,Be,ne.rowCount,ne.pageSize,ne.selectedCount,ne.curPage,ne.offset))}}function je(tt,sn){if(1&tt&&(r.TgZ(0,"span"),r._uU(1),r.qZA()),2&tt){const ne=r.oxw(2);r.xp6(1),r.AsE(" ",null==ne.selectedCount?null:ne.selectedCount.toLocaleString()," ",ne.selectedMessage," / ")}}function He(tt,sn){if(1&tt&&(r.TgZ(0,"div",5),r.YNc(1,je,2,2,"span",1),r._uU(2),r.qZA()),2&tt){const ne=r.oxw();r.xp6(1),r.Q6J("ngIf",ne.selectedMessage),r.xp6(1),r.AsE(" ",null==ne.rowCount?null:ne.rowCount.toLocaleString()," ",ne.totalMessage," ")}}function Vt(tt,sn){if(1&tt){const ne=r.EpF();r.TgZ(0,"datatable-pager",6),r.NdJ("change",function(Lt){return r.CHM(ne),r.oxw().page.emit(Lt)}),r.qZA()}if(2&tt){const ne=r.oxw();r.Q6J("pagerLeftArrowIcon",ne.pagerLeftArrowIcon)("pagerRightArrowIcon",ne.pagerRightArrowIcon)("pagerPreviousIcon",ne.pagerPreviousIcon)("pagerNextIcon",ne.pagerNextIcon)("page",ne.curPage)("size",ne.pageSize)("count",ne.rowCount)("hidden",!ne.isVisible)}}const it=function(tt){return{"selected-count":tt}};function tn(tt,sn){if(1&tt){const ne=r.EpF();r.TgZ(0,"li",6),r.TgZ(1,"a",7),r.NdJ("click",function(){const an=r.CHM(ne).$implicit;return r.oxw().selectPage(an.number)}),r._uU(2),r.qZA(),r.qZA()}if(2&tt){const ne=sn.$implicit,$e=r.oxw();r.ekj("active",ne.number===$e.page),r.uIk("aria-label","page "+ne.number),r.xp6(2),r.hij(" ",ne.text," ")}}function It(tt,sn){if(1&tt){const ne=r.EpF();r.TgZ(0,"datatable-body-cell",3),r.NdJ("activate",function(Lt){const ti=r.CHM(ne).index;return r.oxw(2).onActivate(Lt,ti)})("treeAction",function(){return r.CHM(ne),r.oxw(2).onTreeAction()}),r.qZA()}if(2&tt){const ne=sn.$implicit,$e=r.oxw(2);r.Q6J("row",$e.row)("group",$e.group)("expanded",$e.expanded)("isSelected",$e.isSelected)("rowIndex",$e.rowIndex)("column",ne)("rowHeight",$e.rowHeight)("displayCheck",$e.displayCheck)("treeStatus",$e.treeStatus)}}function Zt(tt,sn){if(1&tt&&(r.TgZ(0,"div",1),r.YNc(1,It,1,9,"datatable-body-cell",2),r.qZA()),2&tt){const ne=sn.$implicit,$e=r.oxw();r.Gre("datatable-row-",ne.type," datatable-row-group"),r.Q6J("ngStyle",$e._groupStyles[ne.type]),r.xp6(1),r.Q6J("ngForOf",ne.columns)("ngForTrackBy",$e.columnTrackingFn)}}function Ut(tt,sn){}function Bt(tt,sn){if(1&tt&&r.YNc(0,Ut,0,0,"ng-template",4),2&tt){const ne=r.oxw(2);r.Q6J("ngTemplateOutlet",ne.groupHeader.template)("ngTemplateOutletContext",ne.groupContext)}}function bt(tt,sn){if(1&tt&&(r.TgZ(0,"div",3),r.YNc(1,Bt,1,2,void 0,1),r.qZA()),2&tt){const ne=r.oxw();r.Q6J("ngStyle",ne.getGroupHeaderStyle()),r.xp6(1),r.Q6J("ngIf",ne.groupHeader&&ne.groupHeader.template)}}function Gt(tt,sn){1&tt&&r.Hsn(0,0,["*ngIf","(groupHeader && groupHeader.template && expanded) || !groupHeader || !groupHeader.template"])}function xt(tt,sn){}function Xt(tt,sn){if(1&tt&&r.YNc(0,xt,0,0,"ng-template",4),2&tt){const ne=r.oxw(2);r.Q6J("ngTemplateOutlet",ne.rowDetail.template)("ngTemplateOutletContext",ne.rowContext)}}function Zn(tt,sn){if(1&tt&&(r.TgZ(0,"div",5),r.YNc(1,Xt,1,2,void 0,1),r.qZA()),2&tt){const ne=r.oxw();r.Udp("height",ne.detailRowHeight,"px"),r.xp6(1),r.Q6J("ngIf",ne.rowDetail&&ne.rowDetail.template)}}const Ur=["cellTemplate"];function di(tt,sn){if(1&tt){const ne=r.EpF();r.TgZ(0,"label",4),r.TgZ(1,"input",5),r.NdJ("click",function(Lt){return r.CHM(ne),r.oxw().onCheckboxChange(Lt)}),r.qZA(),r.qZA()}if(2&tt){const ne=r.oxw();r.xp6(1),r.Q6J("checked",ne.isSelected)}}function Lr(tt,sn){1&tt&&r._UZ(0,"i",11)}function Mr(tt,sn){1&tt&&r._UZ(0,"i",12)}function Kr(tt,sn){1&tt&&r._UZ(0,"i",13)}function ei(tt,sn){if(1&tt){const ne=r.EpF();r.TgZ(0,"button",7),r.NdJ("click",function(){return r.CHM(ne),r.oxw(2).onTreeAction()}),r.TgZ(1,"span"),r.YNc(2,Lr,1,0,"i",8),r.YNc(3,Mr,1,0,"i",9),r.YNc(4,Kr,1,0,"i",10),r.qZA(),r.qZA()}if(2&tt){const ne=r.oxw(2);r.Q6J("disabled","disabled"===ne.treeStatus),r.xp6(2),r.Q6J("ngIf","loading"===ne.treeStatus),r.xp6(1),r.Q6J("ngIf","collapsed"===ne.treeStatus),r.xp6(1),r.Q6J("ngIf","expanded"===ne.treeStatus||"disabled"===ne.treeStatus)}}function Nn(tt,sn){}const $n=function(tt){return{cellContext:tt}};function Br(tt,sn){if(1&tt&&r.YNc(0,Nn,0,0,"ng-template",14),2&tt){const ne=r.oxw(2);r.Q6J("ngTemplateOutlet",ne.column.treeToggleTemplate)("ngTemplateOutletContext",r.VKq(2,$n,ne.cellContext))}}function Yr(tt,sn){if(1&tt&&(r.ynx(0),r.YNc(1,ei,5,4,"button",6),r.YNc(2,Br,1,4,void 0,2),r.BQk()),2&tt){const ne=r.oxw();r.xp6(1),r.Q6J("ngIf",!ne.column.treeToggleTemplate),r.xp6(1),r.Q6J("ngIf",ne.column.treeToggleTemplate)}}function fi(tt,sn){if(1&tt&&r._UZ(0,"span",15),2&tt){const ne=r.oxw();r.Q6J("title",ne.sanitizedValue)("innerHTML",ne.value,r.oJD)}}function ki(tt,sn){}function Hi(tt,sn){if(1&tt&&r.YNc(0,ki,0,0,"ng-template",14,16,r.W1O),2&tt){const ne=r.oxw();r.Q6J("ngTemplateOutlet",ne.column.cellTemplate)("ngTemplateOutletContext",ne.cellContext)}}function Zr(tt,sn){if(1&tt&&r._UZ(0,"datatable-body-row",1),2&tt){const ne=r.oxw();r.Q6J("innerWidth",ne.innerWidth)("offsetX",ne.offsetX)("columns",ne._internalColumns)("rowHeight",ne.rowHeight)("row",ne.summaryRow)("rowIndex",-1)}}let Cn=(()=>{class tt{constructor(ne){this.document=ne,this.width=this.getWidth()}getWidth(){const ne=this.document.createElement("div");ne.style.visibility="hidden",ne.style.width="100px",ne.style.msOverflowStyle="scrollbar",this.document.body.appendChild(ne);const $e=ne.offsetWidth;ne.style.overflow="scroll";const Lt=this.document.createElement("div");Lt.style.width="100%",ne.appendChild(Lt);const an=Lt.offsetWidth;return ne.parentNode.removeChild(ne),$e-an}}return tt.\u0275fac=function(ne){return new(ne||tt)(r.LFG(u.K0))},tt.\u0275prov=r.Yz7({token:tt,factory:tt.\u0275fac}),tt})(),Wt=(()=>{class tt{getDimensions(ne){return ne.getBoundingClientRect()}}return tt.\u0275fac=function(ne){return new(ne||tt)},tt.\u0275prov=r.Yz7({token:tt,factory:tt.\u0275fac}),tt})(),zn=(()=>{class tt{constructor(){this.columnInputChanges=new p.xQ}get columnInputChanges$(){return this.columnInputChanges.asObservable()}onInputChange(){this.columnInputChanges.next()}}return tt.\u0275fac=function(ne){return new(ne||tt)},tt.\u0275prov=r.Yz7({token:tt,factory:tt.\u0275fac}),tt})(),rr=(()=>{class tt{constructor(ne){this.template=ne}}return tt.\u0275fac=function(ne){return new(ne||tt)(r.Y36(r.Rgc))},tt.\u0275dir=r.lG2({type:tt,selectors:[["","ngx-datatable-footer-template",""]]}),tt})(),Fr=(()=>{class tt{constructor(ne,$e){this.element=ne,this.zone=$e,this.isVisible=!1,this.visible=new r.vpe}ngOnInit(){this.runCheck()}ngOnDestroy(){clearTimeout(this.timeout)}onVisibilityChange(){this.zone.run(()=>{this.isVisible=!0,this.visible.emit(!0)})}runCheck(){const ne=()=>{const{offsetHeight:$e,offsetWidth:Lt}=this.element.nativeElement;$e&&Lt?(clearTimeout(this.timeout),this.onVisibilityChange()):(clearTimeout(this.timeout),this.zone.runOutsideAngular(()=>{this.timeout=setTimeout(()=>ne(),50)}))};this.timeout=setTimeout(()=>ne())}}return tt.\u0275fac=function(ne){return new(ne||tt)(r.Y36(r.SBq),r.Y36(r.R0b))},tt.\u0275dir=r.lG2({type:tt,selectors:[["","visibilityObserver",""]],hostVars:2,hostBindings:function(ne,$e){2&ne&&r.ekj("visible",$e.isVisible)},outputs:{visible:"visible"}}),tt})(),Gn=(()=>{class tt{constructor(ne){this.dragX=!0,this.dragY=!0,this.dragStart=new r.vpe,this.dragging=new r.vpe,this.dragEnd=new r.vpe,this.isDragging=!1,this.element=ne.nativeElement}ngOnChanges(ne){ne.dragEventTarget&&ne.dragEventTarget.currentValue&&this.dragModel.dragging&&this.onMousedown(ne.dragEventTarget.currentValue)}ngOnDestroy(){this._destroySubscription()}onMouseup(ne){!this.isDragging||(this.isDragging=!1,this.element.classList.remove("dragging"),this.subscription&&(this._destroySubscription(),this.dragEnd.emit({event:ne,element:this.element,model:this.dragModel})))}onMousedown(ne){if(ne.target.classList.contains("draggable")&&(this.dragX||this.dragY)){ne.preventDefault(),this.isDragging=!0;const Lt={x:ne.clientX,y:ne.clientY},an=(0,d.R)(document,"mouseup");this.subscription=an.subscribe(pi=>this.onMouseup(pi));const ti=(0,d.R)(document,"mousemove").pipe((0,_.R)(an)).subscribe(pi=>this.move(pi,Lt));this.subscription.add(ti),this.dragStart.emit({event:ne,element:this.element,model:this.dragModel})}}move(ne,$e){if(!this.isDragging)return;const an=ne.clientY-$e.y;this.dragX&&(this.element.style.left=ne.clientX-$e.x+"px"),this.dragY&&(this.element.style.top=`${an}px`),this.element.classList.add("dragging"),this.dragging.emit({event:ne,element:this.element,model:this.dragModel})}_destroySubscription(){this.subscription&&(this.subscription.unsubscribe(),this.subscription=void 0)}}return tt.\u0275fac=function(ne){return new(ne||tt)(r.Y36(r.SBq))},tt.\u0275dir=r.lG2({type:tt,selectors:[["","draggable",""]],inputs:{dragX:"dragX",dragY:"dragY",dragEventTarget:"dragEventTarget",dragModel:"dragModel"},outputs:{dragStart:"dragStart",dragging:"dragging",dragEnd:"dragEnd"},features:[r.TTD]}),tt})(),Jr=(()=>{class tt{constructor(ne,$e){this.renderer=$e,this.resizeEnabled=!0,this.resize=new r.vpe,this.resizing=!1,this.element=ne.nativeElement}ngAfterViewInit(){const ne=this.renderer;this.resizeHandle=ne.createElement("span"),ne.addClass(this.resizeHandle,this.resizeEnabled?"resize-handle":"resize-handle--not-resizable"),ne.appendChild(this.element,this.resizeHandle)}ngOnDestroy(){this._destroySubscription(),this.renderer.destroyNode?this.renderer.destroyNode(this.resizeHandle):this.resizeHandle&&this.renderer.removeChild(this.renderer.parentNode(this.resizeHandle),this.resizeHandle)}onMouseup(){this.resizing=!1,this.subscription&&!this.subscription.closed&&(this._destroySubscription(),this.resize.emit(this.element.clientWidth))}onMousedown(ne){const $e=ne.target.classList.contains("resize-handle"),Lt=this.element.clientWidth,an=ne.screenX;if($e){ne.stopPropagation(),this.resizing=!0;const ti=(0,d.R)(document,"mouseup");this.subscription=ti.subscribe(xi=>this.onMouseup());const pi=(0,d.R)(document,"mousemove").pipe((0,_.R)(ti)).subscribe(xi=>this.move(xi,Lt,an));this.subscription.add(pi)}}move(ne,$e,Lt){const ti=$e+(ne.screenX-Lt);(!this.minWidth||ti>=this.minWidth)&&(!this.maxWidth||ti<=this.maxWidth)&&(this.element.style.width=`${ti}px`)}_destroySubscription(){this.subscription&&(this.subscription.unsubscribe(),this.subscription=void 0)}}return tt.\u0275fac=function(ne){return new(ne||tt)(r.Y36(r.SBq),r.Y36(r.Qsj))},tt.\u0275dir=r.lG2({type:tt,selectors:[["","resizeable",""]],hostVars:2,hostBindings:function(ne,$e){1&ne&&r.NdJ("mousedown",function(an){return $e.onMousedown(an)}),2&ne&&r.ekj("resizeable",$e.resizeEnabled)},inputs:{resizeEnabled:"resizeEnabled",minWidth:"minWidth",maxWidth:"maxWidth"},outputs:{resize:"resize"}}),tt})(),_i=(()=>{class tt{constructor(ne,$e){this.document=$e,this.reorder=new r.vpe,this.targetChanged=new r.vpe,this.differ=ne.find({}).create()}ngAfterContentInit(){this.updateSubscriptions(),this.draggables.changes.subscribe(this.updateSubscriptions.bind(this))}ngOnDestroy(){this.draggables.forEach(ne=>{ne.dragStart.unsubscribe(),ne.dragging.unsubscribe(),ne.dragEnd.unsubscribe()})}updateSubscriptions(){const ne=this.differ.diff(this.createMapDiffs());if(ne){const $e=({currentValue:an,previousValue:ti})=>{Lt({previousValue:ti}),an&&(an.dragStart.subscribe(this.onDragStart.bind(this)),an.dragging.subscribe(this.onDragging.bind(this)),an.dragEnd.subscribe(this.onDragEnd.bind(this)))},Lt=({previousValue:an})=>{an&&(an.dragStart.unsubscribe(),an.dragging.unsubscribe(),an.dragEnd.unsubscribe())};ne.forEachAddedItem($e),ne.forEachRemovedItem(Lt)}}onDragStart(){this.positions={};let ne=0;for(const $e of this.draggables.toArray()){const Lt=$e.element,an=parseInt(Lt.offsetLeft.toString(),0);this.positions[$e.dragModel.prop]={left:an,right:an+parseInt(Lt.offsetWidth.toString(),0),index:ne++,element:Lt}}}onDragging({model:$e,event:Lt}){const an=this.positions[$e.prop],ti=this.isTarget($e,Lt);ti?this.lastDraggingIndex!==ti.i&&(this.targetChanged.emit({prevIndex:this.lastDraggingIndex,newIndex:ti.i,initialIndex:an.index}),this.lastDraggingIndex=ti.i):this.lastDraggingIndex!==an.index&&(this.targetChanged.emit({prevIndex:this.lastDraggingIndex,initialIndex:an.index}),this.lastDraggingIndex=an.index)}onDragEnd({element:ne,model:$e,event:Lt}){const an=this.positions[$e.prop],ti=this.isTarget($e,Lt);ti&&this.reorder.emit({prevIndex:an.index,newIndex:ti.i,model:$e}),this.lastDraggingIndex=void 0,ne.style.left="auto"}isTarget(ne,$e){let Lt=0;const pi=this.document.elementsFromPoint($e.x||$e.clientX,$e.y||$e.clientY);for(const xi in this.positions){const ts=this.positions[xi];if(ne.prop!==xi&&pi.find(wo=>wo===ts.element))return{pos:ts,i:Lt};Lt++}}createMapDiffs(){return this.draggables.toArray().reduce((ne,$e)=>(ne[$e.dragModel.$$id]=$e,ne),{})}}return tt.\u0275fac=function(ne){return new(ne||tt)(r.Y36(r.aQg),r.Y36(u.K0))},tt.\u0275dir=r.lG2({type:tt,selectors:[["","orderable",""]],contentQueries:function(ne,$e,Lt){if(1&ne&&r.Suo(Lt,Gn,5),2&ne){let an;r.iGM(an=r.CRH())&&($e.draggables=an)}},outputs:{reorder:"reorder",targetChanged:"targetChanged"}}),tt})(),wi=(()=>{class tt{constructor(){this.pressEnabled=!0,this.duration=500,this.longPressStart=new r.vpe,this.longPressing=new r.vpe,this.longPressEnd=new r.vpe,this.mouseX=0,this.mouseY=0}get press(){return this.pressing}get isLongPress(){return this.isLongPressing}onMouseDown(ne){if(1!==ne.which||!this.pressEnabled||ne.target.classList.contains("resize-handle"))return;this.mouseX=ne.clientX,this.mouseY=ne.clientY,this.pressing=!0,this.isLongPressing=!1;const Lt=(0,d.R)(document,"mouseup");this.subscription=Lt.subscribe(an=>this.onMouseup()),this.timeout=setTimeout(()=>{this.isLongPressing=!0,this.longPressStart.emit({event:ne,model:this.pressModel}),this.subscription.add((0,d.R)(document,"mousemove").pipe((0,_.R)(Lt)).subscribe(an=>this.onMouseMove(an))),this.loop(ne)},this.duration),this.loop(ne)}onMouseMove(ne){if(this.pressing&&!this.isLongPressing){const $e=Math.abs(ne.clientX-this.mouseX)>10,Lt=Math.abs(ne.clientY-this.mouseY)>10;($e||Lt)&&this.endPress()}}loop(ne){this.isLongPressing&&(this.timeout=setTimeout(()=>{this.longPressing.emit({event:ne,model:this.pressModel}),this.loop(ne)},50))}endPress(){clearTimeout(this.timeout),this.isLongPressing=!1,this.pressing=!1,this._destroySubscription(),this.longPressEnd.emit({model:this.pressModel})}onMouseup(){this.endPress()}ngOnDestroy(){this._destroySubscription()}_destroySubscription(){this.subscription&&(this.subscription.unsubscribe(),this.subscription=void 0)}}return tt.\u0275fac=function(ne){return new(ne||tt)},tt.\u0275dir=r.lG2({type:tt,selectors:[["","long-press",""]],hostVars:4,hostBindings:function(ne,$e){1&ne&&r.NdJ("mousedown",function(an){return $e.onMouseDown(an)}),2&ne&&r.ekj("press",$e.press)("longpress",$e.isLongPress)},inputs:{pressEnabled:"pressEnabled",duration:"duration",pressModel:"pressModel"},outputs:{longPressStart:"longPressStart",longPressing:"longPressing",longPressEnd:"longPressEnd"}}),tt})(),br=(()=>{class tt{constructor(ne,$e,Lt){this.ngZone=ne,this.renderer=Lt,this.scrollbarV=!1,this.scrollbarH=!1,this.scroll=new r.vpe,this.scrollYPos=0,this.scrollXPos=0,this.prevScrollYPos=0,this.prevScrollXPos=0,this._scrollEventListener=null,this.element=$e.nativeElement}ngOnInit(){if(this.scrollbarV||this.scrollbarH){const ne=this.renderer;this.parentElement=ne.parentNode(ne.parentNode(this.element)),this._scrollEventListener=this.onScrolled.bind(this),this.parentElement.addEventListener("scroll",this._scrollEventListener)}}ngOnDestroy(){this._scrollEventListener&&(this.parentElement.removeEventListener("scroll",this._scrollEventListener),this._scrollEventListener=null)}setOffset(ne){this.parentElement&&(this.parentElement.scrollTop=ne)}onScrolled(ne){const $e=ne.currentTarget;requestAnimationFrame(()=>{this.scrollYPos=$e.scrollTop,this.scrollXPos=$e.scrollLeft,this.updateOffset()})}updateOffset(){let ne;this.scrollYPosthis.prevScrollYPos&&(ne="up"),this.scroll.emit({direction:ne,scrollYPos:this.scrollYPos,scrollXPos:this.scrollXPos}),this.prevScrollYPos=this.scrollYPos,this.prevScrollXPos=this.scrollXPos}}return tt.\u0275fac=function(ne){return new(ne||tt)(r.Y36(r.R0b),r.Y36(r.SBq),r.Y36(r.Qsj))},tt.\u0275cmp=r.Xpm({type:tt,selectors:[["datatable-scroller"]],hostAttrs:[1,"datatable-scroll"],hostVars:4,hostBindings:function(ne,$e){2&ne&&r.Udp("height",$e.scrollHeight,"px")("width",$e.scrollWidth,"px")},inputs:{scrollbarV:"scrollbarV",scrollbarH:"scrollbarH",scrollHeight:"scrollHeight",scrollWidth:"scrollWidth"},outputs:{scroll:"scroll"},ngContentSelectors:S,decls:1,vars:0,template:function(ne,$e){1&ne&&(r.F$t(),r.Hsn(0))},encapsulation:2,changeDetection:0}),tt})(),Dr=(()=>{class tt{constructor(ne){this.template=ne}}return tt.\u0275fac=function(ne){return new(ne||tt)(r.Y36(r.Rgc))},tt.\u0275dir=r.lG2({type:tt,selectors:[["","ngx-datatable-group-header-template",""]]}),tt})(),gn=(()=>{class tt{constructor(){this.rowHeight=0,this.toggle=new r.vpe}get template(){return this._templateInput||this._templateQuery}toggleExpandGroup(ne){this.toggle.emit({type:"group",value:ne})}expandAllGroups(){this.toggle.emit({type:"all",value:!0})}collapseAllGroups(){this.toggle.emit({type:"all",value:!1})}}return tt.\u0275fac=function(ne){return new(ne||tt)},tt.\u0275dir=r.lG2({type:tt,selectors:[["ngx-datatable-group-header"]],contentQueries:function(ne,$e,Lt){if(1&ne&&r.Suo(Lt,Dr,7,r.Rgc),2&ne){let an;r.iGM(an=r.CRH())&&($e._templateQuery=an.first)}},inputs:{rowHeight:"rowHeight",_templateInput:["template","_templateInput"]},outputs:{toggle:"toggle"}}),tt})();function yn(){return""}function gr(tt){return null==tt?yn:"number"==typeof tt?Jt:-1!==tt.indexOf(".")?mr:Vn}function Jt(tt,sn){if(null==tt)return"";if(!tt||null==sn)return tt;const ne=tt[sn];return null==ne?"":ne}function Vn(tt,sn){if(null==tt)return"";if(!tt||!sn)return tt;const ne=tt[sn];return null==ne?"":ne}function mr(tt,sn){if(null==tt)return"";if(!tt||!sn)return tt;let ne=tt[sn];if(void 0!==ne)return ne;ne=tt;const $e=sn.split(".");if($e.length)for(let Lt=0;Lt<$e.length;Lt++)if(ne=ne[$e[Lt]],null==ne)return"";return ne}function Dn(tt){return tt&&(sn=>gr(tt)(sn,tt))}function Pr(tt,sn,ne){if(sn&&ne){const $e={},Lt=tt.length;let an=null;$e[0]=new Yt;const ti=tt.reduce((xi,ts)=>{const wo=ne(ts);return-1===xi.indexOf(wo)&&xi.push(wo),xi},[]);for(let xi=0;xi-1&&(ts=wo),an.parent=$e[ts],an.row.level=an.parent.row.level+1,an.parent.children.push(an)}let pi=[];return $e[0].flatten(function(){pi=[...pi,this.row]},!0),pi}return tt}class Yt{constructor(sn=null){sn||(sn={level:-1,treeStatus:"expanded"}),this.row=sn,this.parent=null,this.children=[]}flatten(sn,ne){if("expanded"===this.row.treeStatus)for(let $e=0,Lt=this.children.length;$e` ${sn}`).replace(/^./,sn=>sn.toUpperCase())}function mi(tt){if(!tt)return;let sn=!1;for(const ne of tt)ne.$$id||(ne.$$id=("0000"+(Math.random()*Math.pow(36,4)<<0).toString(36)).slice(-4)),An(ne.prop)&&ne.name&&(ne.prop=_n(ne.name)),ne.$$valueGetter||(ne.$$valueGetter=gr(ne.prop)),!An(ne.prop)&&An(ne.name)&&(ne.name=Ge(String(ne.prop))),An(ne.prop)&&An(ne.name)&&(ne.name=""),ne.hasOwnProperty("resizeable")||(ne.resizeable=!0),ne.hasOwnProperty("sortable")||(ne.sortable=!0),ne.hasOwnProperty("draggable")||(ne.draggable=!0),ne.hasOwnProperty("canAutoResize")||(ne.canAutoResize=!0),ne.hasOwnProperty("width")||(ne.width=150),ne.hasOwnProperty("isTreeColumn")&&ne.isTreeColumn&&!sn?sn=!0:ne.isTreeColumn=!1}function An(tt){return null==tt}var Wr=(()=>{return(tt=Wr||(Wr={})).standard="standard",tt.flex="flex",tt.force="force",Wr;var tt})(),dr=(()=>{return(tt=dr||(dr={})).single="single",tt.multi="multi",tt.multiClick="multiClick",tt.cell="cell",tt.checkbox="checkbox",dr;var tt})(),Fn=(()=>{return(tt=Fn||(Fn={})).single="single",tt.multi="multi",Fn;var tt})(),ar=(()=>{return(tt=ar||(ar={})).header="header",tt.body="body",ar;var tt})();let Wi=(()=>{class tt{constructor(ne){this.template=ne}}return tt.\u0275fac=function(ne){return new(ne||tt)(r.Y36(r.Rgc))},tt.\u0275dir=r.lG2({type:tt,selectors:[["","ngx-datatable-header-template",""]]}),tt})(),lo=(()=>{class tt{constructor(ne){this.template=ne}}return tt.\u0275fac=function(ne){return new(ne||tt)(r.Y36(r.Rgc))},tt.\u0275dir=r.lG2({type:tt,selectors:[["","ngx-datatable-cell-template",""]]}),tt})(),vo=(()=>{class tt{constructor(ne){this.template=ne}}return tt.\u0275fac=function(ne){return new(ne||tt)(r.Y36(r.Rgc))},tt.\u0275dir=r.lG2({type:tt,selectors:[["","ngx-datatable-tree-toggle",""]]}),tt})(),Co=(()=>{class tt{constructor(ne){this.columnChangesService=ne,this.isFirstChange=!0}get cellTemplate(){return this._cellTemplateInput||this._cellTemplateQuery}get headerTemplate(){return this._headerTemplateInput||this._headerTemplateQuery}get treeToggleTemplate(){return this._treeToggleTemplateInput||this._treeToggleTemplateQuery}ngOnChanges(){this.isFirstChange?this.isFirstChange=!1:this.columnChangesService.onInputChange()}}return tt.\u0275fac=function(ne){return new(ne||tt)(r.Y36(zn))},tt.\u0275dir=r.lG2({type:tt,selectors:[["ngx-datatable-column"]],contentQueries:function(ne,$e,Lt){if(1&ne&&(r.Suo(Lt,lo,7,r.Rgc),r.Suo(Lt,Wi,7,r.Rgc),r.Suo(Lt,vo,7,r.Rgc)),2&ne){let an;r.iGM(an=r.CRH())&&($e._cellTemplateQuery=an.first),r.iGM(an=r.CRH())&&($e._headerTemplateQuery=an.first),r.iGM(an=r.CRH())&&($e._treeToggleTemplateQuery=an.first)}},inputs:{name:"name",prop:"prop",frozenLeft:"frozenLeft",frozenRight:"frozenRight",flexGrow:"flexGrow",resizeable:"resizeable",comparator:"comparator",pipe:"pipe",sortable:"sortable",draggable:"draggable",canAutoResize:"canAutoResize",minWidth:"minWidth",width:"width",maxWidth:"maxWidth",checkboxable:"checkboxable",headerCheckboxable:"headerCheckboxable",headerClass:"headerClass",cellClass:"cellClass",isTreeColumn:"isTreeColumn",treeLevelIndent:"treeLevelIndent",summaryFunc:"summaryFunc",summaryTemplate:"summaryTemplate",_cellTemplateInput:["cellTemplate","_cellTemplateInput"],_headerTemplateInput:["headerTemplate","_headerTemplateInput"],_treeToggleTemplateInput:["treeToggleTemplate","_treeToggleTemplateInput"]},features:[r.TTD]}),tt})(),Gi=(()=>{class tt{constructor(ne){this.template=ne}}return tt.\u0275fac=function(ne){return new(ne||tt)(r.Y36(r.Rgc))},tt.\u0275dir=r.lG2({type:tt,selectors:[["","ngx-datatable-row-detail-template",""]]}),tt})(),os=(()=>{class tt{constructor(){this.rowHeight=0,this.toggle=new r.vpe}get template(){return this._templateInput||this._templateQuery}toggleExpandRow(ne){this.toggle.emit({type:"row",value:ne})}expandAllRows(){this.toggle.emit({type:"all",value:!0})}collapseAllRows(){this.toggle.emit({type:"all",value:!1})}}return tt.\u0275fac=function(ne){return new(ne||tt)},tt.\u0275dir=r.lG2({type:tt,selectors:[["ngx-datatable-row-detail"]],contentQueries:function(ne,$e,Lt){if(1&ne&&r.Suo(Lt,Gi,7,r.Rgc),2&ne){let an;r.iGM(an=r.CRH())&&($e._templateQuery=an.first)}},inputs:{rowHeight:"rowHeight",_templateInput:["template","_templateInput"]},outputs:{toggle:"toggle"}}),tt})(),jo=(()=>{class tt{get template(){return this._templateInput||this._templateQuery}}return tt.\u0275fac=function(ne){return new(ne||tt)},tt.\u0275dir=r.lG2({type:tt,selectors:[["ngx-datatable-footer"]],contentQueries:function(ne,$e,Lt){if(1&ne&&r.Suo(Lt,rr,5,r.Rgc),2&ne){let an;r.iGM(an=r.CRH())&&($e._templateQuery=an.first)}},inputs:{footerHeight:"footerHeight",totalMessage:"totalMessage",selectedMessage:"selectedMessage",pagerLeftArrowIcon:"pagerLeftArrowIcon",pagerRightArrowIcon:"pagerRightArrowIcon",pagerPreviousIcon:"pagerPreviousIcon",pagerNextIcon:"pagerNextIcon",_templateInput:["template","_templateInput"]}}),tt})();function To(tt){const sn={left:[],center:[],right:[]};if(tt)for(const ne of tt)ne.frozenLeft?sn.left.push(ne):ne.frozenRight?sn.right.push(ne):sn.center.push(ne);return sn}function Mi(tt,sn){return{left:li(tt.left),center:li(tt.center),right:li(tt.right),total:Math.floor(li(sn))}}function li(tt,sn){let ne=0;if(tt)for(const $e of tt)ne+=parseFloat(sn&&$e[sn]?$e[sn]:$e.width);return ne}function lr(tt){const sn=[],ne=To(tt);return sn.push({type:"left",columns:ne.left}),sn.push({type:"center",columns:ne.center}),sn.push({type:"right",columns:ne.right}),sn}class vr{constructor(){this.treeArray=[]}clearCache(){this.treeArray=[]}initCache(sn){const{rows:ne,rowHeight:$e,detailRowHeight:Lt,externalVirtual:an,rowCount:ti,rowIndexes:pi,rowExpansions:xi}=sn,ts="function"==typeof $e,wo="function"==typeof Lt;if(!ts&&isNaN($e))throw new Error(`Row Height cache initialization failed. Please ensure that 'rowHeight' is a\n valid number or function value: (${$e}) when 'scrollbarV' is enabled.`);if(!wo&&isNaN(Lt))throw new Error(`Row Height cache initialization failed. Please ensure that 'detailRowHeight' is a\n valid number or function value: (${Lt}) when 'scrollbarV' is enabled.`);const ko=an?ti:ne.length;this.treeArray=new Array(ko);for(let Eo=0;Eo=0;)ne+=this.treeArray[sn],sn=(sn&sn+1)-1;return ne}queryBetween(sn,ne){return this.query(ne)-this.query(sn-1)}calcRowIndex(sn){if(!this.treeArray.length)return 0;let ne=-1;const $e=this.treeArray.length;for(let an=Math.pow(2,$e.toString(2).length-1);0!==an;an>>=1){const ti=ne+an;ti<$e&&sn>=this.treeArray[ti]&&(sn-=this.treeArray[ti],ne=ti)}return ne+1}}const er={},ri="undefined"!=typeof document?document.createElement("div").style:void 0,Ci=function(){const tt="undefined"!=typeof window?window.getComputedStyle(document.documentElement,""):void 0,sn=void 0!==tt?Array.prototype.slice.call(tt).join("").match(/-(moz|webkit|ms)-/):null,ne=null!==sn?sn[1]:void 0,$e=void 0!==ne?"WebKit|Moz|MS|O".match(new RegExp("("+ne+")","i"))[1]:void 0;return $e?{dom:$e,lowercase:ne,css:`-${ne}-`,js:ne[0].toUpperCase()+ne.substr(1)}:void 0}();function $o(tt){const sn=_n(tt);return er[sn]||(void 0!==Ci&&void 0!==ri[Ci.css+tt]?er[sn]=Ci.css+tt:void 0!==ri[tt]&&(er[sn]=tt)),er[sn]}const tr="undefined"!=typeof window?$o("transform"):void 0,Ar="undefined"!=typeof window?$o("backfaceVisibility"):void 0,Rt="undefined"!=typeof window?!!$o("transform"):void 0,_t="undefined"!=typeof window?!!$o("perspective"):void 0,mt="undefined"!=typeof window?window.navigator.userAgent:"Chrome",jt=/Safari\//.test(mt)&&!/Chrome\//.test(mt);function on(tt,sn,ne){void 0!==tr&&Rt?!jt&&_t?(tt[tr]=`translate3d(${sn}px, ${ne}px, 0)`,tt[Ar]="hidden"):tt[_n(tr)]=`translate(${sn}px, ${ne}px)`:(tt.top=`${ne}px`,tt.left=`${sn}px`)}let si=(()=>{class tt{constructor(ne){this.cd=ne,this.selected=[],this.scroll=new r.vpe,this.page=new r.vpe,this.activate=new r.vpe,this.select=new r.vpe,this.detailToggle=new r.vpe,this.rowContextmenu=new r.vpe(!1),this.treeAction=new r.vpe,this.rowHeightsCache=new vr,this.temp=[],this.offsetY=0,this.indexes={},this.rowIndexes=new WeakMap,this.rowExpansions=[],this.getDetailRowHeight=($e,Lt)=>{if(!this.rowDetail)return 0;const an=this.rowDetail.rowHeight;return"function"==typeof an?an($e,Lt):an},this.rowTrackingFn=($e,Lt)=>{const an=this.getRowIndex(Lt);return this.trackByProp?Lt[this.trackByProp]:an}}set pageSize(ne){this._pageSize=ne,this.recalcLayout()}get pageSize(){return this._pageSize}set rows(ne){this._rows=ne,this.recalcLayout()}get rows(){return this._rows}set columns(ne){this._columns=ne;const $e=To(ne);this.columnGroupWidths=Mi($e,ne)}get columns(){return this._columns}set offset(ne){this._offset=ne,(!this.scrollbarV||this.scrollbarV&&!this.virtualization)&&this.recalcLayout()}get offset(){return this._offset}set rowCount(ne){this._rowCount=ne,this.recalcLayout()}get rowCount(){return this._rowCount}get bodyWidth(){return this.scrollbarH?this.innerWidth+"px":"100%"}set bodyHeight(ne){this._bodyHeight=this.scrollbarV?ne+"px":"auto",this.recalcLayout()}get bodyHeight(){return this._bodyHeight}get selectEnabled(){return!!this.selectionType}get scrollHeight(){if(this.scrollbarV&&this.virtualization&&this.rowCount)return this.rowHeightsCache.query(this.rowCount-1)}ngOnInit(){this.rowDetail&&(this.listener=this.rowDetail.toggle.subscribe(({type:ne,value:$e})=>{"row"===ne&&this.toggleRowExpansion($e),"all"===ne&&this.toggleAllRows($e),this.updateIndexes(),this.updateRows(),this.cd.markForCheck()})),this.groupHeader&&(this.listener=this.groupHeader.toggle.subscribe(({type:ne,value:$e})=>{"group"===ne&&this.toggleRowExpansion($e),"all"===ne&&this.toggleAllRows($e),this.updateIndexes(),this.updateRows(),this.cd.markForCheck()}))}ngOnDestroy(){(this.rowDetail||this.groupHeader)&&this.listener.unsubscribe()}updateOffsetY(ne){this.scroller&&(this.scrollbarV&&this.virtualization&&ne?ne=this.rowHeightsCache.query(this.pageSize*ne-1):this.scrollbarV&&!this.virtualization&&(ne=0),this.scroller.setOffset(ne||0))}onBodyScroll(ne){const $e=ne.scrollYPos,Lt=ne.scrollXPos;(this.offsetY!==$e||this.offsetX!==Lt)&&this.scroll.emit({offsetY:$e,offsetX:Lt}),this.offsetY=$e,this.offsetX=Lt,this.updateIndexes(),this.updatePage(ne.direction),this.updateRows()}updatePage(ne){let $e=this.indexes.first/this.pageSize;"up"===ne?$e=Math.ceil($e):"down"===ne&&($e=Math.floor($e)),void 0!==ne&&!isNaN($e)&&this.page.emit({offset:$e})}updateRows(){const{first:ne,last:$e}=this.indexes;let Lt=ne,an=0;const ti=[];if(this.groupedRows){let pi=3;for(1===this.groupedRows.length&&(pi=this.groupedRows[0].value.length);Lt<$e&&Lt{this.rowIndexes.set(ts,`${Lt}-${wo}`)}),ti[an]=xi,an++,Lt++}}else for(;Lt<$e&&Ltthis.loadingIndicator=!1,500)}updateIndexes(){let ne=0,$e=0;if(this.scrollbarV)if(this.virtualization){const Lt=parseInt(this.bodyHeight,0);ne=this.rowHeightsCache.getRowIndex(this.offsetY),$e=this.rowHeightsCache.getRowIndex(Lt+this.offsetY)+1}else ne=0,$e=this.rowCount;else this.externalPaging||(ne=Math.max(this.offset*this.pageSize,0)),$e=Math.min(ne+this.pageSize,this.rowCount);this.indexes={first:ne,last:$e}}refreshRowHeightCache(){if(this.scrollbarV&&(!this.scrollbarV||this.virtualization)&&(this.rowHeightsCache.clearCache(),this.rows&&this.rows.length)){const ne=new Set;for(const $e of this.rows)this.getRowExpanded($e)&&ne.add($e);this.rowHeightsCache.initCache({rows:this.rows,rowHeight:this.rowHeight,detailRowHeight:this.getDetailRowHeight,externalVirtual:this.scrollbarV&&this.externalPaging,rowCount:this.rowCount,rowIndexes:this.rowIndexes,rowExpansions:ne})}}getAdjustedViewPortIndex(){const ne=this.indexes.first;return this.scrollbarV&&this.virtualization&&this.rowHeightsCache.query(ne-1)<=this.offsetY?ne-1:ne}toggleRowExpansion(ne){const $e=this.getAdjustedViewPortIndex(),Lt=this.getRowExpandedIdx(ne,this.rowExpansions),an=Lt>-1;if(this.scrollbarV&&this.virtualization){const ti=this.getDetailRowHeight(ne)*(an?-1:1),pi=this.getRowIndex(ne);this.rowHeightsCache.update(pi,ti)}an?this.rowExpansions.splice(Lt,1):this.rowExpansions.push(ne),this.detailToggle.emit({rows:[ne],currentIndex:$e})}toggleAllRows(ne){this.rowExpansions=[];const $e=this.getAdjustedViewPortIndex();if(ne)for(const Lt of this.rows)this.rowExpansions.push(Lt);this.scrollbarV&&this.recalcLayout(),this.detailToggle.emit({rows:this.rows,currentIndex:$e})}recalcLayout(){this.refreshRowHeightCache(),this.updateIndexes(),this.updateRows()}columnTrackingFn(ne,$e){return $e.$$id}stylesByGroup(ne){const $e=this.columnGroupWidths,Lt=this.offsetX,an={width:`${$e[ne]}px`};if("left"===ne)on(an,Lt,0);else if("right"===ne){const ti=parseInt(this.innerWidth+"",0);on(an,-1*($e.total-ti-Lt),0)}return an}getRowExpanded(ne){if(0===this.rowExpansions.length&&this.groupExpansionDefault)for(const $e of this.groupedRows)this.rowExpansions.push($e);return this.getRowExpandedIdx(ne,this.rowExpansions)>-1}getRowExpandedIdx(ne,$e){if(!$e||!$e.length)return-1;const Lt=this.rowIdentity(ne);return $e.findIndex(an=>this.rowIdentity(an)===Lt)}getRowIndex(ne){return this.rowIndexes.get(ne)||0}onTreeAction(ne){this.treeAction.emit({row:ne})}}return tt.\u0275fac=function(ne){return new(ne||tt)(r.Y36(r.sBO))},tt.\u0275cmp=r.Xpm({type:tt,selectors:[["datatable-body"]],viewQuery:function(ne,$e){if(1&ne&&r.Gf(br,5),2&ne){let Lt;r.iGM(Lt=r.CRH())&&($e.scroller=Lt.first)}},hostAttrs:[1,"datatable-body"],hostVars:4,hostBindings:function(ne,$e){2&ne&&r.Udp("width",$e.bodyWidth)("height",$e.bodyHeight)},inputs:{selected:"selected",pageSize:"pageSize",rows:"rows",columns:"columns",offset:"offset",rowCount:"rowCount",bodyHeight:"bodyHeight",offsetX:"offsetX",loadingIndicator:"loadingIndicator",scrollbarV:"scrollbarV",scrollbarH:"scrollbarH",externalPaging:"externalPaging",rowHeight:"rowHeight",emptyMessage:"emptyMessage",selectionType:"selectionType",rowIdentity:"rowIdentity",rowDetail:"rowDetail",groupHeader:"groupHeader",selectCheck:"selectCheck",displayCheck:"displayCheck",trackByProp:"trackByProp",rowClass:"rowClass",groupedRows:"groupedRows",groupExpansionDefault:"groupExpansionDefault",innerWidth:"innerWidth",groupRowsBy:"groupRowsBy",virtualization:"virtualization",summaryRow:"summaryRow",summaryPosition:"summaryPosition",summaryHeight:"summaryHeight"},outputs:{scroll:"scroll",page:"page",activate:"activate",select:"select",detailToggle:"detailToggle",rowContextmenu:"rowContextmenu",treeAction:"treeAction"},decls:5,vars:9,consts:[[4,"ngIf"],[3,"selected","rows","selectCheck","selectEnabled","selectionType","rowIdentity","select","activate"],["selector",""],[3,"scrollbarV","scrollbarH","scrollHeight","scrollWidth","scroll",4,"ngIf"],["class","empty-row",3,"innerHTML",4,"ngIf"],[3,"scrollbarV","scrollbarH","scrollHeight","scrollWidth","scroll"],[3,"rowHeight","offsetX","innerWidth","rows","columns",4,"ngIf"],[3,"groupedRows","innerWidth","ngStyle","rowDetail","groupHeader","offsetX","detailRowHeight","row","expanded","rowIndex","rowContextmenu",4,"ngFor","ngForOf","ngForTrackBy"],[3,"ngStyle","rowHeight","offsetX","innerWidth","rows","columns",4,"ngIf"],[3,"rowHeight","offsetX","innerWidth","rows","columns"],[3,"groupedRows","innerWidth","ngStyle","rowDetail","groupHeader","offsetX","detailRowHeight","row","expanded","rowIndex","rowContextmenu"],["tabindex","-1",3,"isSelected","innerWidth","offsetX","columns","rowHeight","row","rowIndex","expanded","rowClass","displayCheck","treeStatus","treeAction","activate",4,"ngIf","ngIfElse"],["groupedRowsTemplate",""],["tabindex","-1",3,"isSelected","innerWidth","offsetX","columns","rowHeight","row","rowIndex","expanded","rowClass","displayCheck","treeStatus","treeAction","activate"],["tabindex","-1",3,"isSelected","innerWidth","offsetX","columns","rowHeight","row","group","rowIndex","expanded","rowClass","activate",4,"ngFor","ngForOf","ngForTrackBy"],["tabindex","-1",3,"isSelected","innerWidth","offsetX","columns","rowHeight","row","group","rowIndex","expanded","rowClass","activate"],[3,"ngStyle","rowHeight","offsetX","innerWidth","rows","columns"],[1,"empty-row",3,"innerHTML"]],template:function(ne,$e){1&ne&&(r.YNc(0,A,1,0,"datatable-progress",0),r.TgZ(1,"datatable-selection",1,2),r.NdJ("select",function(an){return $e.select.emit(an)})("activate",function(an){return $e.activate.emit(an)}),r.YNc(3,ue,4,8,"datatable-scroller",3),r.YNc(4,ae,1,1,"div",4),r.qZA()),2&ne&&(r.Q6J("ngIf",$e.loadingIndicator),r.xp6(1),r.Q6J("selected",$e.selected)("rows",$e.rows)("selectCheck",$e.selectCheck)("selectEnabled",$e.selectEnabled)("selectionType",$e.selectionType)("rowIdentity",$e.rowIdentity),r.xp6(2),r.Q6J("ngIf",null==$e.rows?null:$e.rows.length),r.xp6(1),r.Q6J("ngIf",!(null!=$e.rows&&$e.rows.length||$e.loadingIndicator)))},directives:function(){return[u.O5,Po,Ya,br,u.sg,ps,Ca,u.PC,fs]},encapsulation:2,changeDetection:0}),tt})(),Vi=(()=>{class tt{constructor(ne){this.cd=ne,this.sort=new r.vpe,this.reorder=new r.vpe,this.resize=new r.vpe,this.select=new r.vpe,this.columnContextmenu=new r.vpe(!1),this._columnGroupWidths={total:100},this._styleByGroup={left:{},center:{},right:{}},this.destroyed=!1}set innerWidth(ne){this._innerWidth=ne,setTimeout(()=>{if(this._columns){const $e=To(this._columns);this._columnGroupWidths=Mi($e,this._columns),this.setStylesByGroup()}})}get innerWidth(){return this._innerWidth}set headerHeight(ne){this._headerHeight="auto"!==ne?`${ne}px`:ne}get headerHeight(){return this._headerHeight}set columns(ne){this._columns=ne;const $e=To(ne);this._columnsByPin=lr(ne),setTimeout(()=>{this._columnGroupWidths=Mi($e,ne),this.setStylesByGroup()})}get columns(){return this._columns}set offsetX(ne){this._offsetX=ne,this.setStylesByGroup()}get offsetX(){return this._offsetX}ngOnDestroy(){this.destroyed=!0}onLongPressStart({event:ne,model:$e}){$e.dragging=!0,this.dragEventTarget=ne}onLongPressEnd({event:ne,model:$e}){this.dragEventTarget=ne,setTimeout(()=>{const Lt=this._columns.find(an=>an.$$id===$e.$$id);Lt&&(Lt.dragging=!1)},5)}get headerWidth(){return this.scrollbarH?this.innerWidth+"px":"100%"}trackByGroups(ne,$e){return $e.type}columnTrackingFn(ne,$e){return $e.$$id}onColumnResized(ne,$e){ne<=$e.minWidth?ne=$e.minWidth:ne>=$e.maxWidth&&(ne=$e.maxWidth),this.resize.emit({column:$e,prevValue:$e.width,newValue:ne})}onColumnReordered({prevIndex:ne,newIndex:$e,model:Lt}){const an=this.getColumn($e);an.isTarget=!1,an.targetMarkerContext=void 0,this.reorder.emit({column:Lt,prevValue:ne,newValue:$e})}onTargetChanged({prevIndex:ne,newIndex:$e,initialIndex:Lt}){if(ne||0===ne){const an=this.getColumn(ne);an.isTarget=!1,an.targetMarkerContext=void 0}if($e||0===$e){const an=this.getColumn($e);an.isTarget=!0,Lt!==$e&&(an.targetMarkerContext={class:"targetMarker ".concat(Lt>$e?"dragFromRight":"dragFromLeft")})}}getColumn(ne){const $e=this._columnsByPin[0].columns.length;if(ne<$e)return this._columnsByPin[0].columns[ne];const Lt=this._columnsByPin[1].columns.length;return ne<$e+Lt?this._columnsByPin[1].columns[ne-$e]:this._columnsByPin[2].columns[ne-$e-Lt]}onSort({column:ne,prevValue:$e,newValue:Lt}){if(ne.dragging)return;const an=this.calcNewSorts(ne,$e,Lt);this.sort.emit({sorts:an,column:ne,prevValue:$e,newValue:Lt})}calcNewSorts(ne,$e,Lt){let an=0;this.sorts||(this.sorts=[]);const ti=this.sorts.map((pi,xi)=>((pi=Object.assign({},pi)).prop===ne.prop&&(an=xi),pi));return void 0===Lt?ti.splice(an,1):$e?ti[an].dir=Lt:(this.sortType===Fn.single&&ti.splice(0,this.sorts.length),ti.push({dir:Lt,prop:ne.prop})),ti}setStylesByGroup(){this._styleByGroup.left=this.calcStylesByGroup("left"),this._styleByGroup.center=this.calcStylesByGroup("center"),this._styleByGroup.right=this.calcStylesByGroup("right"),this.destroyed||this.cd.detectChanges()}calcStylesByGroup(ne){const $e=this._columnGroupWidths,an={width:`${$e[ne]}px`};return"center"===ne?on(an,-1*this.offsetX,0):"right"===ne&&on(an,-1*($e.total-this.innerWidth),0),an}}return tt.\u0275fac=function(ne){return new(ne||tt)(r.Y36(r.sBO))},tt.\u0275cmp=r.Xpm({type:tt,selectors:[["datatable-header"]],hostAttrs:[1,"datatable-header"],hostVars:4,hostBindings:function(ne,$e){2&ne&&r.Udp("height",$e.headerHeight)("width",$e.headerWidth)},inputs:{innerWidth:"innerWidth",headerHeight:"headerHeight",columns:"columns",offsetX:"offsetX",sorts:"sorts",sortAscendingIcon:"sortAscendingIcon",sortDescendingIcon:"sortDescendingIcon",sortUnsetIcon:"sortUnsetIcon",scrollbarH:"scrollbarH",dealsWithGroup:"dealsWithGroup",targetMarkerTemplate:"targetMarkerTemplate",sortType:"sortType",allRowsSelected:"allRowsSelected",selectionType:"selectionType",reorderable:"reorderable"},outputs:{sort:"sort",reorder:"reorder",resize:"resize",select:"select",columnContextmenu:"columnContextmenu"},decls:2,vars:4,consts:[["orderable","",1,"datatable-header-inner",3,"reorder","targetChanged"],[3,"class","ngStyle",4,"ngFor","ngForOf","ngForTrackBy"],[3,"ngStyle"],["resizeable","","long-press","","draggable","",3,"resizeEnabled","pressModel","pressEnabled","dragX","dragY","dragModel","dragEventTarget","headerHeight","isTarget","targetMarkerTemplate","targetMarkerContext","column","sortType","sorts","selectionType","sortAscendingIcon","sortDescendingIcon","sortUnsetIcon","allRowsSelected","resize","longPressStart","longPressEnd","sort","select","columnContextmenu",4,"ngFor","ngForOf","ngForTrackBy"],["resizeable","","long-press","","draggable","",3,"resizeEnabled","pressModel","pressEnabled","dragX","dragY","dragModel","dragEventTarget","headerHeight","isTarget","targetMarkerTemplate","targetMarkerContext","column","sortType","sorts","selectionType","sortAscendingIcon","sortDescendingIcon","sortUnsetIcon","allRowsSelected","resize","longPressStart","longPressEnd","sort","select","columnContextmenu"]],template:function(ne,$e){1&ne&&(r.TgZ(0,"div",0),r.NdJ("reorder",function(an){return $e.onColumnReordered(an)})("targetChanged",function(an){return $e.onTargetChanged(an)}),r.YNc(1,se,2,5,"div",1),r.qZA()),2&ne&&(r.Udp("width",$e._columnGroupWidths.total,"px"),r.xp6(1),r.Q6J("ngForOf",$e._columnsByPin)("ngForTrackBy",$e.trackByGroups))},directives:function(){return[_i,u.sg,u.PC,da,Jr,wi,Gn]},encapsulation:2,changeDetection:0}),tt})();function _o(tt,sn,ne){ne=ne||{};let $e,Lt,an,ti=null,pi=0;function xi(){pi=!1===ne.leading?0:+new Date,ti=null,an=tt.apply($e,Lt)}return function(){const ts=+new Date;!pi&&!1===ne.leading&&(pi=ts);const wo=sn-(ts-pi);return $e=this,Lt=arguments,wo<=0?(clearTimeout(ti),ti=null,pi=ts,an=tt.apply($e,Lt)):!ti&&!1!==ne.trailing&&(ti=setTimeout(xi,wo)),an}}function co(tt,sn){return function($e,Lt,an){return{configurable:!0,enumerable:an.enumerable,get:function(){return Object.defineProperty(this,Lt,{configurable:!0,enumerable:an.enumerable,value:_o(an.value,tt,sn)}),this[Lt]}}}}function ta(tt,sn){for(const ne of sn){const $e=tt.indexOf(ne);tt.splice($e,1)}}function Is(tt,sn=300){let ne=0;for(const $e of tt)ne+=$e.width||sn;return ne}var us=(()=>{return(tt=us||(us={})).asc="asc",tt.desc="desc",us;var tt})();function el(tt,sn){if(null==tt&&(tt=0),null==sn&&(sn=0),tt instanceof Date&&sn instanceof Date){if(ttsn)return 1}else if(isNaN(parseFloat(tt))||!isFinite(tt)||isNaN(parseFloat(sn))||!isFinite(sn)){if(tt=String(tt),sn=String(sn),tt.toLowerCase()sn.toLowerCase())return 1}else{if(parseFloat(tt)parseFloat(sn))return 1}return 0}let $s=(()=>{class tt{constructor(ne,$e,Lt,an,ti,pi,xi){this.scrollbarHelper=ne,this.dimensionsHelper=$e,this.cd=Lt,this.columnChangesService=pi,this.configuration=xi,this.selected=[],this.scrollbarV=!1,this.scrollbarH=!1,this.rowHeight=30,this.columnMode=Wr.standard,this.headerHeight=30,this.footerHeight=0,this.externalPaging=!1,this.externalSorting=!1,this.loadingIndicator=!1,this.reorderable=!0,this.swapColumns=!0,this.sortType=Fn.single,this.sorts=[],this.cssClasses={sortAscending:"datatable-icon-up",sortDescending:"datatable-icon-down",sortUnset:"datatable-icon-sort-unset",pagerLeftArrow:"datatable-icon-left",pagerRightArrow:"datatable-icon-right",pagerPrevious:"datatable-icon-prev",pagerNext:"datatable-icon-skip"},this.messages={emptyMessage:"No data to display",totalMessage:"total",selectedMessage:"selected"},this.groupExpansionDefault=!1,this.selectAllRowsOnPage=!1,this.virtualization=!0,this.summaryRow=!1,this.summaryHeight=30,this.summaryPosition="top",this.scroll=new r.vpe,this.activate=new r.vpe,this.select=new r.vpe,this.sort=new r.vpe,this.page=new r.vpe,this.reorder=new r.vpe,this.resize=new r.vpe,this.tableContextmenu=new r.vpe(!1),this.treeAction=new r.vpe,this.rowCount=0,this._offsetX=new e.X(0),this._count=0,this._offset=0,this._subscriptions=[],this.rowIdentity=ts=>this._groupRowsBy?ts.key:ts,this.element=an.nativeElement,this.rowDiffer=ti.find({}).create(),this.configuration&&this.configuration.messages&&(this.messages=Object.assign({},this.configuration.messages))}set rows(ne){this._rows=ne,ne&&(this._internalRows=[...ne]),this.externalSorting||this.sortInternalRows(),this._internalRows=Pr(this._internalRows,Dn(this.treeFromRelation),Dn(this.treeToRelation)),this.recalculate(),this._rows&&this._groupRowsBy&&(this.groupedRows=this.groupArrayBy(this._rows,this._groupRowsBy)),this.cd.markForCheck()}get rows(){return this._rows}set groupRowsBy(ne){ne&&(this._groupRowsBy=ne,this._rows&&this._groupRowsBy&&(this.groupedRows=this.groupArrayBy(this._rows,this._groupRowsBy)))}get groupRowsBy(){return this._groupRowsBy}set columns(ne){ne&&(this._internalColumns=[...ne],mi(this._internalColumns),this.recalculateColumns()),this._columns=ne}get columns(){return this._columns}set limit(ne){this._limit=ne,this.recalculate()}get limit(){return this._limit}set count(ne){this._count=ne,this.recalculate()}get count(){return this._count}set offset(ne){this._offset=ne}get offset(){return Math.max(Math.min(this._offset,Math.ceil(this.rowCount/this.pageSize)-1),0)}get isFixedHeader(){const ne=this.headerHeight;return"string"!=typeof ne||"auto"!==ne}get isFixedRow(){return"auto"!==this.rowHeight}get isVertScroll(){return this.scrollbarV}get isVirtualized(){return this.virtualization}get isHorScroll(){return this.scrollbarH}get isSelectable(){return void 0!==this.selectionType}get isCheckboxSelection(){return this.selectionType===dr.checkbox}get isCellSelection(){return this.selectionType===dr.cell}get isSingleSelection(){return this.selectionType===dr.single}get isMultiSelection(){return this.selectionType===dr.multi}get isMultiClickSelection(){return this.selectionType===dr.multiClick}set columnTemplates(ne){this._columnTemplates=ne,this.translateColumns(ne)}get columnTemplates(){return this._columnTemplates}get allRowsSelected(){let ne=this.rows&&this.selected&&this.selected.length===this.rows.length;if(this.bodyComponent&&this.selectAllRowsOnPage){const $e=this.bodyComponent.indexes;ne=this.selected.length===$e.last-$e.first}return this.selected&&this.rows&&0!==this.rows.length&&ne}ngOnInit(){this.recalculate()}ngAfterViewInit(){this.externalSorting||this.sortInternalRows(),"undefined"!=typeof requestAnimationFrame&&requestAnimationFrame(()=>{this.recalculate(),this.externalPaging&&this.scrollbarV&&this.page.emit({count:this.count,pageSize:this.pageSize,limit:this.limit,offset:0})})}ngAfterContentInit(){this.columnTemplates.changes.subscribe(ne=>this.translateColumns(ne)),this.listenForColumnInputChanges()}translateColumns(ne){if(ne){const $e=ne.toArray();$e.length&&(this._internalColumns=function(tt){const sn=[];for(const ne of tt){const $e={},Lt=Object.getOwnPropertyNames(ne);for(const an of Lt)$e[an]=ne[an];ne.headerTemplate&&($e.headerTemplate=ne.headerTemplate),ne.cellTemplate&&($e.cellTemplate=ne.cellTemplate),ne.summaryFunc&&($e.summaryFunc=ne.summaryFunc),ne.summaryTemplate&&($e.summaryTemplate=ne.summaryTemplate),sn.push($e)}return sn}($e),mi(this._internalColumns),this.recalculateColumns(),this.sortInternalRows(),this.cd.markForCheck())}}groupArrayBy(ne,$e){const Lt=new Map;return ne.forEach(pi=>{const xi=pi[$e];Lt.has(xi)?Lt.get(xi).push(pi):Lt.set(xi,[pi])}),Array.from(Lt,pi=>((pi,xi)=>({key:pi,value:xi}))(pi[0],pi[1]))}ngDoCheck(){this.rowDiffer.diff(this.rows)&&(this.externalSorting?this._internalRows=[...this.rows]:this.sortInternalRows(),this._internalRows=Pr(this._internalRows,Dn(this.treeFromRelation),Dn(this.treeToRelation)),this.recalculatePages(),this.cd.markForCheck())}recalculate(){this.recalculateDims(),this.recalculateColumns(),this.cd.markForCheck()}onWindowResize(){this.recalculate()}recalculateColumns(ne=this._internalColumns,$e=-1,Lt=this.scrollbarH){if(!ne)return;let an=this._innerWidth;return this.scrollbarV&&(an-=this.scrollbarHelper.width),this.columnMode===Wr.force?function(tt,sn,ne,$e,Lt=300){const an=tt.slice(ne+1,tt.length).filter(Eo=>!1!==Eo.canAutoResize);for(const Eo of an)Eo.$$oldWidth||(Eo.$$oldWidth=Eo.width);let ti=0,pi=!1,xi=Is(tt,Lt),ts=sn-xi;const wo=[];do{ti=ts/an.length,pi=xi>=sn;for(const Eo of an){if(pi&&$e)Eo.width=Eo.$$oldWidth||Eo.width||Lt;else{const ba=(Eo.width||Lt)+ti;Eo.minWidth&&baEo.maxWidth?(Eo.width=Eo.maxWidth,wo.push(Eo)):Eo.width=ba}Eo.width=Math.max(0,Eo.width)}xi=Is(tt),ts=sn-xi,ta(an,wo)}while(ts>1&&0!==an.length)}(ne,an,$e,Lt):this.columnMode===Wr.flex&&function(tt,sn){const ne=function(tt,sn){let ne=0;for(const $e of tt)ne+=$e.width;return ne}(tt),$e=function(tt){let sn=0;for(const ne of tt)sn+=ne.flexGrow||0;return sn}(tt),Lt=To(tt);ne!==sn&&function(tt,sn,ne){for(const an in tt)for(const ti of tt[an])ti.canAutoResize?ti.width=0:(sn-=ti.width,ne-=ti.flexGrow?ti.flexGrow:0);const $e={};let Lt=sn;do{const an=Lt/ne;Lt=0;for(const ti in tt)for(const pi of tt[ti])if(pi.canAutoResize&&!$e[pi.prop]){const xi=pi.width+pi.flexGrow*an;void 0!==pi.minWidth&&xi((ti=Object.assign({},ti)).$$id===ne.$$id&&(Lt=pi,ti.width=$e,ti.$$oldWidth=$e),ti));this.recalculateColumns(an,Lt),this._internalColumns=an,this.resize.emit({column:ne,newValue:$e})}onColumnReorder({column:ne,newValue:$e,prevValue:Lt}){const an=this._internalColumns.map(ti=>Object.assign({},ti));if(this.swapColumns){const ti=an[$e];an[$e]=ne,an[Lt]=ti}else if($e>Lt){const ti=an[Lt];for(let pi=Lt;pi<$e;pi++)an[pi]=an[pi+1];an[$e]=ti}else{const ti=an[Lt];for(let pi=Lt;pi>$e;pi--)an[pi]=an[pi-1];an[$e]=ti}this._internalColumns=an,this.reorder.emit({column:ne,newValue:$e,prevValue:Lt})}onColumnSort(ne){this.selectAllRowsOnPage&&(this.selected=[],this.select.emit({selected:this.selected})),this.sorts=ne.sorts,!1===this.externalSorting&&this.sortInternalRows(),this._internalRows=Pr(this._internalRows,Dn(this.treeFromRelation),Dn(this.treeToRelation)),this.offset=0,this.bodyComponent.updateOffsetY(this.offset),this.sort.emit(ne)}onHeaderSelect(ne){if(this.bodyComponent&&this.selectAllRowsOnPage){const $e=this.bodyComponent.indexes.first,Lt=this.bodyComponent.indexes.last,an=this.selected.length===Lt-$e;this.selected=[],an||this.selected.push(...this._internalRows.slice($e,Lt))}else{const $e=this.selected.length===this.rows.length;this.selected=[],$e||this.selected.push(...this.rows)}this.select.emit({selected:this.selected})}onBodySelect(ne){this.select.emit(ne)}onTreeAction(ne){const $e=ne.row,Lt=this._rows.findIndex(an=>an[this.treeToRelation]===ne.row[this.treeToRelation]);this.treeAction.emit({row:$e,rowIndex:Lt})}ngOnDestroy(){this._subscriptions.forEach(ne=>ne.unsubscribe())}listenForColumnInputChanges(){this._subscriptions.push(this.columnChangesService.columnInputChanges$.subscribe(()=>{this.columnTemplates&&this.columnTemplates.notifyOnChanges()}))}sortInternalRows(){this._internalRows=function(tt,sn,ne){if(!tt)return[];if(!ne||!ne.length||!sn)return[...tt];const $e=new Map;tt.forEach((pi,xi)=>$e.set(pi,xi));const Lt=[...tt],an=sn.reduce((pi,xi)=>(xi.comparator&&"function"==typeof xi.comparator&&(pi[xi.prop]=xi.comparator),pi),{}),ti=ne.map(pi=>{const xi=pi.prop;return{prop:xi,dir:pi.dir,valueGetter:gr(xi),compareFn:an[xi]||el}});return Lt.sort(function(pi,xi){for(const ts of ti){const{prop:wo,valueGetter:ko}=ts,Eo=ko(pi,wo),ba=ko(xi,wo),sl=ts.dir!==us.desc?ts.compareFn(Eo,ba,pi,xi,ts.dir):-ts.compareFn(Eo,ba,pi,xi,ts.dir);if(0!==sl)return sl}return $e.has(pi)&&$e.has(xi)?$e.get(pi)<$e.get(xi)?-1:1:0})}(this._internalRows,this._internalColumns,this.sorts)}}return tt.\u0275fac=function(ne){return new(ne||tt)(r.Y36(Cn,4),r.Y36(Wt,4),r.Y36(r.sBO),r.Y36(r.SBq),r.Y36(r.aQg),r.Y36(zn),r.Y36("configuration",8))},tt.\u0275cmp=r.Xpm({type:tt,selectors:[["ngx-datatable"]],contentQueries:function(ne,$e,Lt){if(1&ne&&(r.Suo(Lt,os,5),r.Suo(Lt,gn,5),r.Suo(Lt,jo,5),r.Suo(Lt,Co,4)),2&ne){let an;r.iGM(an=r.CRH())&&($e.rowDetail=an.first),r.iGM(an=r.CRH())&&($e.groupHeader=an.first),r.iGM(an=r.CRH())&&($e.footer=an.first),r.iGM(an=r.CRH())&&($e.columnTemplates=an)}},viewQuery:function(ne,$e){if(1&ne&&(r.Gf(si,5),r.Gf(Vi,5)),2&ne){let Lt;r.iGM(Lt=r.CRH())&&($e.bodyComponent=Lt.first),r.iGM(Lt=r.CRH())&&($e.headerComponent=Lt.first)}},hostAttrs:[1,"ngx-datatable"],hostVars:22,hostBindings:function(ne,$e){1&ne&&r.NdJ("resize",function(){return $e.onWindowResize()},!1,r.Jf7),2&ne&&r.ekj("fixed-header",$e.isFixedHeader)("fixed-row",$e.isFixedRow)("scroll-vertical",$e.isVertScroll)("virtualized",$e.isVirtualized)("scroll-horz",$e.isHorScroll)("selectable",$e.isSelectable)("checkbox-selection",$e.isCheckboxSelection)("cell-selection",$e.isCellSelection)("single-selection",$e.isSingleSelection)("multi-selection",$e.isMultiSelection)("multi-click-selection",$e.isMultiClickSelection)},inputs:{selected:"selected",scrollbarV:"scrollbarV",scrollbarH:"scrollbarH",rowHeight:"rowHeight",columnMode:"columnMode",headerHeight:"headerHeight",footerHeight:"footerHeight",externalPaging:"externalPaging",externalSorting:"externalSorting",loadingIndicator:"loadingIndicator",reorderable:"reorderable",swapColumns:"swapColumns",sortType:"sortType",sorts:"sorts",cssClasses:"cssClasses",messages:"messages",groupExpansionDefault:"groupExpansionDefault",selectAllRowsOnPage:"selectAllRowsOnPage",virtualization:"virtualization",summaryRow:"summaryRow",summaryHeight:"summaryHeight",summaryPosition:"summaryPosition",rowIdentity:"rowIdentity",rows:"rows",groupedRows:"groupedRows",groupRowsBy:"groupRowsBy",columns:"columns",limit:"limit",count:"count",offset:"offset",targetMarkerTemplate:"targetMarkerTemplate",selectionType:"selectionType",rowClass:"rowClass",selectCheck:"selectCheck",displayCheck:"displayCheck",trackByProp:"trackByProp",treeFromRelation:"treeFromRelation",treeToRelation:"treeToRelation"},outputs:{scroll:"scroll",activate:"activate",select:"select",sort:"sort",page:"page",reorder:"reorder",resize:"resize",tableContextmenu:"tableContextmenu",treeAction:"treeAction"},decls:5,vars:34,consts:[["visibilityObserver","",3,"visible"],[3,"sorts","sortType","scrollbarH","innerWidth","offsetX","dealsWithGroup","columns","headerHeight","reorderable","targetMarkerTemplate","sortAscendingIcon","sortDescendingIcon","sortUnsetIcon","allRowsSelected","selectionType","sort","resize","reorder","select","columnContextmenu",4,"ngIf"],[3,"groupRowsBy","groupedRows","rows","groupExpansionDefault","scrollbarV","scrollbarH","virtualization","loadingIndicator","externalPaging","rowHeight","rowCount","offset","trackByProp","columns","pageSize","offsetX","rowDetail","groupHeader","selected","innerWidth","bodyHeight","selectionType","emptyMessage","rowIdentity","rowClass","selectCheck","displayCheck","summaryRow","summaryHeight","summaryPosition","page","activate","rowContextmenu","select","scroll","treeAction"],[3,"rowCount","pageSize","offset","footerHeight","footerTemplate","totalMessage","pagerLeftArrowIcon","pagerRightArrowIcon","pagerPreviousIcon","selectedCount","selectedMessage","pagerNextIcon","page",4,"ngIf"],[3,"sorts","sortType","scrollbarH","innerWidth","offsetX","dealsWithGroup","columns","headerHeight","reorderable","targetMarkerTemplate","sortAscendingIcon","sortDescendingIcon","sortUnsetIcon","allRowsSelected","selectionType","sort","resize","reorder","select","columnContextmenu"],[3,"rowCount","pageSize","offset","footerHeight","footerTemplate","totalMessage","pagerLeftArrowIcon","pagerRightArrowIcon","pagerPreviousIcon","selectedCount","selectedMessage","pagerNextIcon","page"]],template:function(ne,$e){1&ne&&(r.TgZ(0,"div",0),r.NdJ("visible",function(){return $e.recalculate()}),r.YNc(1,Ee,2,17,"datatable-header",1),r.TgZ(2,"datatable-body",2),r.NdJ("page",function(an){return $e.onBodyPage(an)})("activate",function(an){return $e.activate.emit(an)})("rowContextmenu",function(an){return $e.onRowContextmenu(an)})("select",function(an){return $e.onBodySelect(an)})("scroll",function(an){return $e.onBodyScroll(an)})("treeAction",function(an){return $e.onTreeAction(an)}),r.ALo(3,"async"),r.qZA(),r.YNc(4,ie,1,12,"datatable-footer",3),r.qZA()),2&ne&&(r.xp6(1),r.Q6J("ngIf",$e.headerHeight),r.xp6(1),r.Q6J("groupRowsBy",$e.groupRowsBy)("groupedRows",$e.groupedRows)("rows",$e._internalRows)("groupExpansionDefault",$e.groupExpansionDefault)("scrollbarV",$e.scrollbarV)("scrollbarH",$e.scrollbarH)("virtualization",$e.virtualization)("loadingIndicator",$e.loadingIndicator)("externalPaging",$e.externalPaging)("rowHeight",$e.rowHeight)("rowCount",$e.rowCount)("offset",$e.offset)("trackByProp",$e.trackByProp)("columns",$e._internalColumns)("pageSize",$e.pageSize)("offsetX",r.lcZ(3,32,$e._offsetX))("rowDetail",$e.rowDetail)("groupHeader",$e.groupHeader)("selected",$e.selected)("innerWidth",$e._innerWidth)("bodyHeight",$e.bodyHeight)("selectionType",$e.selectionType)("emptyMessage",$e.messages.emptyMessage)("rowIdentity",$e.rowIdentity)("rowClass",$e.rowClass)("selectCheck",$e.selectCheck)("displayCheck",$e.displayCheck)("summaryRow",$e.summaryRow)("summaryHeight",$e.summaryHeight)("summaryPosition",$e.summaryPosition),r.xp6(2),r.Q6J("ngIf",$e.footerHeight))},directives:function(){return[Fr,u.O5,si,Vi,Il]},pipes:function(){return[u.Ov]},styles:[".ngx-datatable{display:block;justify-content:center;overflow:hidden;position:relative;transform:translateZ(0)}.ngx-datatable [hidden]{display:none!important}.ngx-datatable *,.ngx-datatable :after,.ngx-datatable :before{box-sizing:border-box}.ngx-datatable.scroll-vertical .datatable-body{overflow-y:auto}.ngx-datatable.scroll-vertical.virtualized .datatable-body .datatable-row-wrapper{position:absolute}.ngx-datatable.scroll-horz .datatable-body{-webkit-overflow-scrolling:touch;overflow-x:auto}.ngx-datatable.fixed-header .datatable-header .datatable-header-inner{white-space:nowrap}.ngx-datatable.fixed-header .datatable-header .datatable-header-inner .datatable-header-cell{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ngx-datatable.fixed-row .datatable-scroll,.ngx-datatable.fixed-row .datatable-scroll .datatable-body-row{white-space:nowrap}.ngx-datatable.fixed-row .datatable-scroll .datatable-body-row .datatable-body-cell,.ngx-datatable.fixed-row .datatable-scroll .datatable-body-row .datatable-body-group-cell{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ngx-datatable .datatable-body-row,.ngx-datatable .datatable-header-inner,.ngx-datatable .datatable-row-center{-o-flex-flow:row;display:flex;flex-direction:row;flex-flow:row}.ngx-datatable .datatable-body-cell,.ngx-datatable .datatable-header-cell{display:inline-block;line-height:1.625;overflow-x:hidden;vertical-align:top}.ngx-datatable .datatable-body-cell:focus,.ngx-datatable .datatable-header-cell:focus{outline:none}.ngx-datatable .datatable-row-left,.ngx-datatable .datatable-row-right{z-index:9}.ngx-datatable .datatable-row-center,.ngx-datatable .datatable-row-group,.ngx-datatable .datatable-row-left,.ngx-datatable .datatable-row-right{position:relative}.ngx-datatable .datatable-header{display:block;overflow:hidden}.ngx-datatable .datatable-header .datatable-header-inner{-webkit-align-items:stretch;align-items:stretch}.ngx-datatable .datatable-header .datatable-header-cell{display:inline-block;position:relative}.ngx-datatable .datatable-header .datatable-header-cell.sortable .datatable-header-cell-wrapper{cursor:pointer}.ngx-datatable .datatable-header .datatable-header-cell.longpress .datatable-header-cell-wrapper{cursor:move}.ngx-datatable .datatable-header .datatable-header-cell .sort-btn{cursor:pointer;display:inline-block;line-height:100%;vertical-align:middle}.ngx-datatable .datatable-header .datatable-header-cell .resize-handle,.ngx-datatable .datatable-header .datatable-header-cell .resize-handle--not-resizable{bottom:0;display:inline-block;padding:0 4px;position:absolute;right:0;top:0;visibility:hidden;width:5px}.ngx-datatable .datatable-header .datatable-header-cell .resize-handle{cursor:ew-resize}.ngx-datatable .datatable-header .datatable-header-cell.resizeable:hover .resize-handle,.ngx-datatable .datatable-header .datatable-header-cell:hover .resize-handle--not-resizable{visibility:visible}.ngx-datatable .datatable-header .datatable-header-cell .targetMarker{bottom:0;position:absolute;top:0}.ngx-datatable .datatable-header .datatable-header-cell .targetMarker.dragFromLeft{right:0}.ngx-datatable .datatable-header .datatable-header-cell .targetMarker.dragFromRight{left:0}.ngx-datatable .datatable-header .datatable-header-cell .datatable-header-cell-template-wrap{height:inherit}.ngx-datatable .datatable-body{display:block;position:relative;z-index:10}.ngx-datatable .datatable-body .datatable-scroll{display:inline-block}.ngx-datatable .datatable-body .datatable-row-detail{overflow-y:hidden}.ngx-datatable .datatable-body .datatable-row-wrapper{display:flex;flex-direction:column}.ngx-datatable .datatable-body .datatable-body-row{outline:none}.ngx-datatable .datatable-body .datatable-body-row>div{display:flex}.ngx-datatable .datatable-footer{display:block;overflow:auto;width:100%}.ngx-datatable .datatable-footer .datatable-footer-inner{align-items:center;display:flex;width:100%}.ngx-datatable .datatable-footer .selected-count .page-count{flex:1 1 40%}.ngx-datatable .datatable-footer .selected-count .datatable-pager{flex:1 1 60%}.ngx-datatable .datatable-footer .page-count{flex:1 1 20%}.ngx-datatable .datatable-footer .datatable-pager{flex:1 1 80%;text-align:right}.ngx-datatable .datatable-footer .datatable-pager .pager,.ngx-datatable .datatable-footer .datatable-pager .pager li{display:inline-block;list-style:none;margin:0;padding:0}.ngx-datatable .datatable-footer .datatable-pager .pager li,.ngx-datatable .datatable-footer .datatable-pager .pager li a{outline:none}.ngx-datatable .datatable-footer .datatable-pager .pager li a{cursor:pointer;display:inline-block}.ngx-datatable .datatable-footer .datatable-pager .pager li.disabled a{cursor:not-allowed}"],encapsulation:2,changeDetection:0}),(0,y.gn)([co(5)],tt.prototype,"onWindowResize",null),tt})(),da=(()=>{class tt{constructor(ne){this.cd=ne,this.sort=new r.vpe,this.select=new r.vpe,this.columnContextmenu=new r.vpe(!1),this.sortFn=this.onSort.bind(this),this.selectFn=this.select.emit.bind(this.select),this.cellContext={column:this.column,sortDir:this.sortDir,sortFn:this.sortFn,allRowsSelected:this.allRowsSelected,selectFn:this.selectFn}}set allRowsSelected(ne){this._allRowsSelected=ne,this.cellContext.allRowsSelected=ne}get allRowsSelected(){return this._allRowsSelected}set column(ne){this._column=ne,this.cellContext.column=ne,this.cd.markForCheck()}get column(){return this._column}set sorts(ne){this._sorts=ne,this.sortDir=this.calcSortDir(ne),this.cellContext.sortDir=this.sortDir,this.sortClass=this.calcSortClass(this.sortDir),this.cd.markForCheck()}get sorts(){return this._sorts}get columnCssClasses(){let ne="datatable-header-cell";if(this.column.sortable&&(ne+=" sortable"),this.column.resizeable&&(ne+=" resizeable"),this.column.headerClass)if("string"==typeof this.column.headerClass)ne+=" "+this.column.headerClass;else if("function"==typeof this.column.headerClass){const Lt=this.column.headerClass({column:this.column});if("string"==typeof Lt)ne+=Lt;else if("object"==typeof Lt){const an=Object.keys(Lt);for(const ti of an)!0===Lt[ti]&&(ne+=` ${ti}`)}}const $e=this.sortDir;return $e&&(ne+=` sort-active sort-${$e}`),ne}get name(){return void 0===this.column.headerTemplate?this.column.name:void 0}get minWidth(){return this.column.minWidth}get maxWidth(){return this.column.maxWidth}get width(){return this.column.width}get isCheckboxable(){return this.column.checkboxable&&this.column.headerCheckboxable&&this.selectionType===dr.checkbox}onContextmenu(ne){this.columnContextmenu.emit({event:ne,column:this.column})}ngOnInit(){this.sortClass=this.calcSortClass(this.sortDir)}calcSortDir(ne){if(ne&&this.column){const $e=ne.find(Lt=>Lt.prop===this.column.prop);if($e)return $e.dir}}onSort(){if(!this.column.sortable)return;const ne=function(tt,sn){return tt===Fn.single?sn===us.asc?us.desc:us.asc:sn?sn===us.asc?us.desc:void 0:us.asc}(this.sortType,this.sortDir);this.sort.emit({column:this.column,prevValue:this.sortDir,newValue:ne})}calcSortClass(ne){if(this.cellContext.column.sortable)return ne===us.asc?`sort-btn sort-asc ${this.sortAscendingIcon}`:ne===us.desc?`sort-btn sort-desc ${this.sortDescendingIcon}`:`sort-btn ${this.sortUnsetIcon}`}}return tt.\u0275fac=function(ne){return new(ne||tt)(r.Y36(r.sBO))},tt.\u0275cmp=r.Xpm({type:tt,selectors:[["datatable-header-cell"]],hostAttrs:[1,"datatable-header-cell"],hostVars:11,hostBindings:function(ne,$e){1&ne&&r.NdJ("contextmenu",function(an){return $e.onContextmenu(an)}),2&ne&&(r.uIk("title",$e.name),r.Tol($e.columnCssClasses),r.Udp("min-width",$e.minWidth,"px")("max-width",$e.maxWidth,"px")("width",$e.width,"px")("height",$e.headerHeight,"px"))},inputs:{allRowsSelected:"allRowsSelected",column:"column",sorts:"sorts",sortType:"sortType",sortAscendingIcon:"sortAscendingIcon",sortDescendingIcon:"sortDescendingIcon",sortUnsetIcon:"sortUnsetIcon",isTarget:"isTarget",targetMarkerTemplate:"targetMarkerTemplate",targetMarkerContext:"targetMarkerContext",selectionType:"selectionType",headerHeight:"headerHeight"},outputs:{sort:"sort",select:"select",columnContextmenu:"columnContextmenu"},decls:6,vars:6,consts:[[1,"datatable-header-cell-template-wrap"],[4,"ngIf"],["class","datatable-checkbox",4,"ngIf"],["class","datatable-header-cell-wrapper",4,"ngIf"],[3,"click"],[3,"ngTemplateOutlet","ngTemplateOutletContext"],[1,"datatable-checkbox"],["type","checkbox",3,"checked","change"],[1,"datatable-header-cell-wrapper"],[1,"datatable-header-cell-label","draggable",3,"innerHTML","click"]],template:function(ne,$e){1&ne&&(r.TgZ(0,"div",0),r.YNc(1,ge,1,2,void 0,1),r.YNc(2,De,2,1,"label",2),r.YNc(3,ce,2,1,"span",3),r.YNc(4,Ve,1,2,void 0,1),r.TgZ(5,"span",4),r.NdJ("click",function(){return $e.onSort()}),r.qZA(),r.qZA()),2&ne&&(r.xp6(1),r.Q6J("ngIf",$e.isTarget),r.xp6(1),r.Q6J("ngIf",$e.isCheckboxable),r.xp6(1),r.Q6J("ngIf",!$e.column.headerTemplate),r.xp6(1),r.Q6J("ngIf",$e.column.headerTemplate),r.xp6(1),r.Tol($e.sortClass))},directives:[u.O5,u.tP],encapsulation:2,changeDetection:0}),tt})(),Il=(()=>{class tt{constructor(){this.selectedCount=0,this.page=new r.vpe}get isVisible(){return this.rowCount/this.pageSize>1}get curPage(){return this.offset+1}}return tt.\u0275fac=function(ne){return new(ne||tt)},tt.\u0275cmp=r.Xpm({type:tt,selectors:[["datatable-footer"]],hostAttrs:[1,"datatable-footer"],inputs:{selectedCount:"selectedCount",footerHeight:"footerHeight",rowCount:"rowCount",pageSize:"pageSize",offset:"offset",pagerLeftArrowIcon:"pagerLeftArrowIcon",pagerRightArrowIcon:"pagerRightArrowIcon",pagerPreviousIcon:"pagerPreviousIcon",pagerNextIcon:"pagerNextIcon",totalMessage:"totalMessage",footerTemplate:"footerTemplate",selectedMessage:"selectedMessage"},outputs:{page:"page"},decls:4,vars:8,consts:[[1,"datatable-footer-inner",3,"ngClass"],[4,"ngIf"],["class","page-count",4,"ngIf"],[3,"pagerLeftArrowIcon","pagerRightArrowIcon","pagerPreviousIcon","pagerNextIcon","page","size","count","hidden","change",4,"ngIf"],[3,"ngTemplateOutlet","ngTemplateOutletContext"],[1,"page-count"],[3,"pagerLeftArrowIcon","pagerRightArrowIcon","pagerPreviousIcon","pagerNextIcon","page","size","count","hidden","change"]],template:function(ne,$e){1&ne&&(r.TgZ(0,"div",0),r.YNc(1,Pe,1,8,void 0,1),r.YNc(2,He,3,3,"div",2),r.YNc(3,Vt,1,8,"datatable-pager",3),r.qZA()),2&ne&&(r.Udp("height",$e.footerHeight,"px"),r.Q6J("ngClass",r.VKq(6,it,$e.selectedMessage)),r.xp6(1),r.Q6J("ngIf",$e.footerTemplate),r.xp6(1),r.Q6J("ngIf",!$e.footerTemplate),r.xp6(1),r.Q6J("ngIf",!$e.footerTemplate))},directives:function(){return[u.mk,u.O5,u.tP,fo]},encapsulation:2,changeDetection:0}),tt})(),fo=(()=>{class tt{constructor(){this.change=new r.vpe,this._count=0,this._page=1,this._size=0}set size(ne){this._size=ne,this.pages=this.calcPages()}get size(){return this._size}set count(ne){this._count=ne,this.pages=this.calcPages()}get count(){return this._count}set page(ne){this._page=ne,this.pages=this.calcPages()}get page(){return this._page}get totalPages(){const ne=this.size<1?1:Math.ceil(this.count/this.size);return Math.max(ne||0,1)}canPrevious(){return this.page>1}canNext(){return this.page0&&ne<=this.totalPages&&ne!==this.page&&(this.page=ne,this.change.emit({page:ne}))}calcPages(ne){const $e=[];let Lt=1,an=this.totalPages;ne=ne||this.page,5this.totalPages&&(Lt=Math.max(this.totalPages-5+1,1),an=this.totalPages));for(let xi=Lt;xi<=an;xi++)$e.push({number:xi,text:xi});return $e}}return tt.\u0275fac=function(ne){return new(ne||tt)},tt.\u0275cmp=r.Xpm({type:tt,selectors:[["datatable-pager"]],hostAttrs:[1,"datatable-pager"],inputs:{size:"size",count:"count",page:"page",pagerLeftArrowIcon:"pagerLeftArrowIcon",pagerRightArrowIcon:"pagerRightArrowIcon",pagerPreviousIcon:"pagerPreviousIcon",pagerNextIcon:"pagerNextIcon"},outputs:{change:"change"},decls:14,vars:21,consts:[[1,"pager"],["role","button","aria-label","go to first page","href","javascript:void(0)",3,"click"],["role","button","aria-label","go to previous page","href","javascript:void(0)",3,"click"],["role","button","class","pages",3,"active",4,"ngFor","ngForOf"],["role","button","aria-label","go to next page","href","javascript:void(0)",3,"click"],["role","button","aria-label","go to last page","href","javascript:void(0)",3,"click"],["role","button",1,"pages"],["href","javascript:void(0)",3,"click"]],template:function(ne,$e){1&ne&&(r.TgZ(0,"ul",0),r.TgZ(1,"li"),r.TgZ(2,"a",1),r.NdJ("click",function(){return $e.selectPage(1)}),r._UZ(3,"i"),r.qZA(),r.qZA(),r.TgZ(4,"li"),r.TgZ(5,"a",2),r.NdJ("click",function(){return $e.prevPage()}),r._UZ(6,"i"),r.qZA(),r.qZA(),r.YNc(7,tn,3,4,"li",3),r.TgZ(8,"li"),r.TgZ(9,"a",4),r.NdJ("click",function(){return $e.nextPage()}),r._UZ(10,"i"),r.qZA(),r.qZA(),r.TgZ(11,"li"),r.TgZ(12,"a",5),r.NdJ("click",function(){return $e.selectPage($e.totalPages)}),r._UZ(13,"i"),r.qZA(),r.qZA(),r.qZA()),2&ne&&(r.xp6(1),r.ekj("disabled",!$e.canPrevious()),r.xp6(2),r.Tol($e.pagerPreviousIcon),r.xp6(1),r.ekj("disabled",!$e.canPrevious()),r.xp6(2),r.Tol($e.pagerLeftArrowIcon),r.xp6(1),r.Q6J("ngForOf",$e.pages),r.xp6(1),r.ekj("disabled",!$e.canNext()),r.xp6(2),r.Tol($e.pagerRightArrowIcon),r.xp6(1),r.ekj("disabled",!$e.canNext()),r.xp6(2),r.Tol($e.pagerNextIcon))},directives:[u.sg],encapsulation:2,changeDetection:0}),tt})(),Ya=(()=>{class tt{}return tt.\u0275fac=function(ne){return new(ne||tt)},tt.\u0275cmp=r.Xpm({type:tt,selectors:[["datatable-progress"]],decls:3,vars:0,consts:[["role","progressbar",1,"progress-linear"],[1,"container"],[1,"bar"]],template:function(ne,$e){1&ne&&(r.TgZ(0,"div",0),r.TgZ(1,"div",1),r._UZ(2,"div",2),r.qZA(),r.qZA())},encapsulation:2,changeDetection:0}),tt})();var Ao=(()=>{return(tt=Ao||(Ao={}))[tt.up=38]="up",tt[tt.down=40]="down",tt[tt.return=13]="return",tt[tt.escape=27]="escape",tt[tt.left=37]="left",tt[tt.right=39]="right",Ao;var tt})();let fs=(()=>{class tt{constructor(ne,$e,Lt,an){this.differs=ne,this.scrollbarHelper=$e,this.cd=Lt,this.treeStatus="collapsed",this.activate=new r.vpe,this.treeAction=new r.vpe,this._groupStyles={left:{},center:{},right:{}},this._element=an.nativeElement,this._rowDiffer=ne.find({}).create()}set columns(ne){this._columns=ne,this.recalculateColumns(ne),this.buildStylesByGroup()}get columns(){return this._columns}set innerWidth(ne){if(this._columns){const $e=To(this._columns);this._columnGroupWidths=Mi($e,this._columns)}this._innerWidth=ne,this.recalculateColumns(),this.buildStylesByGroup()}get innerWidth(){return this._innerWidth}set offsetX(ne){this._offsetX=ne,this.buildStylesByGroup()}get offsetX(){return this._offsetX}get cssClass(){let ne="datatable-body-row";if(this.isSelected&&(ne+=" active"),this.rowIndex%2!=0&&(ne+=" datatable-row-odd"),this.rowIndex%2==0&&(ne+=" datatable-row-even"),this.rowClass){const $e=this.rowClass(this.row);if("string"==typeof $e)ne+=` ${$e}`;else if("object"==typeof $e){const Lt=Object.keys($e);for(const an of Lt)!0===$e[an]&&(ne+=` ${an}`)}}return ne}get columnsTotalWidths(){return this._columnGroupWidths.total}ngDoCheck(){this._rowDiffer.diff(this.row)&&this.cd.markForCheck()}trackByGroups(ne,$e){return $e.type}columnTrackingFn(ne,$e){return $e.$$id}buildStylesByGroup(){this._groupStyles.left=this.calcStylesByGroup("left"),this._groupStyles.center=this.calcStylesByGroup("center"),this._groupStyles.right=this.calcStylesByGroup("right"),this.cd.markForCheck()}calcStylesByGroup(ne){const $e=this._columnGroupWidths,Lt=this.offsetX,an={width:`${$e[ne]}px`};if("left"===ne)on(an,Lt,0);else if("right"===ne){const ti=parseInt(this.innerWidth+"",0);on(an,-1*($e.total-ti-Lt+this.scrollbarHelper.width),0)}return an}onActivate(ne,$e){ne.cellIndex=$e,ne.rowElement=this._element,this.activate.emit(ne)}onKeyDown(ne){const $e=ne.keyCode;($e===Ao.return||$e===Ao.down||$e===Ao.up||$e===Ao.left||$e===Ao.right)&&ne.target===this._element&&(ne.preventDefault(),ne.stopPropagation(),this.activate.emit({type:"keydown",event:ne,row:this.row,rowElement:this._element}))}onMouseenter(ne){this.activate.emit({type:"mouseenter",event:ne,row:this.row,rowElement:this._element})}recalculateColumns(ne=this.columns){this._columns=ne;const $e=To(this._columns);this._columnsByPin=lr(this._columns),this._columnGroupWidths=Mi($e,this._columns)}onTreeAction(){this.treeAction.emit()}}return tt.\u0275fac=function(ne){return new(ne||tt)(r.Y36(r.aQg),r.Y36(Cn,4),r.Y36(r.sBO),r.Y36(r.SBq))},tt.\u0275cmp=r.Xpm({type:tt,selectors:[["datatable-body-row"]],hostVars:6,hostBindings:function(ne,$e){1&ne&&r.NdJ("keydown",function(an){return $e.onKeyDown(an)})("mouseenter",function(an){return $e.onMouseenter(an)}),2&ne&&(r.Tol($e.cssClass),r.Udp("width",$e.columnsTotalWidths,"px")("height",$e.rowHeight,"px"))},inputs:{treeStatus:"treeStatus",columns:"columns",innerWidth:"innerWidth",offsetX:"offsetX",expanded:"expanded",rowClass:"rowClass",row:"row",group:"group",isSelected:"isSelected",rowIndex:"rowIndex",displayCheck:"displayCheck",rowHeight:"rowHeight"},outputs:{activate:"activate",treeAction:"treeAction"},decls:1,vars:2,consts:[[3,"class","ngStyle",4,"ngFor","ngForOf","ngForTrackBy"],[3,"ngStyle"],["tabindex","-1",3,"row","group","expanded","isSelected","rowIndex","column","rowHeight","displayCheck","treeStatus","activate","treeAction",4,"ngFor","ngForOf","ngForTrackBy"],["tabindex","-1",3,"row","group","expanded","isSelected","rowIndex","column","rowHeight","displayCheck","treeStatus","activate","treeAction"]],template:function(ne,$e){1&ne&&r.YNc(0,Zt,2,6,"div",0),2&ne&&r.Q6J("ngForOf",$e._columnsByPin)("ngForTrackBy",$e.trackByGroups)},directives:function(){return[u.sg,u.PC,Ra]},encapsulation:2,changeDetection:0}),tt})(),Ca=(()=>{class tt{constructor(ne,$e){this.cd=ne,this.differs=$e,this.rowContextmenu=new r.vpe(!1),this.groupContext={group:this.row,expanded:this.expanded,rowIndex:this.rowIndex},this.rowContext={row:this.row,expanded:this.expanded,rowIndex:this.rowIndex},this._expanded=!1,this.rowDiffer=$e.find({}).create()}set rowIndex(ne){this._rowIndex=ne,this.rowContext.rowIndex=ne,this.groupContext.rowIndex=ne,this.cd.markForCheck()}get rowIndex(){return this._rowIndex}set expanded(ne){this._expanded=ne,this.groupContext.expanded=ne,this.rowContext.expanded=ne,this.cd.markForCheck()}get expanded(){return this._expanded}ngDoCheck(){this.rowDiffer.diff(this.row)&&(this.rowContext.row=this.row,this.groupContext.group=this.row,this.cd.markForCheck())}onContextmenu(ne){this.rowContextmenu.emit({event:ne,row:this.row})}getGroupHeaderStyle(){const ne={};return ne.transform="translate3d("+this.offsetX+"px, 0px, 0px)",ne["backface-visibility"]="hidden",ne.width=this.innerWidth,ne}}return tt.\u0275fac=function(ne){return new(ne||tt)(r.Y36(r.sBO),r.Y36(r.aQg))},tt.\u0275cmp=r.Xpm({type:tt,selectors:[["datatable-row-wrapper"]],hostAttrs:[1,"datatable-row-wrapper"],hostBindings:function(ne,$e){1&ne&&r.NdJ("contextmenu",function(an){return $e.onContextmenu(an)})},inputs:{rowIndex:"rowIndex",expanded:"expanded",innerWidth:"innerWidth",rowDetail:"rowDetail",groupHeader:"groupHeader",offsetX:"offsetX",detailRowHeight:"detailRowHeight",row:"row",groupedRows:"groupedRows"},outputs:{rowContextmenu:"rowContextmenu"},ngContentSelectors:S,decls:3,vars:3,consts:[["class","datatable-group-header",3,"ngStyle",4,"ngIf"],[4,"ngIf"],["class","datatable-row-detail",3,"height",4,"ngIf"],[1,"datatable-group-header",3,"ngStyle"],[3,"ngTemplateOutlet","ngTemplateOutletContext"],[1,"datatable-row-detail"]],template:function(ne,$e){1&ne&&(r.F$t(),r.YNc(0,bt,2,2,"div",0),r.YNc(1,Gt,1,0,"ng-content",1),r.YNc(2,Zn,2,3,"div",2)),2&ne&&(r.Q6J("ngIf",$e.groupHeader&&$e.groupHeader.template),r.xp6(1),r.Q6J("ngIf",$e.groupHeader&&$e.groupHeader.template&&$e.expanded||!$e.groupHeader||!$e.groupHeader.template),r.xp6(1),r.Q6J("ngIf",$e.rowDetail&&$e.rowDetail.template&&$e.expanded))},directives:[u.O5,u.PC,u.tP],encapsulation:2,changeDetection:0}),tt})(),Ra=(()=>{class tt{constructor(ne,$e){this.cd=$e,this.activate=new r.vpe,this.treeAction=new r.vpe,this.isFocused=!1,this.onCheckboxChangeFn=this.onCheckboxChange.bind(this),this.activateFn=this.activate.emit.bind(this.activate),this.cellContext={onCheckboxChangeFn:this.onCheckboxChangeFn,activateFn:this.activateFn,row:this.row,group:this.group,value:this.value,column:this.column,rowHeight:this.rowHeight,isSelected:this.isSelected,rowIndex:this.rowIndex,treeStatus:this.treeStatus,onTreeAction:this.onTreeAction.bind(this)},this._element=ne.nativeElement}set group(ne){this._group=ne,this.cellContext.group=ne,this.checkValueUpdates(),this.cd.markForCheck()}get group(){return this._group}set rowHeight(ne){this._rowHeight=ne,this.cellContext.rowHeight=ne,this.checkValueUpdates(),this.cd.markForCheck()}get rowHeight(){return this._rowHeight}set isSelected(ne){this._isSelected=ne,this.cellContext.isSelected=ne,this.cd.markForCheck()}get isSelected(){return this._isSelected}set expanded(ne){this._expanded=ne,this.cellContext.expanded=ne,this.cd.markForCheck()}get expanded(){return this._expanded}set rowIndex(ne){this._rowIndex=ne,this.cellContext.rowIndex=ne,this.checkValueUpdates(),this.cd.markForCheck()}get rowIndex(){return this._rowIndex}set column(ne){this._column=ne,this.cellContext.column=ne,this.checkValueUpdates(),this.cd.markForCheck()}get column(){return this._column}set row(ne){this._row=ne,this.cellContext.row=ne,this.checkValueUpdates(),this.cd.markForCheck()}get row(){return this._row}set sorts(ne){this._sorts=ne,this.calcSortDir=this.calcSortDir(ne)}get sorts(){return this._sorts}set treeStatus(ne){this._treeStatus="collapsed"!==ne&&"expanded"!==ne&&"loading"!==ne&&"disabled"!==ne?"collapsed":ne,this.cellContext.treeStatus=this._treeStatus,this.checkValueUpdates(),this.cd.markForCheck()}get treeStatus(){return this._treeStatus}get columnCssClasses(){let ne="datatable-body-cell";if(this.column.cellClass)if("string"==typeof this.column.cellClass)ne+=" "+this.column.cellClass;else if("function"==typeof this.column.cellClass){const $e=this.column.cellClass({row:this.row,group:this.group,column:this.column,value:this.value,rowHeight:this.rowHeight});if("string"==typeof $e)ne+=" "+$e;else if("object"==typeof $e){const Lt=Object.keys($e);for(const an of Lt)!0===$e[an]&&(ne+=` ${an}`)}}return this.sortDir||(ne+=" sort-active"),this.isFocused&&(ne+=" active"),this.sortDir===us.asc&&(ne+=" sort-asc"),this.sortDir===us.desc&&(ne+=" sort-desc"),ne}get width(){return this.column.width}get minWidth(){return this.column.minWidth}get maxWidth(){return this.column.maxWidth}get height(){const ne=this.rowHeight;return isNaN(ne)?ne:ne+"px"}ngDoCheck(){this.checkValueUpdates()}ngOnDestroy(){this.cellTemplate&&this.cellTemplate.clear()}checkValueUpdates(){let ne="";if(this.row&&this.column){const $e=this.column.$$valueGetter(this.row,this.column.prop),Lt=this.column.pipe;Lt?ne=Lt.transform($e):void 0!==ne&&(ne=$e)}else ne="";this.value!==ne&&(this.value=ne,this.cellContext.value=ne,this.sanitizedValue=null!=ne?this.stripHtml(ne):ne,this.cd.markForCheck())}onFocus(){this.isFocused=!0}onBlur(){this.isFocused=!1}onClick(ne){this.activate.emit({type:"click",event:ne,row:this.row,group:this.group,rowHeight:this.rowHeight,column:this.column,value:this.value,cellElement:this._element})}onDblClick(ne){this.activate.emit({type:"dblclick",event:ne,row:this.row,group:this.group,rowHeight:this.rowHeight,column:this.column,value:this.value,cellElement:this._element})}onKeyDown(ne){const $e=ne.keyCode;($e===Ao.return||$e===Ao.down||$e===Ao.up||$e===Ao.left||$e===Ao.right)&&ne.target===this._element&&(ne.preventDefault(),ne.stopPropagation(),this.activate.emit({type:"keydown",event:ne,row:this.row,group:this.group,rowHeight:this.rowHeight,column:this.column,value:this.value,cellElement:this._element}))}onCheckboxChange(ne){this.activate.emit({type:"checkbox",event:ne,row:this.row,group:this.group,rowHeight:this.rowHeight,column:this.column,value:this.value,cellElement:this._element,treeStatus:"collapsed"})}calcSortDir(ne){if(!ne)return;const $e=ne.find(Lt=>Lt.prop===this.column.prop);return $e?$e.dir:void 0}stripHtml(ne){return ne.replace?ne.replace(/<\/?[^>]+(>|$)/g,""):ne}onTreeAction(){this.treeAction.emit(this.row)}calcLeftMargin(ne,$e){return ne.isTreeColumn?$e.level*(null!=ne.treeLevelIndent?ne.treeLevelIndent:50):0}}return tt.\u0275fac=function(ne){return new(ne||tt)(r.Y36(r.SBq),r.Y36(r.sBO))},tt.\u0275cmp=r.Xpm({type:tt,selectors:[["datatable-body-cell"]],viewQuery:function(ne,$e){if(1&ne&&r.Gf(Ur,7,r.s_b),2&ne){let Lt;r.iGM(Lt=r.CRH())&&($e.cellTemplate=Lt.first)}},hostVars:10,hostBindings:function(ne,$e){1&ne&&r.NdJ("focus",function(){return $e.onFocus()})("blur",function(){return $e.onBlur()})("click",function(an){return $e.onClick(an)})("dblclick",function(an){return $e.onDblClick(an)})("keydown",function(an){return $e.onKeyDown(an)}),2&ne&&(r.Tol($e.columnCssClasses),r.Udp("width",$e.width,"px")("min-width",$e.minWidth,"px")("max-width",$e.maxWidth,"px")("height",$e.height))},inputs:{group:"group",rowHeight:"rowHeight",isSelected:"isSelected",expanded:"expanded",rowIndex:"rowIndex",column:"column",row:"row",sorts:"sorts",treeStatus:"treeStatus",displayCheck:"displayCheck"},outputs:{activate:"activate",treeAction:"treeAction"},decls:5,vars:6,consts:[[1,"datatable-body-cell-label"],["class","datatable-checkbox",4,"ngIf"],[4,"ngIf"],[3,"title","innerHTML",4,"ngIf"],[1,"datatable-checkbox"],["type","checkbox",3,"checked","click"],["class","datatable-tree-button",3,"disabled","click",4,"ngIf"],[1,"datatable-tree-button",3,"disabled","click"],["class","icon datatable-icon-collapse",4,"ngIf"],["class","icon datatable-icon-up",4,"ngIf"],["class","icon datatable-icon-down",4,"ngIf"],[1,"icon","datatable-icon-collapse"],[1,"icon","datatable-icon-up"],[1,"icon","datatable-icon-down"],[3,"ngTemplateOutlet","ngTemplateOutletContext"],[3,"title","innerHTML"],["cellTemplate",""]],template:function(ne,$e){1&ne&&(r.TgZ(0,"div",0),r.YNc(1,di,2,1,"label",1),r.YNc(2,Yr,3,2,"ng-container",2),r.YNc(3,fi,1,2,"span",3),r.YNc(4,Hi,2,2,void 0,2),r.qZA()),2&ne&&(r.Udp("margin-left",$e.calcLeftMargin($e.column,$e.row),"px"),r.xp6(1),r.Q6J("ngIf",$e.column.checkboxable&&(!$e.displayCheck||$e.displayCheck($e.row,$e.column,$e.value))),r.xp6(1),r.Q6J("ngIf",$e.column.isTreeColumn),r.xp6(1),r.Q6J("ngIf",!$e.column.cellTemplate),r.xp6(1),r.Q6J("ngIf",$e.column.cellTemplate))},directives:[u.O5,u.tP],encapsulation:2,changeDetection:0}),tt})();function pl(tt,sn,ne){const $e=ne(sn,tt);return $e>-1?tt.splice($e,1):tt.push(sn),tt}let Po=(()=>{class tt{constructor(){this.activate=new r.vpe,this.select=new r.vpe}selectRow(ne,$e,Lt){if(!this.selectEnabled)return;const an=this.selectionType===dr.checkbox,pi=this.selectionType===dr.multiClick;let xi=[];xi=this.selectionType===dr.multi||an||pi?ne.shiftKey?function(tt,sn,ne,$e,Lt){const an=ne<$e;for(let ti=0;ti=$e&&ti<=ne,ts=ti<=$e&&ti>=ne;let wo={start:0,end:0};wo=an?{start:ne,end:$e}:{start:$e,end:ne+1},(an&&ts||!an&&xi)&&ti>=wo.start&&ti<=wo.end&&tt.push(pi)}return tt}([],this.rows,$e,this.prevIndex,this.getRowSelectedIdx.bind(this)):pl(ne.ctrlKey||ne.metaKey||pi||an?[...this.selected]:[],Lt,this.getRowSelectedIdx.bind(this)):pl([],Lt,this.getRowSelectedIdx.bind(this)),"function"==typeof this.selectCheck&&(xi=xi.filter(this.selectCheck.bind(this))),this.selected.splice(0,this.selected.length),this.selected.push(...xi),this.prevIndex=$e,this.select.emit({selected:xi})}onActivate(ne,$e){const{type:Lt,event:an,row:ti}=ne,pi=this.selectionType===dr.checkbox;!pi&&("click"===Lt||"dblclick"===Lt)||pi&&"checkbox"===Lt?this.selectRow(an,$e,ti):"keydown"===Lt&&(an.keyCode===Ao.return?this.selectRow(an,$e,ti):this.onKeyboardFocus(ne)),this.activate.emit(ne)}onKeyboardFocus(ne){const{keyCode:$e}=ne.event;if($e===Ao.up||$e===Ao.down||$e===Ao.right||$e===Ao.left){const an=this.selectionType===dr.cell;ne.cellElement&&an?an&&this.focusCell(ne.cellElement,ne.rowElement,$e,ne.cellIndex):this.focusRow(ne.rowElement,$e)}}focusRow(ne,$e){const Lt=this.getPrevNextRow(ne,$e);Lt&&Lt.focus()}getPrevNextRow(ne,$e){const Lt=ne.parentElement;if(Lt){let an;if($e===Ao.up?an=Lt.previousElementSibling:$e===Ao.down&&(an=Lt.nextElementSibling),an&&an.children.length)return an.children[0]}}focusCell(ne,$e,Lt,an){let ti;if(Lt===Ao.left)ti=ne.previousElementSibling;else if(Lt===Ao.right)ti=ne.nextElementSibling;else if(Lt===Ao.up||Lt===Ao.down){const pi=this.getPrevNextRow($e,Lt);if(pi){const xi=pi.getElementsByClassName("datatable-body-cell");xi.length&&(ti=xi[an])}}ti&&ti.focus()}getRowSelected(ne){return this.getRowSelectedIdx(ne,this.selected)>-1}getRowSelectedIdx(ne,$e){if(!$e||!$e.length)return-1;const Lt=this.rowIdentity(ne);return $e.findIndex(an=>this.rowIdentity(an)===Lt)}}return tt.\u0275fac=function(ne){return new(ne||tt)},tt.\u0275cmp=r.Xpm({type:tt,selectors:[["datatable-selection"]],inputs:{rows:"rows",selected:"selected",selectEnabled:"selectEnabled",selectionType:"selectionType",rowIdentity:"rowIdentity",selectCheck:"selectCheck"},outputs:{activate:"activate",select:"select"},ngContentSelectors:S,decls:1,vars:0,template:function(ne,$e){1&ne&&(r.F$t(),r.Hsn(0))},encapsulation:2,changeDetection:0}),tt})();function bo(tt){const sn=tt.filter(ne=>!!ne);return!sn.length||sn.some(ne=>"number"!=typeof ne)?null:sn.reduce((ne,$e)=>ne+$e)}function Ls(tt){return null}let ps=(()=>{class tt{constructor(){this.summaryRow={}}ngOnChanges(){!this.columns||!this.rows||(this.updateInternalColumns(),this.updateValues())}updateInternalColumns(){this._internalColumns=this.columns.map(ne=>Object.assign(Object.assign({},ne),{cellTemplate:ne.summaryTemplate}))}updateValues(){this.summaryRow={},this.columns.filter(ne=>!ne.summaryTemplate).forEach(ne=>{const $e=this.rows.map(an=>an[ne.prop]),Lt=this.getSummaryFunction(ne);this.summaryRow[ne.prop]=ne.pipe?ne.pipe.transform(Lt($e)):Lt($e)})}getSummaryFunction(ne){return void 0===ne.summaryFunc?bo:null===ne.summaryFunc?Ls:ne.summaryFunc}}return tt.\u0275fac=function(ne){return new(ne||tt)},tt.\u0275cmp=r.Xpm({type:tt,selectors:[["datatable-summary-row"]],hostAttrs:[1,"datatable-summary-row"],inputs:{rows:"rows",columns:"columns",rowHeight:"rowHeight",offsetX:"offsetX",innerWidth:"innerWidth"},features:[r.TTD],decls:1,vars:1,consts:[["tabindex","-1",3,"innerWidth","offsetX","columns","rowHeight","row","rowIndex",4,"ngIf"],["tabindex","-1",3,"innerWidth","offsetX","columns","rowHeight","row","rowIndex"]],template:function(ne,$e){1&ne&&r.YNc(0,Zr,1,6,"datatable-body-row",0),2&ne&&r.Q6J("ngIf",$e.summaryRow&&$e._internalColumns)},directives:[u.O5,fs],encapsulation:2}),tt})(),So=(()=>{class tt{static forRoot(ne){return{ngModule:tt,providers:[{provide:"configuration",useValue:ne}]}}}return tt.\u0275fac=function(ne){return new(ne||tt)},tt.\u0275mod=r.oAB({type:tt}),tt.\u0275inj=r.cJS({providers:[Cn,Wt,zn],imports:[[u.ez]]}),tt})();"undefined"!=typeof document&&!document.elementsFromPoint&&(document.elementsFromPoint=function(tt,sn){const ne=[],$e=[];let Lt,an,ti;for(;(Lt=document.elementFromPoint(tt,sn))&&-1===ne.indexOf(Lt)&&null!=Lt;)ne.push(Lt),$e.push({value:Lt.style.getPropertyValue("pointer-events"),priority:Lt.style.getPropertyPriority("pointer-events")}),Lt.style.setProperty("pointer-events","none","important");for(an=$e.length;ti=$e[--an];)ne[an].style.setProperty("pointer-events",ti.value?ti.value:"",ti.priority);return ne})},67506:v=>{"use strict";function T(u,p,d){u instanceof RegExp&&(u=i(u,d)),p instanceof RegExp&&(p=i(p,d));var e=r(u,p,d);return e&&{start:e[0],end:e[1],pre:d.slice(0,e[0]),body:d.slice(e[0]+u.length,e[1]),post:d.slice(e[1]+p.length)}}function i(u,p){var d=p.match(u);return d?d[0]:null}function r(u,p,d){var e,_,y,S,A,N=d.indexOf(u),L=d.indexOf(p,N+1),Z=N;if(N>=0&&L>0){if(u===p)return[N,L];for(e=[],y=d.length;Z>=0&&!A;)Z==N?(e.push(Z),N=d.indexOf(u,Z+1)):1==e.length?A=[e.pop(),L]:((_=e.pop())=0?N:L;e.length&&(A=[y,S])}return A}v.exports=T,T.range=r},96434:(v,T)=>{"use strict";T.byteLength=function(J){var K=_(J),ue=K[1];return 3*(K[0]+ue)/4-ue},T.toByteArray=function(J){var K,ie,ee=_(J),ue=ee[0],ae=ee[1],H=new u(function(J,K,ee){return 3*(K+ee)/4-ee}(0,ue,ae)),se=0,Ee=ae>0?ue-4:ue;for(ie=0;ie>16&255,H[se++]=K>>8&255,H[se++]=255&K;return 2===ae&&(K=r[J.charCodeAt(ie)]<<2|r[J.charCodeAt(ie+1)]>>4,H[se++]=255&K),1===ae&&(K=r[J.charCodeAt(ie)]<<10|r[J.charCodeAt(ie+1)]<<4|r[J.charCodeAt(ie+2)]>>2,H[se++]=K>>8&255,H[se++]=255&K),H},T.fromByteArray=function(J){for(var K,ee=J.length,ue=ee%3,ae=[],H=16383,se=0,Ee=ee-ue;seEe?Ee:se+H));return 1===ue?ae.push(i[(K=J[ee-1])>>2]+i[K<<4&63]+"=="):2===ue&&ae.push(i[(K=(J[ee-2]<<8)+J[ee-1])>>10]+i[K>>4&63]+i[K<<2&63]+"="),ae.join("")};for(var i=[],r=[],u="undefined"!=typeof Uint8Array?Uint8Array:Array,p="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",d=0,e=p.length;d0)throw new Error("Invalid string. Length must be a multiple of 4");var ee=J.indexOf("=");return-1===ee&&(ee=K),[ee,ee===K?0:4-ee%4]}function N(J){return i[J>>18&63]+i[J>>12&63]+i[J>>6&63]+i[63&J]}function L(J,K,ee){for(var ae=[],H=K;H{var r=i(2665),u=i(67506);v.exports=function(se){return se?("{}"===se.substr(0,2)&&(se="\\{\\}"+se.substr(2)),H(function(se){return se.split("\\\\").join(p).split("\\{").join(d).split("\\}").join(e).split("\\,").join(_).split("\\.").join(y)}(se),!0).map(N)):[]};var p="\0SLASH"+Math.random()+"\0",d="\0OPEN"+Math.random()+"\0",e="\0CLOSE"+Math.random()+"\0",_="\0COMMA"+Math.random()+"\0",y="\0PERIOD"+Math.random()+"\0";function S(se){return parseInt(se,10)==se?parseInt(se,10):se.charCodeAt(0)}function N(se){return se.split(p).join("\\").split(d).join("{").split(e).join("}").split(_).join(",").split(y).join(".")}function L(se){if(!se)return[""];var Ee=[],ie=u("{","}",se);if(!ie)return se.split(",");var ge=ie.body,De=ie.post,ce=ie.pre.split(",");ce[ce.length-1]+="{"+ge+"}";var lt=L(De);return De.length&&(ce[ce.length-1]+=lt.shift(),ce.push.apply(ce,lt)),Ee.push.apply(Ee,ce),Ee}function K(se){return"{"+se+"}"}function ee(se){return/^-?0\d/.test(se)}function ue(se,Ee){return se<=Ee}function ae(se,Ee){return se>=Ee}function H(se,Ee){var ie=[],he=u("{","}",se);if(!he||/\$$/.test(he.pre))return[se];var Ve,ge=/^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(he.body),De=/^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(he.body),ce=ge||De,lt=he.body.indexOf(",")>=0;if(!ce&&!lt)return he.post.match(/,.*\}/)?H(se=he.pre+"{"+he.body+e+he.post):[se];if(ce)Ve=he.body.split(/\.\./);else if(1===(Ve=L(he.body)).length&&1===(Ve=H(Ve[0],!1).map(K)).length)return(Be=he.post.length?H(he.post,!1):[""]).map(function(di){return he.pre+Ve[0]+di});var Pe,ze=he.pre,Be=he.post.length?H(he.post,!1):[""];if(ce){var je=S(Ve[0]),He=S(Ve[1]),Vt=Math.max(Ve[0].length,Ve[1].length),it=3==Ve.length?Math.abs(S(Ve[2])):1,tn=ue;He0){var Gt=new Array(bt+1).join("0");Bt=Ut<0?"-"+Gt+Bt.slice(1):Gt+Bt}}Pe.push(Bt)}}else Pe=r(Ve,function(Ur){return H(Ur,!1)});for(var xt=0;xt{"use strict";var r=i(18540),u=i(60044),p=u(r("String.prototype.indexOf"));v.exports=function(e,_){var y=r(e,!!_);return"function"==typeof y&&p(e,".prototype.")>-1?u(y):y}},60044:(v,T,i)=>{"use strict";var r=i(75396),u=i(18540),p=u("%Function.prototype.apply%"),d=u("%Function.prototype.call%"),e=u("%Reflect.apply%",!0)||r.call(d,p),_=u("%Object.getOwnPropertyDescriptor%",!0),y=u("%Object.defineProperty%",!0),S=u("%Math.max%");if(y)try{y({},"a",{value:1})}catch(N){y=null}v.exports=function(L){var Z=e(r,d,arguments);if(_&&y){var J=_(Z,"length");J.configurable&&y(Z,"length",{value:1+S(0,L.length-(arguments.length-1))})}return Z};var A=function(){return e(r,p,arguments)};y?y(v.exports,"apply",{value:A}):v.exports.apply=A},72318:v=>{var T=!("undefined"==typeof window||!window.document||!window.document.createElement);v.exports=T},6823:function(v,T,i){v.exports=function(r){"use strict";r=r&&r.hasOwnProperty("default")?r.default:r;var d={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],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],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},e=function(h,E){return h(E={exports:{}},E.exports),E.exports}(function(h){var E={};for(var R in d)d.hasOwnProperty(R)&&(E[d[R]]=R);var F=h.exports={rgb:{channels:3,labels:"rgb"},hsl:{channels:3,labels:"hsl"},hsv:{channels:3,labels:"hsv"},hwb:{channels:3,labels:"hwb"},cmyk:{channels:4,labels:"cmyk"},xyz:{channels:3,labels:"xyz"},lab:{channels:3,labels:"lab"},lch:{channels:3,labels:"lch"},hex:{channels:1,labels:["hex"]},keyword:{channels:1,labels:["keyword"]},ansi16:{channels:1,labels:["ansi16"]},ansi256:{channels:1,labels:["ansi256"]},hcg:{channels:3,labels:["h","c","g"]},apple:{channels:3,labels:["r16","g16","b16"]},gray:{channels:1,labels:["gray"]}};for(var q in F)if(F.hasOwnProperty(q)){if(!("channels"in F[q]))throw new Error("missing channels property: "+q);if(!("labels"in F[q]))throw new Error("missing channel labels property: "+q);if(F[q].labels.length!==F[q].channels)throw new Error("channel and label counts mismatch: "+q);var de=F[q].channels,ye=F[q].labels;delete F[q].channels,delete F[q].labels,Object.defineProperty(F[q],"channels",{value:de}),Object.defineProperty(F[q],"labels",{value:ye})}function Ue(Oe,Xe){return Math.pow(Oe[0]-Xe[0],2)+Math.pow(Oe[1]-Xe[1],2)+Math.pow(Oe[2]-Xe[2],2)}F.rgb.hsl=function(Oe){var ir,Pi,Xe=Oe[0]/255,gt=Oe[1]/255,yt=Oe[2]/255,nn=Math.min(Xe,gt,yt),Pn=Math.max(Xe,gt,yt),xn=Pn-nn;return Pn===nn?ir=0:Xe===Pn?ir=(gt-yt)/xn:gt===Pn?ir=2+(yt-Xe)/xn:yt===Pn&&(ir=4+(Xe-gt)/xn),(ir=Math.min(60*ir,360))<0&&(ir+=360),Pi=(nn+Pn)/2,[ir,100*(Pn===nn?0:Pi<=.5?xn/(Pn+nn):xn/(2-Pn-nn)),100*Pi]},F.rgb.hsv=function(Oe){var Xe,gt,yt,nn,Pn,xn=Oe[0]/255,ir=Oe[1]/255,Gr=Oe[2]/255,Pi=Math.max(xn,ir,Gr),Zo=Pi-Math.min(xn,ir,Gr),Lo=function(Gs){return(Pi-Gs)/6/Zo+.5};return 0===Zo?nn=Pn=0:(Pn=Zo/Pi,Xe=Lo(xn),gt=Lo(ir),yt=Lo(Gr),xn===Pi?nn=yt-gt:ir===Pi?nn=1/3+Xe-yt:Gr===Pi&&(nn=2/3+gt-Xe),nn<0?nn+=1:nn>1&&(nn-=1)),[360*nn,100*Pn,100*Pi]},F.rgb.hwb=function(Oe){var Xe=Oe[0],gt=Oe[1],yt=Oe[2];return[F.rgb.hsl(Oe)[0],1/255*Math.min(Xe,Math.min(gt,yt))*100,100*(yt=1-1/255*Math.max(Xe,Math.max(gt,yt)))]},F.rgb.cmyk=function(Oe){var ir,Xe=Oe[0]/255,gt=Oe[1]/255,yt=Oe[2]/255;return[100*((1-Xe-(ir=Math.min(1-Xe,1-gt,1-yt)))/(1-ir)||0),100*((1-gt-ir)/(1-ir)||0),100*((1-yt-ir)/(1-ir)||0),100*ir]},F.rgb.keyword=function(Oe){var Xe=E[Oe];if(Xe)return Xe;var yt,gt=1/0;for(var nn in d)if(d.hasOwnProperty(nn)){var xn=Ue(Oe,d[nn]);xn.04045?Math.pow((Xe+.055)/1.055,2.4):Xe/12.92)+.3576*(gt=gt>.04045?Math.pow((gt+.055)/1.055,2.4):gt/12.92)+.1805*(yt=yt>.04045?Math.pow((yt+.055)/1.055,2.4):yt/12.92)),100*(.2126*Xe+.7152*gt+.0722*yt),100*(.0193*Xe+.1192*gt+.9505*yt)]},F.rgb.lab=function(Oe){var Xe=F.rgb.xyz(Oe),gt=Xe[0],yt=Xe[1],nn=Xe[2];return yt/=100,nn/=108.883,gt=(gt/=95.047)>.008856?Math.pow(gt,1/3):7.787*gt+16/116,[116*(yt=yt>.008856?Math.pow(yt,1/3):7.787*yt+16/116)-16,500*(gt-yt),200*(yt-(nn=nn>.008856?Math.pow(nn,1/3):7.787*nn+16/116))]},F.hsl.rgb=function(Oe){var nn,Pn,xn,ir,Gr,Xe=Oe[0]/360,gt=Oe[1]/100,yt=Oe[2]/100;if(0===gt)return[Gr=255*yt,Gr,Gr];nn=2*yt-(Pn=yt<.5?yt*(1+gt):yt+gt-yt*gt),ir=[0,0,0];for(var Pi=0;Pi<3;Pi++)(xn=Xe+1/3*-(Pi-1))<0&&xn++,xn>1&&xn--,ir[Pi]=255*(Gr=6*xn<1?nn+6*(Pn-nn)*xn:2*xn<1?Pn:3*xn<2?nn+(Pn-nn)*(2/3-xn)*6:nn);return ir},F.hsl.hsv=function(Oe){var Xe=Oe[0],gt=Oe[1]/100,yt=Oe[2]/100,nn=gt,Pn=Math.max(yt,.01);return gt*=(yt*=2)<=1?yt:2-yt,nn*=Pn<=1?Pn:2-Pn,[Xe,100*(0===yt?2*nn/(Pn+nn):2*gt/(yt+gt)),(yt+gt)/2*100]},F.hsv.rgb=function(Oe){var Xe=Oe[0]/60,gt=Oe[1]/100,yt=Oe[2]/100,nn=Math.floor(Xe)%6,Pn=Xe-Math.floor(Xe),xn=255*yt*(1-gt),ir=255*yt*(1-gt*Pn),Gr=255*yt*(1-gt*(1-Pn));switch(yt*=255,nn){case 0:return[yt,Gr,xn];case 1:return[ir,yt,xn];case 2:return[xn,yt,Gr];case 3:return[xn,ir,yt];case 4:return[Gr,xn,yt];case 5:return[yt,xn,ir]}},F.hsv.hsl=function(Oe){var Pn,xn,ir,Xe=Oe[0],gt=Oe[1]/100,yt=Oe[2]/100,nn=Math.max(yt,.01);return ir=(2-gt)*yt,xn=gt*nn,[Xe,100*(xn=(xn/=(Pn=(2-gt)*nn)<=1?Pn:2-Pn)||0),100*(ir/=2)]},F.hwb.rgb=function(Oe){var Pn,xn,ir,Gr,Pi,Zo,Lo,Xe=Oe[0]/360,gt=Oe[1]/100,yt=Oe[2]/100,nn=gt+yt;switch(nn>1&&(gt/=nn,yt/=nn),ir=6*Xe-(Pn=Math.floor(6*Xe)),0!=(1&Pn)&&(ir=1-ir),Gr=gt+ir*((xn=1-yt)-gt),Pn){default:case 6:case 0:Pi=xn,Zo=Gr,Lo=gt;break;case 1:Pi=Gr,Zo=xn,Lo=gt;break;case 2:Pi=gt,Zo=xn,Lo=Gr;break;case 3:Pi=gt,Zo=Gr,Lo=xn;break;case 4:Pi=Gr,Zo=gt,Lo=xn;break;case 5:Pi=xn,Zo=gt,Lo=Gr}return[255*Pi,255*Zo,255*Lo]},F.cmyk.rgb=function(Oe){var gt=Oe[1]/100,yt=Oe[2]/100,nn=Oe[3]/100;return[255*(1-Math.min(1,Oe[0]/100*(1-nn)+nn)),255*(1-Math.min(1,gt*(1-nn)+nn)),255*(1-Math.min(1,yt*(1-nn)+nn))]},F.xyz.rgb=function(Oe){var nn,Pn,xn,Xe=Oe[0]/100,gt=Oe[1]/100,yt=Oe[2]/100;return Pn=-.9689*Xe+1.8758*gt+.0415*yt,xn=.0557*Xe+-.204*gt+1.057*yt,nn=(nn=3.2406*Xe+-1.5372*gt+-.4986*yt)>.0031308?1.055*Math.pow(nn,1/2.4)-.055:12.92*nn,Pn=Pn>.0031308?1.055*Math.pow(Pn,1/2.4)-.055:12.92*Pn,xn=xn>.0031308?1.055*Math.pow(xn,1/2.4)-.055:12.92*xn,[255*(nn=Math.min(Math.max(0,nn),1)),255*(Pn=Math.min(Math.max(0,Pn),1)),255*(xn=Math.min(Math.max(0,xn),1))]},F.xyz.lab=function(Oe){var Xe=Oe[0],gt=Oe[1],yt=Oe[2];return gt/=100,yt/=108.883,Xe=(Xe/=95.047)>.008856?Math.pow(Xe,1/3):7.787*Xe+16/116,[116*(gt=gt>.008856?Math.pow(gt,1/3):7.787*gt+16/116)-16,500*(Xe-gt),200*(gt-(yt=yt>.008856?Math.pow(yt,1/3):7.787*yt+16/116))]},F.lab.xyz=function(Oe){var nn,Pn,xn;nn=Oe[1]/500+(Pn=(Oe[0]+16)/116),xn=Pn-Oe[2]/200;var ir=Math.pow(Pn,3),Gr=Math.pow(nn,3),Pi=Math.pow(xn,3);return Pn=ir>.008856?ir:(Pn-16/116)/7.787,nn=Gr>.008856?Gr:(nn-16/116)/7.787,xn=Pi>.008856?Pi:(xn-16/116)/7.787,[nn*=95.047,Pn*=100,xn*=108.883]},F.lab.lch=function(Oe){var Pn,Xe=Oe[0],gt=Oe[1],yt=Oe[2];return(Pn=360*Math.atan2(yt,gt)/2/Math.PI)<0&&(Pn+=360),[Xe,Math.sqrt(gt*gt+yt*yt),Pn]},F.lch.lab=function(Oe){var xn,gt=Oe[1];return xn=Oe[2]/360*2*Math.PI,[Oe[0],gt*Math.cos(xn),gt*Math.sin(xn)]},F.rgb.ansi16=function(Oe){var Xe=Oe[0],gt=Oe[1],yt=Oe[2],nn=1 in arguments?arguments[1]:F.rgb.hsv(Oe)[2];if(0===(nn=Math.round(nn/50)))return 30;var Pn=30+(Math.round(yt/255)<<2|Math.round(gt/255)<<1|Math.round(Xe/255));return 2===nn&&(Pn+=60),Pn},F.hsv.ansi16=function(Oe){return F.rgb.ansi16(F.hsv.rgb(Oe),Oe[2])},F.rgb.ansi256=function(Oe){var Xe=Oe[0],gt=Oe[1],yt=Oe[2];return Xe===gt&>===yt?Xe<8?16:Xe>248?231:Math.round((Xe-8)/247*24)+232:16+36*Math.round(Xe/255*5)+6*Math.round(gt/255*5)+Math.round(yt/255*5)},F.ansi16.rgb=function(Oe){var Xe=Oe%10;if(0===Xe||7===Xe)return Oe>50&&(Xe+=3.5),[Xe=Xe/10.5*255,Xe,Xe];var gt=.5*(1+~~(Oe>50));return[(1&Xe)*gt*255,(Xe>>1&1)*gt*255,(Xe>>2&1)*gt*255]},F.ansi256.rgb=function(Oe){if(Oe>=232){var Xe=10*(Oe-232)+8;return[Xe,Xe,Xe]}var gt;return Oe-=16,[Math.floor(Oe/36)/5*255,Math.floor((gt=Oe%36)/6)/5*255,gt%6/5*255]},F.rgb.hex=function(Oe){var gt=(((255&Math.round(Oe[0]))<<16)+((255&Math.round(Oe[1]))<<8)+(255&Math.round(Oe[2]))).toString(16).toUpperCase();return"000000".substring(gt.length)+gt},F.hex.rgb=function(Oe){var Xe=Oe.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);if(!Xe)return[0,0,0];var gt=Xe[0];3===Xe[0].length&&(gt=gt.split("").map(function(ir){return ir+ir}).join(""));var yt=parseInt(gt,16);return[yt>>16&255,yt>>8&255,255&yt]},F.rgb.hcg=function(Oe){var Gr,Xe=Oe[0]/255,gt=Oe[1]/255,yt=Oe[2]/255,nn=Math.max(Math.max(Xe,gt),yt),Pn=Math.min(Math.min(Xe,gt),yt),xn=nn-Pn;return Gr=xn<=0?0:nn===Xe?(gt-yt)/xn%6:nn===gt?2+(yt-Xe)/xn:4+(Xe-gt)/xn+4,Gr/=6,[360*(Gr%=1),100*xn,100*(xn<1?Pn/(1-xn):0)]},F.hsl.hcg=function(Oe){var yt,Xe=Oe[1]/100,gt=Oe[2]/100,nn=0;return(yt=gt<.5?2*Xe*gt:2*Xe*(1-gt))<1&&(nn=(gt-.5*yt)/(1-yt)),[Oe[0],100*yt,100*nn]},F.hsv.hcg=function(Oe){var gt=Oe[2]/100,yt=Oe[1]/100*gt,nn=0;return yt<1&&(nn=(gt-yt)/(1-yt)),[Oe[0],100*yt,100*nn]},F.hcg.rgb=function(Oe){var gt=Oe[1]/100,yt=Oe[2]/100;if(0===gt)return[255*yt,255*yt,255*yt];var Gr,nn=[0,0,0],Pn=Oe[0]/360%1*6,xn=Pn%1,ir=1-xn;switch(Math.floor(Pn)){case 0:nn[0]=1,nn[1]=xn,nn[2]=0;break;case 1:nn[0]=ir,nn[1]=1,nn[2]=0;break;case 2:nn[0]=0,nn[1]=1,nn[2]=xn;break;case 3:nn[0]=0,nn[1]=ir,nn[2]=1;break;case 4:nn[0]=xn,nn[1]=0,nn[2]=1;break;default:nn[0]=1,nn[1]=0,nn[2]=ir}return[255*(gt*nn[0]+(Gr=(1-gt)*yt)),255*(gt*nn[1]+Gr),255*(gt*nn[2]+Gr)]},F.hcg.hsv=function(Oe){var Xe=Oe[1]/100,yt=Xe+Oe[2]/100*(1-Xe),nn=0;return yt>0&&(nn=Xe/yt),[Oe[0],100*nn,100*yt]},F.hcg.hsl=function(Oe){var Xe=Oe[1]/100,yt=Oe[2]/100*(1-Xe)+.5*Xe,nn=0;return yt>0&&yt<.5?nn=Xe/(2*yt):yt>=.5&&yt<1&&(nn=Xe/(2*(1-yt))),[Oe[0],100*nn,100*yt]},F.hcg.hwb=function(Oe){var Xe=Oe[1]/100,yt=Xe+Oe[2]/100*(1-Xe);return[Oe[0],100*(yt-Xe),100*(1-yt)]},F.hwb.hcg=function(Oe){var yt=1-Oe[2]/100,nn=yt-Oe[1]/100,Pn=0;return nn<1&&(Pn=(yt-nn)/(1-nn)),[Oe[0],100*nn,100*Pn]},F.apple.rgb=function(Oe){return[Oe[0]/65535*255,Oe[1]/65535*255,Oe[2]/65535*255]},F.rgb.apple=function(Oe){return[Oe[0]/255*65535,Oe[1]/255*65535,Oe[2]/255*65535]},F.gray.rgb=function(Oe){return[Oe[0]/100*255,Oe[0]/100*255,Oe[0]/100*255]},F.gray.hsl=F.gray.hsv=function(Oe){return[0,0,Oe[0]]},F.gray.hwb=function(Oe){return[0,100,Oe[0]]},F.gray.cmyk=function(Oe){return[0,0,0,Oe[0]]},F.gray.lab=function(Oe){return[Oe[0],0,0]},F.gray.hex=function(Oe){var Xe=255&Math.round(Oe[0]/100*255),yt=((Xe<<16)+(Xe<<8)+Xe).toString(16).toUpperCase();return"000000".substring(yt.length)+yt},F.rgb.gray=function(Oe){return[(Oe[0]+Oe[1]+Oe[2])/3/255*100]}});function he(h){var E=function(){for(var h={},E=Object.keys(e),R=E.length,F=0;F1&&(R=Array.prototype.slice.call(arguments));var F=h(R);if("object"==typeof F)for(var q=F.length,de=0;de1&&(R=Array.prototype.slice.call(arguments)),h(R))};return"conversion"in h&&(E.conversion=h.conversion),E}(q)})});var Pe=lt,je={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],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],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},He={getRgba:Vt,getHsla:it,getRgb:function(h){var E=Vt(h);return E&&E.slice(0,3)},getHsl:function(h){var E=it(h);return E&&E.slice(0,3)},getHwb:tn,getAlpha:function(h){var E=Vt(h);return E||(E=it(h))||(E=tn(h))?E[3]:void 0},hexString:function(h,E){return E=void 0!==E&&3===h.length?E:h[3],"#"+Kr(h[0])+Kr(h[1])+Kr(h[2])+(E>=0&&E<1?Kr(Math.round(255*E)):"")},rgbString:function(h,E){return E<1||h[3]&&h[3]<1?Gt(h,E):"rgb("+h[0]+", "+h[1]+", "+h[2]+")"},rgbaString:Gt,percentString:function(h,E){return E<1||h[3]&&h[3]<1?Xt(h,E):"rgb("+Math.round(h[0]/255*100)+"%, "+Math.round(h[1]/255*100)+"%, "+Math.round(h[2]/255*100)+"%)"},percentaString:Xt,hslString:function(h,E){return E<1||h[3]&&h[3]<1?Ur(h,E):"hsl("+h[0]+", "+h[1]+"%, "+h[2]+"%)"},hslaString:Ur,hwbString:function(h,E){return void 0===E&&(E=void 0!==h[3]?h[3]:1),"hwb("+h[0]+", "+h[1]+"%, "+h[2]+"%"+(void 0!==E&&1!==E?", "+E:"")+")"},keyword:function(h){return ei[h.slice(0,3)]}};function Vt(h){if(h){var ye=[0,0,0],Ue=1,Oe=h.match(/^#([a-fA-F0-9]{3,4})$/i),Xe="";if(Oe){Xe=(Oe=Oe[1])[3];for(var gt=0;gtR?(E+.05)/(R+.05):(R+.05)/(E+.05)},level:function(h){var E=this.contrast(h);return E>=7.1?"AAA":E>=4.5?"AA":""},dark:function(){var h=this.values.rgb;return(299*h[0]+587*h[1]+114*h[2])/1e3<128},light:function(){return!this.dark()},negate:function(){for(var h=[],E=0;E<3;E++)h[E]=255-this.values.rgb[E];return this.setValues("rgb",h),this},lighten:function(h){var E=this.values.hsl;return E[2]+=E[2]*h,this.setValues("hsl",E),this},darken:function(h){var E=this.values.hsl;return E[2]-=E[2]*h,this.setValues("hsl",E),this},saturate:function(h){var E=this.values.hsl;return E[1]+=E[1]*h,this.setValues("hsl",E),this},desaturate:function(h){var E=this.values.hsl;return E[1]-=E[1]*h,this.setValues("hsl",E),this},whiten:function(h){var E=this.values.hwb;return E[1]+=E[1]*h,this.setValues("hwb",E),this},blacken:function(h){var E=this.values.hwb;return E[2]+=E[2]*h,this.setValues("hwb",E),this},greyscale:function(){var h=this.values.rgb,E=.3*h[0]+.59*h[1]+.11*h[2];return this.setValues("rgb",[E,E,E]),this},clearer:function(h){var E=this.values.alpha;return this.setValues("alpha",E-E*h),this},opaquer:function(h){var E=this.values.alpha;return this.setValues("alpha",E+E*h),this},rotate:function(h){var E=this.values.hsl,R=(E[0]+h)%360;return E[0]=R<0?360+R:R,this.setValues("hsl",E),this},mix:function(h,E){var R=this,F=h,q=void 0===E?.5:E,de=2*q-1,ye=R.alpha()-F.alpha(),Ue=((de*ye==-1?de:(de+ye)/(1+de*ye))+1)/2,Oe=1-Ue;return this.rgb(Ue*R.red()+Oe*F.red(),Ue*R.green()+Oe*F.green(),Ue*R.blue()+Oe*F.blue()).alpha(R.alpha()*q+F.alpha()*(1-q))},toJSON:function(){return this.rgb()},clone:function(){var F,q,h=new $n,E=this.values,R=h.values;for(var de in E)E.hasOwnProperty(de)&&("[object Array]"===(q={}.toString.call(F=E[de]))?R[de]=F.slice(0):"[object Number]"===q?R[de]=F:console.error("unexpected color value:",F));return h}},$n.prototype.spaces={rgb:["red","green","blue"],hsl:["hue","saturation","lightness"],hsv:["hue","saturation","value"],hwb:["hue","whiteness","blackness"],cmyk:["cyan","magenta","yellow","black"]},$n.prototype.maxes={rgb:[255,255,255],hsl:[360,100,100],hsv:[360,100,100],hwb:[360,100,100],cmyk:[100,100,100,100]},$n.prototype.getValues=function(h){for(var E=this.values,R={},F=0;F=0;q--)E.call(R,h[q],q);else for(q=0;q=1?h:-(Math.sqrt(1-h*h)-1)},easeOutCirc:function(h){return Math.sqrt(1-(h-=1)*h)},easeInOutCirc:function(h){return(h/=.5)<1?-.5*(Math.sqrt(1-h*h)-1):.5*(Math.sqrt(1-(h-=2)*h)+1)},easeInElastic:function(h){var E=1.70158,R=0,F=1;return 0===h?0:1===h?1:(R||(R=.3),F<1?(F=1,E=R/4):E=R/(2*Math.PI)*Math.asin(1/F),-F*Math.pow(2,10*(h-=1))*Math.sin((h-E)*(2*Math.PI)/R))},easeOutElastic:function(h){var E=1.70158,R=0,F=1;return 0===h?0:1===h?1:(R||(R=.3),F<1?(F=1,E=R/4):E=R/(2*Math.PI)*Math.asin(1/F),F*Math.pow(2,-10*h)*Math.sin((h-E)*(2*Math.PI)/R)+1)},easeInOutElastic:function(h){var E=1.70158,R=0,F=1;return 0===h?0:2==(h/=.5)?1:(R||(R=.45),F<1?(F=1,E=R/4):E=R/(2*Math.PI)*Math.asin(1/F),h<1?F*Math.pow(2,10*(h-=1))*Math.sin((h-E)*(2*Math.PI)/R)*-.5:F*Math.pow(2,-10*(h-=1))*Math.sin((h-E)*(2*Math.PI)/R)*.5+1)},easeInBack:function(h){var E=1.70158;return h*h*((E+1)*h-E)},easeOutBack:function(h){var E=1.70158;return(h-=1)*h*((E+1)*h+E)+1},easeInOutBack:function(h){var E=1.70158;return(h/=.5)<1?h*h*((1+(E*=1.525))*h-E)*.5:.5*((h-=2)*h*((1+(E*=1.525))*h+E)+2)},easeInBounce:function(h){return 1-Hi.easeOutBounce(1-h)},easeOutBounce:function(h){return h<1/2.75?7.5625*h*h:h<2/2.75?7.5625*(h-=1.5/2.75)*h+.75:h<2.5/2.75?7.5625*(h-=2.25/2.75)*h+.9375:7.5625*(h-=2.625/2.75)*h+.984375},easeInOutBounce:function(h){return h<.5?.5*Hi.easeInBounce(2*h):.5*Hi.easeOutBounce(2*h-1)+.5}},Zr={effects:Hi};ki.easingEffects=Hi;var Cn=Math.PI,Wt=Cn/180,zn=2*Cn,rr=Cn/2,Fr=Cn/4,Gn=2*Cn/3,Jr={clear:function(h){h.ctx.clearRect(0,0,h.width,h.height)},roundedRect:function(h,E,R,F,q,de){if(de){var ye=Math.min(de,q/2,F/2),Ue=E+ye,Oe=R+ye,Xe=E+F-ye,gt=R+q-ye;h.moveTo(E,Oe),UeE.left-R&&h.xE.top-R&&h.y0&&h.requestAnimationFrame()},advance:function(){for(var E,R,F,q,h=this.animations,de=0;de=F?(Ge.callback(E.onAnimationComplete,[E],R),R.animating=!1,h.splice(de,1)):++de}},Co=Ge.options.resolve,Gi=["push","pop","shift","splice","unshift"];function jo(h,E){var R=h._chartjs;if(R){var F=R.listeners,q=F.indexOf(E);-1!==q&&F.splice(q,1),!(F.length>0)&&(Gi.forEach(function(de){delete h[de]}),delete h._chartjs)}}var To=function(h,E){this.initialize(h,E)};Ge.extend(To.prototype,{datasetElementType:null,dataElementType:null,_datasetElementOptions:["backgroundColor","borderCapStyle","borderColor","borderDash","borderDashOffset","borderJoinStyle","borderWidth"],_dataElementOptions:["backgroundColor","borderColor","borderWidth","pointStyle"],initialize:function(h,E){var R=this;R.chart=h,R.index=E,R.linkScales(),R.addElements(),R._type=R.getMeta().type},updateIndex:function(h){this.index=h},linkScales:function(){var h=this,E=h.getMeta(),R=h.chart,F=R.scales,q=h.getDataset(),de=R.options.scales;(null===E.xAxisID||!(E.xAxisID in F)||q.xAxisID)&&(E.xAxisID=q.xAxisID||de.xAxes[0].id),(null===E.yAxisID||!(E.yAxisID in F)||q.yAxisID)&&(E.yAxisID=q.yAxisID||de.yAxes[0].id)},getDataset:function(){return this.chart.data.datasets[this.index]},getMeta:function(){return this.chart.getDatasetMeta(this.index)},getScaleForId:function(h){return this.chart.scales[h]},_getValueScaleId:function(){return this.getMeta().yAxisID},_getIndexScaleId:function(){return this.getMeta().xAxisID},_getValueScale:function(){return this.getScaleForId(this._getValueScaleId())},_getIndexScale:function(){return this.getScaleForId(this._getIndexScaleId())},reset:function(){this._update(!0)},destroy:function(){this._data&&jo(this._data,this)},createMetaDataset:function(){var h=this,E=h.datasetElementType;return E&&new E({_chart:h.chart,_datasetIndex:h.index})},createMetaData:function(h){var E=this,R=E.dataElementType;return R&&new R({_chart:E.chart,_datasetIndex:E.index,_index:h})},addElements:function(){var q,de,h=this,E=h.getMeta(),R=h.getDataset().data||[],F=E.data;for(q=0,de=R.length;qF&&h.insertElements(F,q-F)},insertElements:function(h,E){for(var R=0;Rq?h.arc(ye,Ue,E.innerRadius-q,F+(de=q/E.innerRadius),R-de,!0):h.arc(ye,Ue,q,F+Math.PI/2,R-Math.PI/2),h.closePath(),h.clip()}function vr(h,E,R){var F="inner"===E.borderAlign;F?(h.lineWidth=2*E.borderWidth,h.lineJoin="round"):(h.lineWidth=E.borderWidth,h.lineJoin="bevel"),R.fullCircles&&function(h,E,R,F){var de,q=R.endAngle;for(F&&(R.endAngle=R.startAngle+li,Bn(h,R),R.endAngle=q,R.endAngle===R.startAngle&&R.fullCircles&&(R.endAngle+=li,R.fullCircles--)),h.beginPath(),h.arc(R.x,R.y,R.innerRadius,R.startAngle+li,R.startAngle,!0),de=0;deUe;)q-=li;for(;q=ye&&q<=Ue&&de>=R.innerRadius&&de<=R.outerRadius}return!1},getCenterPoint:function(){var h=this._view,E=(h.startAngle+h.endAngle)/2,R=(h.innerRadius+h.outerRadius)/2;return{x:h.x+Math.cos(E)*R,y:h.y+Math.sin(E)*R}},getArea:function(){var h=this._view;return Math.PI*((h.endAngle-h.startAngle)/(2*Math.PI))*(Math.pow(h.outerRadius,2)-Math.pow(h.innerRadius,2))},tooltipPosition:function(){var h=this._view,E=h.startAngle+(h.endAngle-h.startAngle)/2,R=(h.outerRadius-h.innerRadius)/2+h.innerRadius;return{x:h.x+Math.cos(E)*R,y:h.y+Math.sin(E)*R}},draw:function(){var q,h=this._chart.ctx,E=this._view,R="inner"===E.borderAlign?.33:0,F={x:E.x,y:E.y,innerRadius:E.innerRadius,outerRadius:Math.max(E.outerRadius-R,0),pixelMargin:R,startAngle:E.startAngle,endAngle:E.endAngle,fullCircles:Math.floor(E.circumference/li)};if(h.save(),h.fillStyle=E.backgroundColor,h.strokeStyle=E.borderColor,F.fullCircles){for(F.endAngle=F.startAngle+li,h.beginPath(),h.arc(F.x,F.y,F.outerRadius,F.startAngle,F.endAngle),h.arc(F.x,F.y,F.innerRadius,F.endAngle,F.startAngle,!0),h.closePath(),q=0;qh.x&&(E=si(E,"left","right")):h.baseR?R:de,r:q.right||ye<0?0:ye>E?E:ye,b:q.bottom||Ue<0?0:Ue>R?R:Ue,l:q.left||Oe<0?0:Oe>E?E:Oe}}function Es(h,E,R){var F=null===E,q=null===R,de=!(!h||F&&q)&&on(h);return de&&(F||E>=de.left&&E<=de.right)&&(q||R>=de.top&&R<=de.bottom)}br._set("global",{elements:{rectangle:{backgroundColor:mt,borderColor:mt,borderSkipped:"bottom",borderWidth:0}}});var Zs=ar.extend({_type:"rectangle",draw:function(){var h=this._chart.ctx,E=this._view,R=function(h){var E=on(h),R=E.right-E.left,F=E.bottom-E.top,q=_o(h,R/2,F/2);return{outer:{x:E.left,y:E.top,w:R,h:F},inner:{x:E.left+q.l,y:E.top+q.t,w:R-q.l-q.r,h:F-q.t-q.b}}}(E),F=R.outer,q=R.inner;h.fillStyle=E.backgroundColor,h.fillRect(F.x,F.y,F.w,F.h),(F.w!==q.w||F.h!==q.h)&&(h.save(),h.beginPath(),h.rect(F.x,F.y,F.w,F.h),h.clip(),h.fillStyle=E.borderColor,h.rect(q.x,q.y,q.w,q.h),h.fill("evenodd"),h.restore())},height:function(){var h=this._view;return h.base-h.y},inRange:function(h,E){return Es(this._view,h,E)},inLabelRange:function(h,E){var R=this._view;return jt(R)?Es(R,h,null):Es(R,null,E)},inXRange:function(h){return Es(this._view,h,null)},inYRange:function(h){return Es(this._view,null,h)},getCenterPoint:function(){var E,R,h=this._view;return jt(h)?(E=h.x,R=(h.y+h.base)/2):(E=(h.x+h.base)/2,R=h.y),{x:E,y:R}},getArea:function(){var h=this._view;return jt(h)?h.width*Math.abs(h.y-h.base):h.height*Math.abs(h.x-h.base)},tooltipPosition:function(){var h=this._view;return{x:h.x,y:h.y}}}),ls={},ta=Ci,Is=_t,us=Zs;ls.Arc=er,ls.Line=ta,ls.Point=Is,ls.Rectangle=us;var ya=Ge._deprecated,el=Ge.valueOrDefault;function $s(h,E,R){var Ue,Oe,F=R.barThickness,q=E.stackCount,de=E.pixels[h],ye=Ge.isNullOrUndef(F)?function(h,E){var F,q,de,ye,R=h._length;for(de=1,ye=E.length;de0?Math.min(R,Math.abs(q-F)):R,F=q;return R}(E.scale,E.pixels):-1;return Ge.isNullOrUndef(F)?(Ue=ye*R.categoryPercentage,Oe=R.barPercentage):(Ue=F*q,Oe=1),{chunk:Ue/q,ratio:Oe,start:de-Ue/2}}br._set("bar",{hover:{mode:"label"},scales:{xAxes:[{type:"category",offset:!0,gridLines:{offsetGridLines:!0}}],yAxes:[{type:"linear"}]}}),br._set("global",{datasets:{bar:{categoryPercentage:.8,barPercentage:.9}}});var Il=Mi.extend({dataElementType:ls.Rectangle,_dataElementOptions:["backgroundColor","borderColor","borderSkipped","borderWidth","barPercentage","barThickness","categoryPercentage","maxBarThickness","minBarLength"],initialize:function(){var E,R,h=this;Mi.prototype.initialize.apply(h,arguments),(E=h.getMeta()).stack=h.getDataset().stack,E.bar=!0,R=h._getIndexScale().options,ya("bar chart",R.barPercentage,"scales.[x/y]Axes.barPercentage","dataset.barPercentage"),ya("bar chart",R.barThickness,"scales.[x/y]Axes.barThickness","dataset.barThickness"),ya("bar chart",R.categoryPercentage,"scales.[x/y]Axes.categoryPercentage","dataset.categoryPercentage"),ya("bar chart",h._getValueScale().options.minBarLength,"scales.[x/y]Axes.minBarLength","dataset.minBarLength"),ya("bar chart",R.maxBarThickness,"scales.[x/y]Axes.maxBarThickness","dataset.maxBarThickness")},update:function(h){var F,q,E=this,R=E.getMeta().data;for(E._ruler=E.getRuler(),F=0,q=R.length;F=0&&Xe.min>=0?Xe.min:Xe.max,xn=void 0===Xe.start?Xe.end:Xe.max>=0&&Xe.min>=0?Xe.max-Xe.min:Xe.min-Xe.max,ir=Oe.length;if(yt||void 0===yt&&void 0!==nn)for(Gr=0;Gr=0&&dl.max>=0?dl.max:dl.min,(Xe.min<0&&Zo<0||Xe.max>=0&&Zo>0)&&(Pn+=Zo));return Lo=de.getPixelForValue(Pn),Xs=(Gs=de.getPixelForValue(Pn+xn))-Lo,void 0!==gt&&Math.abs(Xs)=0&&!ye||xn<0&&ye?Lo-gt:Lo+gt),{size:Xs,base:Lo,head:Gs,center:Gs+Xs/2}},calculateBarIndexPixels:function(h,E,R,F){var de="flex"===F.barThickness?function(h,E,R){var Oe,F=E.pixels,q=F[h],de=h>0?F[h-1]:null,ye=h=Ca?-Ra:Zo<-Ca?Ra:0)+nn,Gs=Math.cos(Zo),Xs=Math.sin(Zo),dl=Math.cos(Lo),ha=Math.sin(Lo),ia=Zo<=0&&Lo>=0||Lo>=Ra,Pa=Zo<=pl&&Lo>=pl||Lo>=Ra+pl,Eu=Zo<=-pl&&Lo>=-pl||Lo>=Ca+pl,wa=Zo===-Ca||Lo>=Ca?-1:Math.min(Gs,Gs*yt,dl,dl*yt),ou=Eu?-1:Math.min(Xs,Xs*yt,ha,ha*yt),gu=ia?1:Math.max(Gs,Gs*yt,dl,dl*yt),Nc=Pa?1:Math.max(Xs,Xs*yt,ha,ha*yt);de=(gu-wa)/2,ye=(Nc-ou)/2,Ue=-(gu+wa)/2,Oe=-(Nc+ou)/2}for(Gr=0,Pi=gt.length;Gr0&&!isNaN(h)?Ra*(Math.abs(h)/E):0},getMaxBorderWidth:function(h){var q,de,ye,Ue,Oe,Xe,gt,yt,R=0,F=this.chart;if(!h)for(q=0,de=F.data.datasets.length;q(R=(gt=Xe.borderWidth)>R?gt:R)?yt:R);return R},setHoverStyle:function(h){var E=h._model,R=h._options,F=Ge.getHoverColor;h.$previousStyle={backgroundColor:E.backgroundColor,borderColor:E.borderColor,borderWidth:E.borderWidth},E.backgroundColor=fs(R.hoverBackgroundColor,F(R.backgroundColor)),E.borderColor=fs(R.hoverBorderColor,F(R.borderColor)),E.borderWidth=fs(R.hoverBorderWidth,R.borderWidth)},_getRingWeightOffset:function(h){for(var E=0,R=0;R0&&ps(de[ye-1]._model,q)&&(Oe.controlPointPreviousX=gt(Oe.controlPointPreviousX,q.left,q.right),Oe.controlPointPreviousY=gt(Oe.controlPointPreviousY,q.top,q.bottom)),ye0&&(de=h.getDatasetMeta(de[0]._datasetIndex).data),de},"x-axis":function(h,E){return ba(h,E,{intersect:!1})},point:function(h,E){return wo(h,xi(E,h))},nearest:function(h,E,R){var F=xi(E,h);R.axis=R.axis||"xy";var q=Eo(R.axis);return ko(h,F,R.intersect,q)},x:function(h,E,R){var F=xi(E,h),q=[],de=!1;return ts(h,function(ye){ye.inXRange(F.x)&&q.push(ye),ye.inRange(F.x,F.y)&&(de=!0)}),R.intersect&&!de&&(q=[]),q},y:function(h,E,R){var F=xi(E,h),q=[],de=!1;return ts(h,function(ye){ye.inYRange(F.y)&&q.push(ye),ye.inRange(F.x,F.y)&&(de=!0)}),R.intersect&&!de&&(q=[]),q}}},tc=Ge.extend;function Nu(h,E){return Ge.where(h,function(R){return R.pos===E})}function bc(h,E){return h.sort(function(R,F){var q=E?F:R,de=E?R:F;return q.weight===de.weight?q.index-de.index:q.weight-de.weight})}function Yl(h,E,R,F){return Math.max(h[R],E[R])+Math.max(h[F],E[F])}function Fs(h,E,R){var de,ye,F=R.box,q=h.maxPadding;if(R.size&&(h[R.pos]-=R.size),R.size=R.horizontal?F.height:F.width,h[R.pos]+=R.size,F.getPadding){var Ue=F.getPadding();q.top=Math.max(q.top,Ue.top),q.left=Math.max(q.left,Ue.left),q.bottom=Math.max(q.bottom,Ue.bottom),q.right=Math.max(q.right,Ue.right)}if(de=E.outerWidth-Yl(q,h,"left","right"),ye=E.outerHeight-Yl(q,h,"top","bottom"),de!==h.w||ye!==h.h){h.w=de,h.h=ye;var Oe=R.horizontal?[de,h.w]:[ye,h.h];return!(Oe[0]===Oe[1]||isNaN(Oe[0])&&isNaN(Oe[1]))}}function iu(h,E){var de,R=E.maxPadding;return de={left:0,top:0,right:0,bottom:0},(h?["left","right"]:["top","bottom"]).forEach(function(ye){de[ye]=Math.max(E[ye],R[ye])}),de}function tl(h,E,R){var q,de,ye,Ue,Oe,Xe,F=[];for(q=0,de=h.length;q div {\r\n\tposition: absolute;\r\n\twidth: 1000000px;\r\n\theight: 1000000px;\r\n\tleft: 0;\r\n\ttop: 0;\r\n}\r\n\r\n.chartjs-size-monitor-shrink > div {\r\n\tposition: absolute;\r\n\twidth: 200%;\r\n\theight: 200%;\r\n\tleft: 0;\r\n\ttop: 0;\r\n}\r\n"})),Me="$chartjs",xe="chartjs-",Ct=xe+"size-monitor",ur=xe+"render-monitor",Go=["animationstart","webkitAnimationStart"],ms={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"};function hs(h,E){var R=Ge.getStyle(h,E),F=R&&R.match(/^(\d+)(\.\d+)?px$/);return F?Number(F[1]):void 0}var au=!!function(){var h=!1;try{var E=Object.defineProperty({},"passive",{get:function(){h=!0}});window.addEventListener("e",null,E)}catch(R){}return h}()&&{passive:!0};function Wa(h,E,R){h.addEventListener(E,R,au)}function Rl(h,E,R){h.removeEventListener(E,R,au)}function nc(h,E,R,F,q){return{type:h,chart:E,native:q||null,x:void 0!==R?R:null,y:void 0!==F?F:null}}function Lc(h){var E=document.createElement("div");return E.className=h||"",E}function kc(h,E,R){var F=h[Me]||(h[Me]={}),q=F.resizer=function(h){var E=1e6,R=Lc(Ct),F=Lc(Ct+"-expand"),q=Lc(Ct+"-shrink");F.appendChild(Lc()),q.appendChild(Lc()),R.appendChild(F),R.appendChild(q),R._reset=function(){F.scrollLeft=E,F.scrollTop=E,q.scrollLeft=E,q.scrollTop=E};var de=function(){R._reset(),h()};return Wa(F,"scroll",de.bind(F,"expand")),Wa(q,"scroll",de.bind(q,"shrink")),R}(function(h,E){var R=!1,F=[];return function(){F=Array.prototype.slice.call(arguments),E=E||this,R||(R=!0,Ge.requestAnimFrame.call(window,function(){R=!1,h.apply(E,F)}))}}(function(){if(F.resizer){var de=R.options.maintainAspectRatio&&h.parentNode,ye=de?de.clientWidth:0;E(nc("resize",R)),de&&de.clientWidth0){var de=h[0];de.label?R=de.label:de.xLabel?R=de.xLabel:q>0&&de.index-1?h.split("\n"):h}function Hu(h){var E=h._xScale,R=h._yScale||h._scale,F=h._index,q=h._datasetIndex,de=h._chart.getDatasetMeta(q).controller,ye=de._getIndexScale(),Ue=de._getValueScale();return{xLabel:E?E.getLabelForIndex(F,q):"",yLabel:R?R.getLabelForIndex(F,q):"",label:ye?""+ye.getLabelForIndex(F,q):"",value:Ue?""+Ue.getLabelForIndex(F,q):"",index:F,datasetIndex:q,x:h._model.x,y:h._model.y}}function Uu(h){var E=br.global;return{xPadding:h.xPadding,yPadding:h.yPadding,xAlign:h.xAlign,yAlign:h.yAlign,rtl:h.rtl,textDirection:h.textDirection,bodyFontColor:h.bodyFontColor,_bodyFontFamily:yl(h.bodyFontFamily,E.defaultFontFamily),_bodyFontStyle:yl(h.bodyFontStyle,E.defaultFontStyle),_bodyAlign:h.bodyAlign,bodyFontSize:yl(h.bodyFontSize,E.defaultFontSize),bodySpacing:h.bodySpacing,titleFontColor:h.titleFontColor,_titleFontFamily:yl(h.titleFontFamily,E.defaultFontFamily),_titleFontStyle:yl(h.titleFontStyle,E.defaultFontStyle),titleFontSize:yl(h.titleFontSize,E.defaultFontSize),_titleAlign:h.titleAlign,titleSpacing:h.titleSpacing,titleMarginBottom:h.titleMarginBottom,footerFontColor:h.footerFontColor,_footerFontFamily:yl(h.footerFontFamily,E.defaultFontFamily),_footerFontStyle:yl(h.footerFontStyle,E.defaultFontStyle),footerFontSize:yl(h.footerFontSize,E.defaultFontSize),_footerAlign:h.footerAlign,footerSpacing:h.footerSpacing,footerMarginTop:h.footerMarginTop,caretSize:h.caretSize,cornerRadius:h.cornerRadius,backgroundColor:h.backgroundColor,opacity:0,legendColorBackground:h.multiKeyBackground,displayColors:h.displayColors,borderColor:h.borderColor,borderWidth:h.borderWidth}}function fa(h,E){return"center"===E?h.x+h.width/2:"right"===E?h.x+h.width-h.xPadding:h.x+h.xPadding}function pu(h){return lu([],js(h))}var bl=ar.extend({initialize:function(){this._model=Uu(this._options),this._lastActive=[]},getTitle:function(){var h=this,R=h._options.callbacks,F=R.beforeTitle.apply(h,arguments),q=R.title.apply(h,arguments),de=R.afterTitle.apply(h,arguments),ye=[];return ye=lu(ye,js(F)),ye=lu(ye,js(q)),lu(ye,js(de))},getBeforeBody:function(){return pu(this._options.callbacks.beforeBody.apply(this,arguments))},getBody:function(h,E){var R=this,F=R._options.callbacks,q=[];return Ge.each(h,function(de){var ye={before:[],lines:[],after:[]};lu(ye.before,js(F.beforeLabel.call(R,de,E))),lu(ye.lines,F.label.call(R,de,E)),lu(ye.after,js(F.afterLabel.call(R,de,E))),q.push(ye)}),q},getAfterBody:function(){return pu(this._options.callbacks.afterBody.apply(this,arguments))},getFooter:function(){var h=this,E=h._options.callbacks,R=E.beforeFooter.apply(h,arguments),F=E.footer.apply(h,arguments),q=E.afterFooter.apply(h,arguments),de=[];return de=lu(de,js(R)),de=lu(de,js(F)),lu(de,js(q))},update:function(h){var yt,nn,E=this,R=E._options,F=E._model,q=E._model=Uu(R),de=E._active,ye=E._data,Ue={xAlign:F.xAlign,yAlign:F.yAlign},Oe={x:F.x,y:F.y},Xe={width:F.width,height:F.height},gt={x:F.caretX,y:F.caretY};if(de.length){q.opacity=1;var Pn=[],xn=[];gt=yu[R.position].call(E,de,E._eventPosition);var ir=[];for(yt=0,nn=de.length;ytF.width&&(q=F.width-E.width),q<0&&(q=0)),"top"===gt?de+=yt:de-="bottom"===gt?E.height+yt:E.height/2,"center"===gt?"left"===Xe?q+=yt:"right"===Xe&&(q-=yt):"left"===Xe?q-=nn:"right"===Xe&&(q+=nn),{x:q,y:de}}(q,Xe=function(h,E){var R=h._chart.ctx,F=2*E.yPadding,q=0,de=E.body,ye=de.reduce(function(xn,ir){return xn+ir.before.length+ir.lines.length+ir.after.length},0),Ue=E.title.length,Oe=E.footer.length,Xe=E.titleFontSize,gt=E.bodyFontSize,yt=E.footerFontSize;F+=Ue*Xe,F+=Ue?(Ue-1)*E.titleSpacing:0,F+=Ue?E.titleMarginBottom:0,F+=(ye+=E.beforeBody.length+E.afterBody.length)*gt,F+=ye?(ye-1)*E.bodySpacing:0,F+=Oe?E.footerMarginTop:0,F+=Oe*yt,F+=Oe?(Oe-1)*E.footerSpacing:0;var nn=0,Pn=function(xn){q=Math.max(q,R.measureText(xn).width+nn)};return R.font=Ge.fontString(Xe,E._titleFontStyle,E._titleFontFamily),Ge.each(E.title,Pn),R.font=Ge.fontString(gt,E._bodyFontStyle,E._bodyFontFamily),Ge.each(E.beforeBody.concat(E.afterBody),Pn),nn=E.displayColors?gt+2:0,Ge.each(de,function(xn){Ge.each(xn.before,Pn),Ge.each(xn.lines,Pn),Ge.each(xn.after,Pn)}),nn=0,R.font=Ge.fontString(yt,E._footerFontStyle,E._footerFontFamily),Ge.each(E.footer,Pn),{width:q+=2*E.xPadding,height:F}}(this,q),Ue=function(h,E){var R=h._model,F=h._chart,q=h._chart.chartArea,de="center",ye="center";R.yF.height-E.height&&(ye="bottom");var Ue,Oe,Xe,gt,yt,nn=(q.left+q.right)/2,Pn=(q.top+q.bottom)/2;"center"===ye?(Ue=function(ir){return ir<=nn},Oe=function(ir){return ir>nn}):(Ue=function(ir){return ir<=E.width/2},Oe=function(ir){return ir>=F.width-E.width/2}),Xe=function(ir){return ir+E.width+R.caretSize+R.caretPadding>F.width},gt=function(ir){return ir-E.width-R.caretSize-R.caretPadding<0},yt=function(ir){return ir<=Pn?"top":"bottom"},Ue(R.x)?(de="left",Xe(R.x)&&(de="center",ye=yt(R.y))):Oe(R.x)&&(de="right",gt(R.x)&&(de="center",ye=yt(R.y)));var xn=h._options;return{xAlign:xn.xAlign?xn.xAlign:de,yAlign:xn.yAlign?xn.yAlign:ye}}(this,Xe),E._chart)}else q.opacity=0;return q.xAlign=Ue.xAlign,q.yAlign=Ue.yAlign,q.x=Oe.x,q.y=Oe.y,q.width=Xe.width,q.height=Xe.height,q.caretX=gt.x,q.caretY=gt.y,E._model=q,h&&R.custom&&R.custom.call(E,q),E},drawCaret:function(h,E){var R=this._chart.ctx,q=this.getCaretPosition(h,E,this._view);R.lineTo(q.x1,q.y1),R.lineTo(q.x2,q.y2),R.lineTo(q.x3,q.y3)},getCaretPosition:function(h,E,R){var F,q,de,ye,Ue,Oe,Xe=R.caretSize,gt=R.cornerRadius,yt=R.xAlign,nn=R.yAlign,Pn=h.x,xn=h.y,ir=E.width,Gr=E.height;if("center"===nn)Ue=xn+Gr/2,"left"===yt?(q=(F=Pn)-Xe,de=F,ye=Ue+Xe,Oe=Ue-Xe):(q=(F=Pn+ir)+Xe,de=F,ye=Ue-Xe,Oe=Ue+Xe);else if("left"===yt?(F=(q=Pn+gt+Xe)-Xe,de=q+Xe):"right"===yt?(F=(q=Pn+ir-gt-Xe)-Xe,de=q+Xe):(F=(q=R.caretX)-Xe,de=q+Xe),"top"===nn)Ue=(ye=xn)-Xe,Oe=ye;else{Ue=(ye=xn+Gr)+Xe,Oe=ye;var Pi=de;de=F,F=Pi}return{x1:F,x2:q,x3:de,y1:ye,y2:Ue,y3:Oe}},drawTitle:function(h,E,R){var de,ye,Ue,F=E.title,q=F.length;if(q){var Oe=Mu(E.rtl,E.x,E.width);for(h.x=fa(E,E._titleAlign),R.textAlign=Oe.textAlign(E._titleAlign),R.textBaseline="middle",de=E.titleFontSize,ye=E.titleSpacing,R.fillStyle=E.titleFontColor,R.font=Ge.fontString(de,E._titleFontStyle,E._titleFontFamily),Ue=0;Ue0&&R.stroke()},draw:function(){var h=this._chart.ctx,E=this._view;if(0!==E.opacity){var R={width:E.width,height:E.height},F={x:E.x,y:E.y},q=Math.abs(E.opacity<.001)?0:E.opacity;this._options.enabled&&(E.title.length||E.beforeBody.length||E.body.length||E.afterBody.length||E.footer.length)&&(h.save(),h.globalAlpha=q,this.drawBackground(F,E,h,R),F.y+=E.yPadding,Ge.rtl.overrideTextDirection(h,E.textDirection),this.drawTitle(F,E,h),this.drawBody(F,E,h),this.drawFooter(F,E,h),Ge.rtl.restoreTextDirection(h,E.textDirection),h.restore())}},handleEvent:function(h){var F,E=this,R=E._options;return E._lastActive=E._lastActive||[],"mouseout"===h.type?E._active=[]:(E._active=E._chart.getElementsAtEventForMode(h,R.mode,R),R.reverse&&E._active.reverse()),(F=!Ge.arrayEquals(E._active,E._lastActive))&&(E._lastActive=E._active,(R.enabled||R.custom)&&(E._eventPosition={x:h.x,y:h.y},E.update(!0),E.pivot())),F}});bl.positioners=yu;var El=Ge.valueOrDefault;function Ul(){return Ge.merge(Object.create(null),[].slice.call(arguments),{merger:function(h,E,R,F){if("xAxes"===h||"yAxes"===h){var de,ye,Ue,q=R[h].length;for(E[h]||(E[h]=[]),de=0;de=E[h].length&&E[h].push({}),Ge.merge(E[h][de],!E[h][de].type||Ue.type&&Ue.type!==E[h][de].type?[$u.getScaleDefaults(ye),Ue]:Ue)}else Ge._merger(h,E,R,F)}})}function ks(){return Ge.merge(Object.create(null),[].slice.call(arguments),{merger:function(h,E,R,F){var q=E[h]||Object.create(null),de=R[h];"scales"===h?E[h]=Ul(q,de):"scale"===h?E[h]=Ge.merge(q,[$u.getScaleDefaults(de.type),de]):Ge._merger(h,E,R,F)}})}function Ae(h){var E=h.options;Ge.each(h.scales,function(R){Zl.removeBox(h,R)}),E=ks(br.global,br[h.config.type],E),h.options=h.config.options=E,h.ensureScalesHaveIDs(),h.buildOrUpdateScales(),h.tooltip._options=E.tooltips,h.tooltip.initialize()}function st(h,E,R){var F,q=function(de){return de.id===F};do{F=E+R++}while(Ge.findIndex(h,q)>=0);return F}function vt(h){return"top"===h||"bottom"===h}function ut(h,E){return function(R,F){return R[h]===F[h]?R[E]-F[E]:R[h]-F[h]}}br._set("global",{elements:{},events:["mousemove","mouseout","click","touchstart","touchmove"],hover:{onHover:null,mode:"nearest",intersect:!0,animationDuration:400},onClick:null,maintainAspectRatio:!0,responsive:!0,responsiveAnimationDuration:0});var un=function(h,E){return this.construct(h,E),this};Ge.extend(un.prototype,{construct:function(h,E){var R=this;E=function(h){var E=(h=h||Object.create(null)).data=h.data||{};return E.datasets=E.datasets||[],E.labels=E.labels||[],h.options=ks(br.global,br[h.type],h.options||{}),h}(E);var F=Aa.acquireContext(h,E),q=F&&F.canvas,de=q&&q.height,ye=q&&q.width;R.id=Ge.uid(),R.ctx=F,R.canvas=q,R.config=E,R.width=ye,R.height=de,R.aspectRatio=de?ye/de:null,R.options=E.options,R._bufferedRender=!1,R._layers=[],R.chart=R,R.controller=R,un.instances[R.id]=R,Object.defineProperty(R,"data",{get:function(){return R.config.data},set:function(Ue){R.config.data=Ue}}),F&&q?(R.initialize(),R.update()):console.error("Failed to create chart: can't acquire context from the given item")},initialize:function(){var h=this;return Ss.notify(h,"beforeInit"),Ge.retinaScale(h,h.options.devicePixelRatio),h.bindEvents(),h.options.responsive&&h.resize(!0),h.initToolTip(),Ss.notify(h,"afterInit"),h},clear:function(){return Ge.canvas.clear(this),this},stop:function(){return vo.cancelAnimation(this),this},resize:function(h){var E=this,R=E.options,F=E.canvas,q=R.maintainAspectRatio&&E.aspectRatio||null,de=Math.max(0,Math.floor(Ge.getMaximumWidth(F))),ye=Math.max(0,Math.floor(q?de/q:Ge.getMaximumHeight(F)));if((E.width!==de||E.height!==ye)&&(F.width=E.width=de,F.height=E.height=ye,F.style.width=de+"px",F.style.height=ye+"px",Ge.retinaScale(E,R.devicePixelRatio),!h)){var Ue={width:de,height:ye};Ss.notify(E,"resize",[Ue]),R.onResize&&R.onResize(E,Ue),E.stop(),E.update({duration:R.responsiveAnimationDuration})}},ensureScalesHaveIDs:function(){var h=this.options,E=h.scales||{},R=h.scale;Ge.each(E.xAxes,function(F,q){F.id||(F.id=st(E.xAxes,"x-axis-",q))}),Ge.each(E.yAxes,function(F,q){F.id||(F.id=st(E.yAxes,"y-axis-",q))}),R&&(R.id=R.id||"scale")},buildOrUpdateScales:function(){var h=this,E=h.options,R=h.scales||{},F=[],q=Object.keys(R).reduce(function(de,ye){return de[ye]=!1,de},{});E.scales&&(F=F.concat((E.scales.xAxes||[]).map(function(de){return{options:de,dtype:"category",dposition:"bottom"}}),(E.scales.yAxes||[]).map(function(de){return{options:de,dtype:"linear",dposition:"left"}}))),E.scale&&F.push({options:E.scale,dtype:"radialLinear",isDefault:!0,dposition:"chartArea"}),Ge.each(F,function(de){var ye=de.options,Ue=ye.id,Oe=El(ye.type,de.dtype);vt(ye.position)!==vt(de.dposition)&&(ye.position=de.dposition),q[Ue]=!0;var Xe=null;if(Ue in R&&R[Ue].type===Oe)(Xe=R[Ue]).options=ye,Xe.ctx=h.ctx,Xe.chart=h;else{var gt=$u.getScaleConstructor(Oe);if(!gt)return;Xe=new gt({id:Ue,type:Oe,options:ye,ctx:h.ctx,chart:h}),R[Xe.id]=Xe}Xe.mergeTicksOptions(),de.isDefault&&(h.scale=Xe)}),Ge.each(q,function(de,ye){de||delete R[ye]}),h.scales=R,$u.addScalesToLayout(this)},buildOrUpdateControllers:function(){var F,q,h=this,E=[],R=h.data.datasets;for(F=0,q=R.length;F=0;--F)E.drawDataset(R[F],h);Ss.notify(E,"afterDatasetsDraw",[h])}},drawDataset:function(h,E){var F={meta:h,index:h.index,easingValue:E};!1!==Ss.notify(this,"beforeDatasetDraw",[F])&&(h.controller.draw(E),Ss.notify(this,"afterDatasetDraw",[F]))},_drawTooltip:function(h){var E=this,R=E.tooltip,F={tooltip:R,easingValue:h};!1!==Ss.notify(E,"beforeTooltipDraw",[F])&&(R.draw(),Ss.notify(E,"afterTooltipDraw",[F]))},getElementAtEvent:function(h){return sl.modes.single(this,h)},getElementsAtEvent:function(h){return sl.modes.label(this,h,{intersect:!0})},getElementsAtXAxis:function(h){return sl.modes["x-axis"](this,h,{intersect:!0})},getElementsAtEventForMode:function(h,E,R){var F=sl.modes[E];return"function"==typeof F?F(this,h,R):[]},getDatasetAtEvent:function(h){return sl.modes.dataset(this,h,{intersect:!0})},getDatasetMeta:function(h){var E=this,R=E.data.datasets[h];R._meta||(R._meta={});var F=R._meta[E.id];return F||(F=R._meta[E.id]={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:R.order||0,index:h}),F},getVisibleDatasetCount:function(){for(var h=0,E=0,R=this.data.datasets.length;E3?R[2]-R[1]:R[1]-R[0];Math.abs(F)>1&&h!==Math.floor(h)&&(F=h-Math.floor(h));var q=Ge.log10(Math.abs(F)),de="";if(0!==h)if(Math.max(Math.abs(R[0]),Math.abs(R[R.length-1]))<1e-4){var Ue=Ge.log10(Math.abs(h)),Oe=Math.floor(Ue)-Math.floor(q);Oe=Math.max(Math.min(Oe,20),0),de=h.toExponential(Oe)}else{var Xe=-1*Math.floor(q);Xe=Math.max(Math.min(Xe,20),0),de=h.toFixed(Xe)}else de="0";return de},logarithmic:function(h,E,R){var F=h/Math.pow(10,Math.floor(Ge.log10(h)));return 0===h?"0":1===F||2===F||5===F||0===E||E===R.length-1?h.toExponential():""}}},In=Ge.isArray,Cr=Ge.isNullOrUndef,hr=Ge.valueOrDefault,ao=Ge.valueAtIndexOrDefault;function Na(h,E,R){var Xe,F=h.getTicks().length,q=Math.min(E,F-1),de=h.getPixelForTick(q),ye=h._startPixel,Ue=h._endPixel;if(!(R&&(Xe=1===F?Math.max(de-ye,Ue-de):0===E?(h.getPixelForTick(1)-de)/2:(de-h.getPixelForTick(q-1))/2,de+=qUe+1e-6)))return de}function Qs(h,E,R,F){var gt,yt,nn,Pn,xn,ir,Gr,Pi,Zo,Lo,Gs,Xs,dl,q=R.length,de=[],ye=[],Ue=[],Oe=0,Xe=0;for(gt=0;gtE){for(de=0;de=de||F<=1||!h.isHorizontal()?h.labelRotation=q:(Oe=(Ue=h._getLabelSizes()).widest.width,Xe=Ue.highest.height-Ue.highest.offset,gt=Math.min(h.maxWidth,h.chart.width-Oe),Oe+6>(yt=E.offset?h.maxWidth/F:gt/(F-1))&&(yt=gt/(F-(E.offset?.5:1)),nn=h.maxHeight-ed(E.gridLines)-R.padding-$c(E.scaleLabel),Pn=Math.sqrt(Oe*Oe+Xe*Xe),ye=Ge.toDegrees(Math.min(Math.asin(Math.min((Ue.highest.height+6)/yt,1)),Math.asin(Math.min(nn/Pn,1))-Math.asin(Xe/Pn))),ye=Math.max(q,Math.min(de,ye))),h.labelRotation=ye)},afterCalculateTickRotation:function(){Ge.callback(this.options.afterCalculateTickRotation,[this])},beforeFit:function(){Ge.callback(this.options.beforeFit,[this])},fit:function(){var h=this,E=h.minSize={width:0,height:0},R=h.chart,F=h.options,q=F.ticks,de=F.scaleLabel,ye=F.gridLines,Ue=h._isVisible(),Oe="bottom"===F.position,Xe=h.isHorizontal();if(Xe?E.width=h.maxWidth:Ue&&(E.width=ed(ye)+$c(de)),Xe?Ue&&(E.height=ed(ye)+$c(de)):E.height=h.maxHeight,q.display&&Ue){var gt=$i(q),yt=h._getLabelSizes(),nn=yt.first,Pn=yt.last,xn=yt.widest,ir=yt.highest,Gr=.4*gt.minor.lineHeight,Pi=q.padding;if(Xe){var Zo=0!==h.labelRotation,Lo=Ge.toRadians(h.labelRotation),Gs=Math.cos(Lo),Xs=Math.sin(Lo);E.height=Math.min(h.maxHeight,E.height+(Xs*xn.width+Gs*(ir.height-(Zo?ir.offset:0))+(Zo?0:Gr))+Pi);var Pa,il,ha=h.getPixelForTick(0)-h.left,ia=h.right-h.getPixelForTick(h.getTicks().length-1);Zo?(Pa=Oe?Gs*nn.width+Xs*nn.offset:Xs*(nn.height-nn.offset),il=Oe?Xs*(Pn.height-Pn.offset):Gs*Pn.width+Xs*Pn.offset):(Pa=nn.width/2,il=Pn.width/2),h.paddingLeft=Math.max((Pa-ha)*h.width/(h.width-ha),0)+3,h.paddingRight=Math.max((il-ia)*h.width/(h.width-ia),0)+3}else E.width=Math.min(h.maxWidth,E.width+(q.mirror?0:xn.width+Pi+Gr)),h.paddingTop=nn.height/2,h.paddingBottom=Pn.height/2}h.handleMargins(),Xe?(h.width=h._length=R.width-h.margins.left-h.margins.right,h.height=E.height):(h.width=E.width,h.height=h._length=R.height-h.margins.top-h.margins.bottom)},handleMargins:function(){var h=this;h.margins&&(h.margins.left=Math.max(h.paddingLeft,h.margins.left),h.margins.top=Math.max(h.paddingTop,h.margins.top),h.margins.right=Math.max(h.paddingRight,h.margins.right),h.margins.bottom=Math.max(h.paddingBottom,h.margins.bottom))},afterFit:function(){Ge.callback(this.options.afterFit,[this])},isHorizontal:function(){var h=this.options.position;return"top"===h||"bottom"===h},isFullWidth:function(){return this.options.fullWidth},getRightValue:function(h){if(Cr(h))return NaN;if(("number"==typeof h||h instanceof Number)&&!isFinite(h))return NaN;if(h)if(this.isHorizontal()){if(void 0!==h.x)return this.getRightValue(h.x)}else if(void 0!==h.y)return this.getRightValue(h.y);return h},_convertTicksToLabels:function(h){var R,F,q,E=this;for(E.ticks=h.map(function(de){return de.value}),E.beforeTickToLabelConversion(),R=E.convertTicksToLabels(h)||E.ticks,E.afterTickToLabelConversion(),F=0,q=h.length;FF-1?null:E.getPixelForDecimal(h*q+(R?q/2:0))},getPixelForDecimal:function(h){var E=this;return E._reversePixels&&(h=1-h),E._startPixel+h*E._length},getDecimalForPixel:function(h){var E=(h-this._startPixel)/this._length;return this._reversePixels?1-E:E},getBasePixel:function(){return this.getPixelForValue(this.getBaseValue())},getBaseValue:function(){var h=this,E=h.min,R=h.max;return h.beginAtZero?0:E<0&&R<0?R:E>0&&R>0?E:0},_autoSkip:function(h){var Xe,gt,yt,nn,E=this,R=E.options.ticks,F=E._length,q=R.maxTicksLimit||F/E._tickSize()+1,de=R.major.enabled?function(h){var R,F,E=[];for(R=0,F=h.length;Rq)return function(h,E,R){var de,ye,F=0,q=E[0];for(R=Math.ceil(R),de=0;dede)return Ue;return Math.max(de,1)}(de,h,0,q),ye>0){for(Xe=0,gt=ye-1;Xe1?(Oe-Ue)/(ye-1):null)?0:Ue-nn,Ue),md(h,yt,Oe,Ge.isNullOrUndef(nn)?h.length:Oe+nn),xo(h)}return md(h,yt),xo(h)},_tickSize:function(){var h=this,E=h.options.ticks,R=Ge.toRadians(h.labelRotation),F=Math.abs(Math.cos(R)),q=Math.abs(Math.sin(R)),de=h._getLabelSizes(),ye=E.autoSkipPadding||0,Ue=de?de.widest.width+ye:0,Oe=de?de.highest.height+ye:0;return h.isHorizontal()?Oe*F>Ue*q?Ue/F:Oe/q:Oe*q=0&&(de=Ue),void 0!==q&&(Ue=E.indexOf(q))>=0&&(ye=Ue),h.minIndex=de,h.maxIndex=ye,h.min=E[de],h.max=E[ye]},buildTicks:function(){var h=this,E=h._getLabels(),R=h.minIndex,F=h.maxIndex;h.ticks=0===R&&F===E.length-1?E:E.slice(R,F+1)},getLabelForIndex:function(h,E){var R=this,F=R.chart;return F.getDatasetMeta(E).controller._getValueScaleId()===R.id?R.getRightValue(F.data.datasets[E].data[h]):R._getLabels()[h]},_configure:function(){var h=this,E=h.options.offset,R=h.ticks;Nl.prototype._configure.call(h),h.isHorizontal()||(h._reversePixels=!h._reversePixels),R&&(h._startValue=h.minIndex-(E?.5:0),h._valueRange=Math.max(R.length-(E?0:1),1))},getPixelForValue:function(h,E,R){var q,de,ye,F=this;return!td(E)&&!td(R)&&(h=F.chart.data.datasets[R].data[E]),td(h)||(q=F.isHorizontal()?h.x:h.y),(void 0!==q||void 0!==h&&isNaN(E))&&(de=F._getLabels(),h=Ge.valueOrDefault(q,h),E=-1!==(ye=de.indexOf(h))?ye:E,isNaN(E)&&(E=h)),F.getPixelForDecimal((E-F._startValue)/F._valueRange)},getPixelForTick:function(h){var E=this.ticks;return h<0||h>E.length-1?null:this.getPixelForValue(E[h],h+this.minIndex)},getValueForPixel:function(h){var E=this,R=Math.round(E._startValue+E.getDecimalForPixel(h)*E._valueRange);return Math.min(Math.max(R,0),E.ticks.length-1)},getBasePixel:function(){return this.bottom}});ss._defaults={position:"bottom"};var ic=Ge.isNullOrUndef;var al=Nl.extend({getRightValue:function(h){return"string"==typeof h?+h:Nl.prototype.getRightValue.call(this,h)},handleTickRangeOptions:function(){var h=this,R=h.options.ticks;if(R.beginAtZero){var F=Ge.sign(h.min),q=Ge.sign(h.max);F<0&&q<0?h.max=0:F>0&&q>0&&(h.min=0)}var de=void 0!==R.min||void 0!==R.suggestedMin,ye=void 0!==R.max||void 0!==R.suggestedMax;void 0!==R.min?h.min=R.min:void 0!==R.suggestedMin&&(h.min=null===h.min?R.suggestedMin:Math.min(h.min,R.suggestedMin)),void 0!==R.max?h.max=R.max:void 0!==R.suggestedMax&&(h.max=null===h.max?R.suggestedMax:Math.max(h.max,R.suggestedMax)),de!==ye&&h.min>=h.max&&(de?h.max=h.min+1:h.min=h.max-1),h.min===h.max&&(h.max++,R.beginAtZero||h.min--)},getTickLimit:function(){var q,h=this,E=h.options.ticks,R=E.stepSize,F=E.maxTicksLimit;return R?q=Math.ceil(h.max/R)-Math.floor(h.min/R)+1:(q=h._computeTickLimit(),F=F||11),F&&(q=Math.min(F,q)),q},_computeTickLimit:function(){return Number.POSITIVE_INFINITY},handleDirectionalChanges:Ge.noop,buildTicks:function(){var h=this,R=h.options.ticks,F=h.getTickLimit(),q={maxTicks:F=Math.max(2,F),min:R.min,max:R.max,precision:R.precision,stepSize:Ge.valueOrDefault(R.fixedStepSize,R.stepSize)},de=h.ticks=function(h,E){var Pn,xn,ir,Gr,R=[],q=h.stepSize,de=q||1,ye=h.maxTicks-1,Ue=h.min,Oe=h.max,Xe=h.precision,gt=E.min,yt=E.max,nn=Ge.niceNum((yt-gt)/ye/de)*de;if(nn<1e-14&&ic(Ue)&&ic(Oe))return[gt,yt];(Gr=Math.ceil(yt/nn)-Math.floor(gt/nn))>ye&&(nn=Ge.niceNum(Gr*nn/ye/de)*de),q||ic(Xe)?Pn=Math.pow(10,Ge._decimalPlaces(nn)):(Pn=Math.pow(10,Xe),nn=Math.ceil(nn*Pn)/Pn),xn=Math.floor(gt/nn)*nn,ir=Math.ceil(yt/nn)*nn,q&&(!ic(Ue)&&Ge.almostWhole(Ue/nn,nn/1e3)&&(xn=Ue),!ic(Oe)&&Ge.almostWhole(Oe/nn,nn/1e3)&&(ir=Oe)),Gr=Ge.almostEquals(Gr=(ir-xn)/nn,Math.round(Gr),nn/1e3)?Math.round(Gr):Math.ceil(Gr),xn=Math.round(xn*Pn)/Pn,ir=Math.round(ir*Pn)/Pn,R.push(ic(Ue)?xn:Ue);for(var Pi=1;PiE.length-1?null:this.getPixelForValue(E[h])}});Et._defaults=Wd;var Hn=Ge.valueOrDefault,Xn=Ge.math.log10;var Yo={position:"left",ticks:{callback:Fo.formatters.logarithmic}};function $a(h,E){return Ge.isFinite(h)&&h>=0?h:E}var ns=Nl.extend({determineDataLimits:function(){var ye,Ue,Oe,Xe,gt,yt,h=this,E=h.options,R=h.chart,F=R.data.datasets,q=h.isHorizontal();function de(Gr){return q?Gr.xAxisID===h.id:Gr.yAxisID===h.id}h.min=Number.POSITIVE_INFINITY,h.max=Number.NEGATIVE_INFINITY,h.minNotZero=Number.POSITIVE_INFINITY;var nn=E.stacked;if(void 0===nn)for(ye=0;ye0){var Pi=Ge.min(Gr),Zo=Ge.max(Gr);h.min=Math.min(h.min,Pi),h.max=Math.max(h.max,Zo)}})}else for(ye=0;ye0?h.min:h.max<1?Math.pow(10,Math.floor(Xn(h.max))):1)},buildTicks:function(){var h=this,E=h.options.ticks,R=!h.isHorizontal(),F={min:$a(E.min),max:$a(E.max)},q=h.ticks=function(h,E){var ye,Ue,R=[],F=Hn(h.min,Math.pow(10,Math.floor(Xn(E.min)))),q=Math.floor(Xn(E.max)),de=Math.ceil(E.max/Math.pow(10,q));0===F?(ye=Math.floor(Xn(E.minNotZero)),Ue=Math.floor(E.minNotZero/Math.pow(10,ye)),R.push(F),F=Ue*Math.pow(10,ye)):(ye=Math.floor(Xn(F)),Ue=Math.floor(F/Math.pow(10,ye)));var Oe=ye<0?Math.pow(10,Math.abs(ye)):1;do{R.push(F),10==++Ue&&(Ue=1,Oe=++ye>=0?1:Oe),F=Math.round(Ue*Math.pow(10,ye)*Oe)/Oe}while(yeE.length-1?null:this.getPixelForValue(E[h])},_getFirstTickValue:function(h){var E=Math.floor(Xn(h));return Math.floor(h/Math.pow(10,E))*Math.pow(10,E)},_configure:function(){var h=this,E=h.min,R=0;Nl.prototype._configure.call(h),0===E&&(E=h._getFirstTickValue(h.minNotZero),R=Hn(h.options.ticks.fontSize,br.global.defaultFontSize)/h._length),h._startValue=Xn(E),h._valueOffset=R,h._valueRange=(Xn(h.max)-Xn(E))/(1-R)},getPixelForValue:function(h){var E=this,R=0;return(h=+E.getRightValue(h))>E.min&&h>0&&(R=(Xn(h)-E._startValue)/E._valueRange+E._valueOffset),E.getPixelForDecimal(R)},getValueForPixel:function(h){var E=this,R=E.getDecimalForPixel(h);return 0===R&&0===E.min?0:Math.pow(10,E._startValue+(R-E._valueOffset)*E._valueRange)}});ns._defaults=Yo;var Hs=Ge.valueOrDefault,fc=Ge.valueAtIndexOrDefault,ga=Ge.options.resolve,Ol={display:!0,animate:!0,position:"chartArea",angleLines:{display:!0,color:"rgba(0,0,0,0.1)",lineWidth:1,borderDash:[],borderDashOffset:0},gridLines:{circular:!1},ticks:{showLabelBackdrop:!0,backdropColor:"rgba(255,255,255,0.75)",backdropPaddingY:2,backdropPaddingX:2,callback:Fo.formatters.linear},pointLabels:{display:!0,fontSize:10,callback:function(h){return h}}};function ll(h){var E=h.ticks;return E.display&&h.display?Hs(E.fontSize,br.global.defaultFontSize)+2*E.backdropPaddingY:0}function Za(h,E,R){return Ge.isArray(R)?{w:Ge.longestText(h,h.font,R),h:R.length*E}:{w:h.measureText(R).width,h:E}}function ja(h,E,R,F,q){return h===F||h===q?{start:E-R/2,end:E+R/2}:hq?{start:E-R,end:E}:{start:E,end:E+R}}function Of(h){return 0===h||180===h?"center":h<180?"left":"right"}function Rd(h,E,R,F){var de,ye,q=R.y+F/2;if(Ge.isArray(E))for(de=0,ye=E.length;de270||h<90)&&(R.y-=E.h)}function Cc(h){return Ge.isNumber(h)?h:0}var Xd=al.extend({setDimensions:function(){var h=this;h.width=h.maxWidth,h.height=h.maxHeight,h.paddingTop=ll(h.options)/2,h.xCenter=Math.floor(h.width/2),h.yCenter=Math.floor((h.height-h.paddingTop)/2),h.drawingArea=Math.min(h.height-h.paddingTop,h.width)/2},determineDataLimits:function(){var h=this,E=h.chart,R=Number.POSITIVE_INFINITY,F=Number.NEGATIVE_INFINITY;Ge.each(E.data.datasets,function(q,de){if(E.isDatasetVisible(de)){var ye=E.getDatasetMeta(de);Ge.each(q.data,function(Ue,Oe){var Xe=+h.getRightValue(Ue);isNaN(Xe)||ye.data[Oe].hidden||(R=Math.min(Xe,R),F=Math.max(Xe,F))})}}),h.min=R===Number.POSITIVE_INFINITY?0:R,h.max=F===Number.NEGATIVE_INFINITY?0:F,h.handleTickRangeOptions()},_computeTickLimit:function(){return Math.ceil(this.drawingArea/ll(this.options))},convertTicksToLabels:function(){var h=this;al.prototype.convertTicksToLabels.call(h),h.pointLabels=h.chart.data.labels.map(function(){var E=Ge.callback(h.options.pointLabels.callback,arguments,h);return E||0===E?E:""})},getLabelForIndex:function(h,E){return+this.getRightValue(this.chart.data.datasets[E].data[h])},fit:function(){var h=this,E=h.options;E.display&&E.pointLabels.display?function(h){var q,de,ye,E=Ge.options._parseFont(h.options.pointLabels),R={l:0,r:h.width,t:0,b:h.height-h.paddingTop},F={};h.ctx.font=E.string,h._pointLabelSizes=[];var Ue=h.chart.data.labels.length;for(q=0;qR.r&&(R.r=gt.end,F.r=Oe),yt.startR.b&&(R.b=yt.end,F.b=Oe)}h.setReductions(h.drawingArea,R,F)}(h):h.setCenterPoint(0,0,0,0)},setReductions:function(h,E,R){var F=this,q=E.l/Math.sin(R.l),de=Math.max(E.r-F.width,0)/Math.sin(R.r),ye=-E.t/Math.cos(R.t),Ue=-Math.max(E.b-(F.height-F.paddingTop),0)/Math.cos(R.b);q=Cc(q),de=Cc(de),ye=Cc(ye),Ue=Cc(Ue),F.drawingArea=Math.min(Math.floor(h-(q+de)/2),Math.floor(h-(ye+Ue)/2)),F.setCenterPoint(q,de,ye,Ue)},setCenterPoint:function(h,E,R,F){var q=this,Ue=R+q.drawingArea,Oe=q.height-q.paddingTop-F-q.drawingArea;q.xCenter=Math.floor((h+q.drawingArea+(q.width-E-q.drawingArea))/2+q.left),q.yCenter=Math.floor((Ue+Oe)/2+q.top+q.paddingTop)},getIndexAngle:function(h){var E=this.chart,de=(h*(360/E.data.labels.length)+((E.options||{}).startAngle||0))%360;return(de<0?de+360:de)*Math.PI*2/360},getDistanceFromCenterForValue:function(h){var E=this;if(Ge.isNullOrUndef(h))return NaN;var R=E.drawingArea/(E.max-E.min);return E.options.ticks.reverse?(E.max-h)*R:(h-E.min)*R},getPointPosition:function(h,E){var R=this,F=R.getIndexAngle(h)-Math.PI/2;return{x:Math.cos(F)*E+R.xCenter,y:Math.sin(F)*E+R.yCenter}},getPointPositionForValue:function(h,E){return this.getPointPosition(h,this.getDistanceFromCenterForValue(E))},getBasePosition:function(h){var E=this,R=E.min,F=E.max;return E.getPointPositionForValue(h||0,E.beginAtZero?0:R<0&&F<0?F:R>0&&F>0?R:0)},_drawGrid:function(){var Ue,Oe,Xe,h=this,E=h.ctx,R=h.options,F=R.gridLines,q=R.angleLines,de=Hs(q.lineWidth,F.lineWidth),ye=Hs(q.color,F.color);if(R.pointLabels.display&&function(h){var E=h.ctx,R=h.options,F=R.pointLabels,q=ll(R),de=h.getDistanceFromCenterForValue(R.ticks.reverse?h.min:h.max),ye=Ge.options._parseFont(F);E.save(),E.font=ye.string,E.textBaseline="middle";for(var Ue=h.chart.data.labels.length-1;Ue>=0;Ue--){var Xe=h.getPointPosition(Ue,de+(0===Ue?q/2:0)+5),gt=fc(F.fontColor,Ue,br.global.defaultFontColor);E.fillStyle=gt;var yt=h.getIndexAngle(Ue),nn=Ge.toDegrees(yt);E.textAlign=Of(nn),Qd(nn,h._pointLabelSizes[Ue],Xe),Rd(E,h.pointLabels[Ue],Xe,ye.lineHeight)}E.restore()}(h),F.display&&Ge.each(h.ticks,function(gt,yt){0!==yt&&(Oe=h.getDistanceFromCenterForValue(h.ticksAsNumbers[yt]),function(h,E,R,F){var Xe,q=h.ctx,de=E.circular,ye=h.chart.data.labels.length,Ue=fc(E.color,F-1),Oe=fc(E.lineWidth,F-1);if((de||ye)&&Ue&&Oe){if(q.save(),q.strokeStyle=Ue,q.lineWidth=Oe,q.setLineDash&&(q.setLineDash(E.borderDash||[]),q.lineDashOffset=E.borderDashOffset||0),q.beginPath(),de)q.arc(h.xCenter,h.yCenter,R,0,2*Math.PI);else{Xe=h.getPointPosition(0,R),q.moveTo(Xe.x,Xe.y);for(var gt=1;gt=0;Ue--)Oe=h.getDistanceFromCenterForValue(R.ticks.reverse?h.min:h.max),Xe=h.getPointPosition(Ue,Oe),E.beginPath(),E.moveTo(h.xCenter,h.yCenter),E.lineTo(Xe.x,Xe.y),E.stroke();E.restore()}},_drawLabels:function(){var h=this,E=h.ctx,F=h.options.ticks;if(F.display){var Ue,Oe,q=h.getIndexAngle(0),de=Ge.options._parseFont(F),ye=Hs(F.fontColor,br.global.defaultFontColor);E.save(),E.font=de.string,E.translate(h.xCenter,h.yCenter),E.rotate(q),E.textAlign="center",E.textBaseline="middle",Ge.each(h.ticks,function(Xe,gt){0===gt&&!F.reverse||(Ue=h.getDistanceFromCenterForValue(h.ticksAsNumbers[gt]),F.showLabelBackdrop&&(Oe=E.measureText(Xe).width,E.fillStyle=F.backdropColor,E.fillRect(-Oe/2-F.backdropPaddingX,-Ue-de.size/2-F.backdropPaddingY,Oe+2*F.backdropPaddingX,de.size+2*F.backdropPaddingY)),E.fillStyle=ye,E.fillText(Xe,0,-Ue))}),E.restore()}},_drawTitle:Ge.noop});Xd._defaults=Ol;var $f=Ge._deprecated,Ju=Ge.options.resolve,Sl=Ge.valueOrDefault,wl=Number.MIN_SAFE_INTEGER||-9007199254740991,kl=Number.MAX_SAFE_INTEGER||9007199254740991,Pu={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},Ja=Object.keys(Pu);function Qu(h,E){return h-E}function Oc(h){return Ge.valueOrDefault(h.time.min,h.ticks.min)}function Au(h){return Ge.valueOrDefault(h.time.max,h.ticks.max)}function ff(h,E,R,F){var q=function(h,E,R){for(var de,ye,Ue,F=0,q=h.length-1;F>=0&&F<=q;){if(Ue=h[de=F+q>>1],!(ye=h[de-1]||null))return{lo:null,hi:Ue};if(Ue[E]R))return{lo:ye,hi:Ue};q=de-1}}return{lo:Ue,hi:null}}(h,E,R),de=q.lo?q.hi?q.lo:h[h.length-2]:h[0],ye=q.lo?q.hi?q.hi:h[h.length-1]:h[1],Ue=ye[E]-de[E];return de[F]+(ye[F]-de[F])*(Ue?(R-de[E])/Ue:0)}function Jl(h,E){var R=h._adapter,F=h.options.time,q=F.parser,de=q||F.format,ye=E;return"function"==typeof q&&(ye=q(ye)),Ge.isFinite(ye)||(ye="string"==typeof de?R.parse(ye,de):R.parse(ye)),null!==ye?+ye:(!q&&"function"==typeof de&&(ye=de(E),Ge.isFinite(ye)||(ye=R.parse(ye))),ye)}function vd(h,E){if(Ge.isNullOrUndef(E))return null;var R=h.options.time,F=Jl(h,h.getRightValue(E));return null===F||R.round&&(F=+h._adapter.startOf(F,R.round)),F}function wc(h,E,R,F){var de,ye,q=Ja.length;for(de=Ja.indexOf(h);de=0&&(E[Oe].major=!0);return E}(h,F,q,R):F}var $p=Nl.extend({initialize:function(){this.mergeTicksOptions(),Nl.prototype.initialize.call(this)},update:function(){var h=this,E=h.options,R=E.time||(E.time={}),F=h._adapter=new Io._date(E.adapters.date);return $f("time scale",R.format,"time.format","time.parser"),$f("time scale",R.min,"time.min","ticks.min"),$f("time scale",R.max,"time.max","ticks.max"),Ge.mergeIf(R.displayFormats,F.formats()),Nl.prototype.update.apply(h,arguments)},getRightValue:function(h){return h&&void 0!==h.t&&(h=h.t),Nl.prototype.getRightValue.call(this,h)},determineDataLimits:function(){var gt,yt,nn,Pn,xn,ir,Gr,h=this,E=h.chart,R=h._adapter,F=h.options,q=F.time.unit||"day",de=kl,ye=wl,Ue=[],Oe=[],Xe=[],Pi=h._getLabels();for(gt=0,nn=Pi.length;gt1?function(h){var F,q,de,E={},R=[];for(F=0,q=h.length;F1e5*Oe)throw E+" and "+R+" are too far apart with stepSize of "+Oe+" "+Ue;for(nn=gt;nn=E&&Pn<=R&&Ue.push(Pn);return h.min=E,h.max=R,h._unit=de.unit||(q.autoSkip?wc(de.minUnit,h.min,h.max,Oe):function(h,E,R,F,q){var de,ye;for(de=Ja.length-1;de>=Ja.indexOf(R);de--)if(Pu[ye=Ja[de]].common&&h._adapter.diff(q,F,ye)>=E-1)return ye;return Ja[R?Ja.indexOf(R):0]}(h,Ue.length,de.minUnit,h.min,h.max)),h._majorUnit=q.major.enabled&&"year"!==h._unit?function(h){for(var E=Ja.indexOf(h)+1,R=Ja.length;EE&&Xe=0&&h0?Ue:1}});$p._defaults={position:"bottom",distribution:"linear",bounds:"data",adapters:{},time:{parser:!1,unit:!1,round:!1,displayFormat:!1,isoWeekday:!1,minUnit:"millisecond",displayFormats:{}},ticks:{autoSkip:!1,source:"auto",major:{enabled:!1}}};var _f={category:ss,linear:Et,logarithmic:ns,radialLinear:Xd,time:$p},Dh={datetime:"MMM D, YYYY, h:mm:ss a",millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm a",hour:"hA",day:"MMM D",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"};Io._date.override("function"==typeof r?{_id:"moment",formats:function(){return Dh},parse:function(h,E){return"string"==typeof h&&"string"==typeof E?h=r(h,E):h instanceof r||(h=r(h)),h.isValid()?h.valueOf():null},format:function(h,E){return r(h).format(E)},add:function(h,E,R){return r(h).add(E,R).valueOf()},diff:function(h,E,R){return r(h).diff(r(E),R)},startOf:function(h,E,R){return h=r(h),"isoWeek"===E?h.isoWeekday(R).valueOf():h.startOf(E).valueOf()},endOf:function(h,E){return r(h).endOf(E).valueOf()},_create:function(h){return r(h)}}:{}),br._set("global",{plugins:{filler:{propagate:!0}}});var Ql={dataset:function(h){var E=h.fill,R=h.chart,F=R.getDatasetMeta(E),de=F&&R.isDatasetVisible(E)&&F.dataset._children||[],ye=de.length||0;return ye?function(Ue,Oe){return Oe=R)&&de;switch(q){case"bottom":return"start";case"top":return"end";case"zero":return"origin";case"origin":case"start":case"end":return q;default:return!1}}function Nd(h){return(h.el._scale||{}).getPointPositionForValue?function(h){var ye,Ue,Oe,Xe,gt,E=h.el._scale,R=E.options,F=E.chart.data.labels.length,q=h.fill,de=[];if(!F)return null;for(Ue=R.ticks.reverse?E.min:E.max,Oe=E.getPointPositionForValue(0,ye=R.ticks.reverse?E.max:E.min),Xe=0;Xe0;--de)h.arc(ye,Ue,Oe,R[de].angle,R[de-1].angle,!0);return}for(h.lineTo(R[q-1].x,R[q-1].y),de=q-1;de>0;--de)Ge.canvas.lineTo(h,R[de],R[de-1],!0)}}function Ll(h,E,R,F,q,de){var nn,Pn,xn,ir,Gr,Pi,Zo,Lo,ye=E.length,Ue=F.spanGaps,Oe=[],Xe=[],gt=0,yt=0;for(h.beginPath(),nn=0,Pn=ye;nn=0;--q)(F=E[q].$filler)&&F.visible&&(Ue=(de=F.el)._children||[],Xe=(ye=de._view).backgroundColor||br.global.defaultColor,(Oe=F.mapper)&&Xe&&Ue.length&&(Ge.canvas.clipArea(R,h.chartArea),Ll(R,Ue,Oe,ye,Xe,de._loop),Ge.canvas.unclipArea(R)))}},rl=Ge.rtl.getRtlAdapter,Yu=Ge.noop,pc=Ge.valueOrDefault;function Kd(h,E){return h.usePointStyle&&h.boxWidth>E?E:h.boxWidth}br._set("global",{legend:{display:!0,position:"top",align:"center",fullWidth:!0,reverse:!1,weight:1e3,onClick:function(h,E){var R=E.datasetIndex,F=this.chart,q=F.getDatasetMeta(R);q.hidden=null===q.hidden?!F.data.datasets[R].hidden:null,F.update()},onHover:null,onLeave:null,labels:{boxWidth:40,padding:10,generateLabels:function(h){var E=h.data.datasets,R=h.options.legend||{},F=R.labels&&R.labels.usePointStyle;return h._getSortedDatasetMetas().map(function(q){var de=q.controller.getStyle(F?0:void 0);return{text:E[q.index].label,fillStyle:de.backgroundColor,hidden:!h.isDatasetVisible(q.index),lineCap:de.borderCapStyle,lineDash:de.borderDash,lineDashOffset:de.borderDashOffset,lineJoin:de.borderJoinStyle,lineWidth:de.borderWidth,strokeStyle:de.borderColor,pointStyle:de.pointStyle,rotation:de.rotation,datasetIndex:q.index}},this)}}},legendCallback:function(h){var F,q,de,E=document.createElement("ul"),R=h.data.datasets;for(E.setAttribute("class",h.id+"-legend"),F=0,q=R.length;FOe.width)&&(yt+=ye+R.padding,gt[gt.length-(Lo>0?0:1)]=0),Ue[Lo]={left:0,top:0,width:Xs,height:ye},gt[gt.length-1]+=Xs+R.padding}),Oe.height+=yt}else{var nn=R.padding,Pn=h.columnWidths=[],xn=h.columnHeights=[],ir=R.padding,Gr=0,Pi=0;Ge.each(h.legendItems,function(Zo,Lo){var Xs=Kd(R,ye)+ye/2+q.measureText(Zo.text).width;Lo>0&&Pi+ye+2*nn>Oe.height&&(ir+=Gr+R.padding,Pn.push(Gr),xn.push(Pi),Gr=0,Pi=0),Gr=Math.max(Gr,Xs),Pi+=ye+nn,Ue[Lo]={left:0,top:0,width:Xs,height:ye}}),ir+=Gr,Pn.push(Gr),xn.push(Pi),Oe.width+=ir}h.width=Oe.width,h.height=Oe.height}else h.width=Oe.width=h.height=Oe.height=0},afterFit:Yu,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){var h=this,E=h.options,R=E.labels,F=br.global,q=F.defaultColor,de=F.elements.line,ye=h.height,Ue=h.columnHeights,Oe=h.width,Xe=h.lineWidths;if(E.display){var ir,gt=rl(E.rtl,h.left,h.minSize.width),yt=h.ctx,nn=pc(R.fontColor,F.defaultFontColor),Pn=Ge.options._parseFont(R),xn=Pn.size;yt.textAlign=gt.textAlign("left"),yt.textBaseline="middle",yt.lineWidth=.5,yt.strokeStyle=nn,yt.fillStyle=nn,yt.font=Pn.string;var Gr=Kd(R,xn),Pi=h.legendHitBoxes,Gs=function(ha,ia){switch(E.align){case"start":return R.padding;case"end":return ha-ia;default:return(ha-ia+R.padding)/2}},Xs=h.isHorizontal();ir=Xs?{x:h.left+Gs(Oe,Xe[0]),y:h.top+R.padding,line:0}:{x:h.left+R.padding,y:h.top+Gs(ye,Ue[0]),line:0},Ge.rtl.overrideTextDirection(h.ctx,E.textDirection);var dl=xn+R.padding;Ge.each(h.legendItems,function(ha,ia){var Pa=yt.measureText(ha.text).width,il=Gr+xn/2+Pa,Eu=ir.x,wa=ir.y;gt.setWidth(h.minSize.width),Xs?ia>0&&Eu+il+R.padding>h.left+h.minSize.width&&(wa=ir.y+=dl,ir.line++,Eu=ir.x=h.left+Gs(Oe,Xe[ir.line])):ia>0&&wa+dl>h.top+h.minSize.height&&(Eu=ir.x=Eu+h.columnWidths[ir.line]+R.padding,ir.line++,wa=ir.y=h.top+Gs(ye,Ue[ir.line]));var ou=gt.x(Eu);(function(ha,ia,Pa){if(!(isNaN(Gr)||Gr<=0)){yt.save();var il=pc(Pa.lineWidth,de.borderWidth);if(yt.fillStyle=pc(Pa.fillStyle,q),yt.lineCap=pc(Pa.lineCap,de.borderCapStyle),yt.lineDashOffset=pc(Pa.lineDashOffset,de.borderDashOffset),yt.lineJoin=pc(Pa.lineJoin,de.borderJoinStyle),yt.lineWidth=il,yt.strokeStyle=pc(Pa.strokeStyle,q),yt.setLineDash&&yt.setLineDash(pc(Pa.lineDash,de.borderDash)),R&&R.usePointStyle){var Eu=Gr*Math.SQRT2/2,wa=gt.xPlus(ha,Gr/2);Ge.canvas.drawPoint(yt,Pa.pointStyle,Eu,wa,ia+xn/2,Pa.rotation)}else yt.fillRect(gt.leftForLtr(ha,Gr),ia,Gr,xn),0!==il&&yt.strokeRect(gt.leftForLtr(ha,Gr),ia,Gr,xn);yt.restore()}})(ou,wa,ha),Pi[ia].left=gt.leftForLtr(ou,Pi[ia].width),Pi[ia].top=wa,function(ha,ia,Pa,il){var Eu=xn/2,wa=gt.xPlus(ha,Gr+Eu),ou=ia+Eu;yt.fillText(Pa.text,wa,ou),Pa.hidden&&(yt.beginPath(),yt.lineWidth=2,yt.moveTo(wa,ou),yt.lineTo(gt.xPlus(wa,il),ou),yt.stroke())}(ou,wa,ha,Pa),Xs?ir.x+=il+R.padding:ir.y+=dl}),Ge.rtl.restoreTextDirection(h.ctx,E.textDirection)}},_getLegendItemAt:function(h,E){var F,q,de,R=this;if(h>=R.left&&h<=R.right&&E>=R.top&&E<=R.bottom)for(de=R.legendHitBoxes,F=0;F=(q=de[F]).left&&h<=q.left+q.width&&E>=q.top&&E<=q.top+q.height)return R.legendItems[F];return null},handleEvent:function(h){var q,E=this,R=E.options,F="mouseup"===h.type?"click":h.type;if("mousemove"===F){if(!R.onHover&&!R.onLeave)return}else{if("click"!==F)return;if(!R.onClick)return}q=E._getLegendItemAt(h.x,h.y),"click"===F?q&&R.onClick&&R.onClick.call(E,h.native,q):(R.onLeave&&q!==E._hoveredItem&&(E._hoveredItem&&R.onLeave.call(E,h.native,E._hoveredItem),E._hoveredItem=q),R.onHover&&q&&R.onHover.call(E,h.native,q))}});function Vl(h,E){var R=new wf({ctx:h.ctx,options:E,chart:h});Zl.configure(h,R,E),Zl.addBox(h,R),h.legend=R}var Id={id:"legend",_element:wf,beforeInit:function(h){var E=h.options.legend;E&&Vl(h,E)},beforeUpdate:function(h){var E=h.options.legend,R=h.legend;E?(Ge.mergeIf(E,br.global.legend),R?(Zl.configure(h,R,E),R.options=E):Vl(h,E)):R&&(Zl.removeBox(h,R),delete h.legend)},afterEvent:function(h,E){var R=h.legend;R&&R.handleEvent(E)}},oc=Ge.noop;br._set("global",{title:{display:!1,fontStyle:"bold",fullWidth:!0,padding:10,position:"top",text:"",weight:2e3}});var hc=ar.extend({initialize:function(h){Ge.extend(this,h),this.legendHitBoxes=[]},beforeUpdate:oc,update:function(h,E,R){var F=this;return F.beforeUpdate(),F.maxWidth=h,F.maxHeight=E,F.margins=R,F.beforeSetDimensions(),F.setDimensions(),F.afterSetDimensions(),F.beforeBuildLabels(),F.buildLabels(),F.afterBuildLabels(),F.beforeFit(),F.fit(),F.afterFit(),F.afterUpdate(),F.minSize},afterUpdate:oc,beforeSetDimensions:oc,setDimensions:function(){var h=this;h.isHorizontal()?(h.width=h.maxWidth,h.left=0,h.right=h.width):(h.height=h.maxHeight,h.top=0,h.bottom=h.height),h.paddingLeft=0,h.paddingTop=0,h.paddingRight=0,h.paddingBottom=0,h.minSize={width:0,height:0}},afterSetDimensions:oc,beforeBuildLabels:oc,buildLabels:oc,afterBuildLabels:oc,beforeFit:oc,fit:function(){var de,h=this,E=h.options,R=h.minSize={},F=h.isHorizontal();E.display?(de=(Ge.isArray(E.text)?E.text.length:1)*Ge.options._parseFont(E).lineHeight+2*E.padding,h.width=R.width=F?h.maxWidth:de,h.height=R.height=F?de:h.maxHeight):h.width=R.width=h.height=R.height=0},afterFit:oc,isHorizontal:function(){var h=this.options.position;return"top"===h||"bottom"===h},draw:function(){var h=this,E=h.ctx,R=h.options;if(R.display){var yt,nn,Pn,F=Ge.options._parseFont(R),q=F.lineHeight,de=q/2+R.padding,ye=0,Ue=h.top,Oe=h.left,Xe=h.bottom,gt=h.right;E.fillStyle=Ge.valueOrDefault(R.fontColor,br.global.defaultFontColor),E.font=F.string,h.isHorizontal()?(nn=Oe+(gt-Oe)/2,Pn=Ue+de,yt=gt-Oe):(nn="left"===R.position?Oe+de:gt-de,Pn=Ue+(Xe-Ue)/2,yt=Xe-Ue,ye=Math.PI*("left"===R.position?-.5:.5)),E.save(),E.translate(nn,Pn),E.rotate(ye),E.textAlign="center",E.textBaseline="middle";var xn=R.text;if(Ge.isArray(xn))for(var ir=0,Gr=0;Gr=0;ye--){var Ue=F[ye];if(q(Ue))return Ue}},Ge.isNumber=function(F){return!isNaN(parseFloat(F))&&isFinite(F)},Ge.almostEquals=function(F,q,de){return Math.abs(F-q)=F},Ge.max=function(F){return F.reduce(function(q,de){return isNaN(de)?q:Math.max(q,de)},Number.NEGATIVE_INFINITY)},Ge.min=function(F){return F.reduce(function(q,de){return isNaN(de)?q:Math.min(q,de)},Number.POSITIVE_INFINITY)},Ge.sign=Math.sign?function(F){return Math.sign(F)}:function(F){return 0==(F=+F)||isNaN(F)?F:F>0?1:-1},Ge.toRadians=function(F){return F*(Math.PI/180)},Ge.toDegrees=function(F){return F*(180/Math.PI)},Ge._decimalPlaces=function(F){if(Ge.isFinite(F)){for(var q=1,de=0;Math.round(F*q)/q!==F;)q*=10,de++;return de}},Ge.getAngleFromPoint=function(F,q){var de=q.x-F.x,ye=q.y-F.y,Ue=Math.sqrt(de*de+ye*ye),Oe=Math.atan2(ye,de);return Oe<-.5*Math.PI&&(Oe+=2*Math.PI),{angle:Oe,distance:Ue}},Ge.distanceBetweenPoints=function(F,q){return Math.sqrt(Math.pow(q.x-F.x,2)+Math.pow(q.y-F.y,2))},Ge.aliasPixel=function(F){return F%2==0?0:.5},Ge._alignPixel=function(F,q,de){var ye=F.currentDevicePixelRatio,Ue=de/2;return Math.round((q-Ue)*ye)/ye+Ue},Ge.splineCurve=function(F,q,de,ye){var Ue=F.skip?q:F,Oe=q,Xe=de.skip?q:de,gt=Math.sqrt(Math.pow(Oe.x-Ue.x,2)+Math.pow(Oe.y-Ue.y,2)),yt=Math.sqrt(Math.pow(Xe.x-Oe.x,2)+Math.pow(Xe.y-Oe.y,2)),nn=gt/(gt+yt),Pn=yt/(gt+yt),xn=ye*(nn=isNaN(nn)?0:nn),ir=ye*(Pn=isNaN(Pn)?0:Pn);return{previous:{x:Oe.x-xn*(Xe.x-Ue.x),y:Oe.y-xn*(Xe.y-Ue.y)},next:{x:Oe.x+ir*(Xe.x-Ue.x),y:Oe.y+ir*(Xe.y-Ue.y)}}},Ge.EPSILON=Number.EPSILON||1e-14,Ge.splineCurveMonotone=function(F){var ye,Ue,Oe,Xe,yt,nn,Pn,xn,ir,q=(F||[]).map(function(Gr){return{model:Gr._model,deltaK:0,mK:0}}),de=q.length;for(ye=0;ye0?q[ye-1]:null,(Xe=ye0?q[ye-1]:null)&&!Ue.model.skip&&(Oe.model.controlPointPreviousX=Oe.model.x-(ir=(Oe.model.x-Ue.model.x)/3),Oe.model.controlPointPreviousY=Oe.model.y-ir*Oe.mK),Xe&&!Xe.model.skip&&(Oe.model.controlPointNextX=Oe.model.x+(ir=(Xe.model.x-Oe.model.x)/3),Oe.model.controlPointNextY=Oe.model.y+ir*Oe.mK))},Ge.nextItem=function(F,q,de){return de?q>=F.length-1?F[0]:F[q+1]:q>=F.length-1?F[F.length-1]:F[q+1]},Ge.previousItem=function(F,q,de){return de?q<=0?F[F.length-1]:F[q-1]:q<=0?F[0]:F[q-1]},Ge.niceNum=function(F,q){var de=Math.floor(Ge.log10(F)),ye=F/Math.pow(10,de);return(q?ye<1.5?1:ye<3?2:ye<7?5:10:ye<=1?1:ye<=2?2:ye<=5?5:10)*Math.pow(10,de)},Ge.requestAnimFrame="undefined"==typeof window?function(F){F()}:window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(F){return window.setTimeout(F,1e3/60)},Ge.getRelativePosition=function(F,q){var de,ye,Ue=F.originalEvent||F,Oe=F.target||F.srcElement,Xe=Oe.getBoundingClientRect(),gt=Ue.touches;gt&>.length>0?(de=gt[0].clientX,ye=gt[0].clientY):(de=Ue.clientX,ye=Ue.clientY);var yt=parseFloat(Ge.getStyle(Oe,"padding-left")),nn=parseFloat(Ge.getStyle(Oe,"padding-top")),Pn=parseFloat(Ge.getStyle(Oe,"padding-right")),xn=parseFloat(Ge.getStyle(Oe,"padding-bottom")),Gr=Xe.bottom-Xe.top-nn-xn;return{x:de=Math.round((de-Xe.left-yt)/(Xe.right-Xe.left-yt-Pn)*Oe.width/q.currentDevicePixelRatio),y:ye=Math.round((ye-Xe.top-nn)/Gr*Oe.height/q.currentDevicePixelRatio)}},Ge.getConstraintWidth=function(F){return R(F,"max-width","clientWidth")},Ge.getConstraintHeight=function(F){return R(F,"max-height","clientHeight")},Ge._calculatePadding=function(F,q,de){return(q=Ge.getStyle(F,q)).indexOf("%")>-1?de*parseInt(q,10)/100:parseInt(q,10)},Ge._getParentNode=function(F){var q=F.parentNode;return q&&"[object ShadowRoot]"===q.toString()&&(q=q.host),q},Ge.getMaximumWidth=function(F){var q=Ge._getParentNode(F);if(!q)return F.clientWidth;var de=q.clientWidth,Oe=de-Ge._calculatePadding(q,"padding-left",de)-Ge._calculatePadding(q,"padding-right",de),Xe=Ge.getConstraintWidth(F);return isNaN(Xe)?Oe:Math.min(Oe,Xe)},Ge.getMaximumHeight=function(F){var q=Ge._getParentNode(F);if(!q)return F.clientHeight;var de=q.clientHeight,Oe=de-Ge._calculatePadding(q,"padding-top",de)-Ge._calculatePadding(q,"padding-bottom",de),Xe=Ge.getConstraintHeight(F);return isNaN(Xe)?Oe:Math.min(Oe,Xe)},Ge.getStyle=function(F,q){return F.currentStyle?F.currentStyle[q]:document.defaultView.getComputedStyle(F,null).getPropertyValue(q)},Ge.retinaScale=function(F,q){var de=F.currentDevicePixelRatio=q||"undefined"!=typeof window&&window.devicePixelRatio||1;if(1!==de){var ye=F.canvas,Ue=F.height,Oe=F.width;ye.height=Ue*de,ye.width=Oe*de,F.ctx.scale(de,de),!ye.style.height&&!ye.style.width&&(ye.style.height=Ue+"px",ye.style.width=Oe+"px")}},Ge.fontString=function(F,q,de){return q+" "+F+"px "+de},Ge.longestText=function(F,q,de,ye){var Ue=(ye=ye||{}).data=ye.data||{},Oe=ye.garbageCollect=ye.garbageCollect||[];ye.font!==q&&(Ue=ye.data={},Oe=ye.garbageCollect=[],ye.font=q),F.font=q;var yt,nn,Pn,xn,ir,Xe=0,gt=de.length;for(yt=0;ytde.length){for(yt=0;ytye&&(ye=Oe),ye},Ge.numberOfLabelLines=function(F){var q=1;return Ge.each(F,function(de){Ge.isArray(de)&&de.length>q&&(q=de.length)}),q},Ge.color=Br?function(F){return F instanceof CanvasGradient&&(F=br.global.defaultColor),Br(F)}:function(F){return console.error("Color.js not found!"),F},Ge.getHoverColor=function(F){return F instanceof CanvasPattern||F instanceof CanvasGradient?F:Ge.color(F).saturate(.5).darken(.1).rgbString()}}(),mn._adapters=Io,mn.Animation=lo,mn.animationService=vo,mn.controllers=pi,mn.DatasetController=Mi,mn.defaults=br,mn.Element=ar,mn.elements=ls,mn.Interaction=sl,mn.layouts=Zl,mn.platform=Aa,mn.plugins=Ss,mn.Scale=Nl,mn.scaleService=$u,mn.Ticks=Fo,mn.Tooltip=bl,mn.helpers.each(_f,function(h,E){mn.scaleService.registerScaleType(E,h,h._defaults)}),Hc)Hc.hasOwnProperty(Rc)&&mn.plugins.register(Hc[Rc]);mn.platform.initialize();var bu=mn;return"undefined"!=typeof window&&(window.Chart=mn),mn.Chart=mn,mn.Legend=Hc.legend._element,mn.Title=Hc.title._element,mn.pluginService=mn.plugins,mn.PluginBase=mn.Element.extend({}),mn.canvasHelpers=mn.helpers.canvas,mn.layoutService=mn.layouts,mn.LinearScaleBase=al,mn.helpers.each(["Bar","Bubble","Doughnut","Line","PolarArea","Radar","Scatter"],function(h){mn[h]=function(E,R){return new mn(E,mn.helpers.merge(R||{},{type:h.charAt(0).toLowerCase()+h.slice(1)}))}}),bu}(function(){try{return i(16738)}catch(p){}}())},82885:(v,T)=>{var r;!function(){"use strict";var u={}.hasOwnProperty;function p(){for(var d=[],e=0;e{v.exports=function(i,r){for(var u=[],p=0;p{"use strict";T.parse=function(S,A){if("string"!=typeof S)throw new TypeError("argument str must be a string");for(var N={},Z=(A||{}).decode||d,J=0;J{"use strict";var r=i(35311),u={"text/plain":"Text","text/html":"Url",default:"Text"};v.exports=function(_,y){var S,A,N,L,Z,J,K=!1;y||(y={}),S=y.debug||!1;try{if(N=r(),L=document.createRange(),Z=document.getSelection(),(J=document.createElement("span")).textContent=_,J.ariaHidden="true",J.style.all="unset",J.style.position="fixed",J.style.top=0,J.style.clip="rect(0, 0, 0, 0)",J.style.whiteSpace="pre",J.style.webkitUserSelect="text",J.style.MozUserSelect="text",J.style.msUserSelect="text",J.style.userSelect="text",J.addEventListener("copy",function(ue){ue.stopPropagation(),y.format&&(ue.preventDefault(),void 0===ue.clipboardData?(S&&console.warn("unable to use e.clipboardData"),S&&console.warn("trying IE specific stuff"),window.clipboardData.clearData(),window.clipboardData.setData(u[y.format]||u.default,_)):(ue.clipboardData.clearData(),ue.clipboardData.setData(y.format,_))),y.onCopy&&(ue.preventDefault(),y.onCopy(ue.clipboardData))}),document.body.appendChild(J),L.selectNodeContents(J),Z.addRange(L),!document.execCommand("copy"))throw new Error("copy command was unsuccessful");K=!0}catch(ue){S&&console.error("unable to copy using execCommand: ",ue),S&&console.warn("trying IE specific stuff");try{window.clipboardData.setData(y.format||"text",_),y.onCopy&&y.onCopy(window.clipboardData),K=!0}catch(ae){S&&console.error("unable to copy using clipboardData: ",ae),S&&console.error("falling back to prompt"),A=function(_){var y=(/mac os x/i.test(navigator.userAgent)?"\u2318":"Ctrl")+"+C";return _.replace(/#{\s*key\s*}/g,y)}("message"in y?y.message:"Copy to clipboard: #{key}, Enter"),window.prompt(A,_)}}finally{Z&&("function"==typeof Z.removeRange?Z.removeRange(L):Z.removeAllRanges()),J&&document.body.removeChild(J),N()}return K}},43987:(v,T,i)=>{var r=i(75242);v.exports=r},99556:(v,T,i)=>{var r=i(10323);v.exports=r},39287:(v,T,i)=>{var r=i(8748);v.exports=r},52964:(v,T,i)=>{var r=i(47506);v.exports=r},25272:(v,T,i)=>{var r=i(71873);v.exports=r},54450:(v,T,i)=>{var r=i(19095);v.exports=r},39557:(v,T,i)=>{var r=i(52049);v.exports=r},61611:(v,T,i)=>{var r=i(87054);v.exports=r},22549:(v,T,i)=>{var r=i(45284);v.exports=r},47646:(v,T,i)=>{var r=i(70157);v.exports=r},78663:(v,T,i)=>{var r=i(640);v.exports=r},48498:(v,T,i)=>{var r=i(50320);v.exports=r},4922:(v,T,i)=>{var r=i(93006);v.exports=r},95190:(v,T,i)=>{var r=i(36226);v.exports=r},78525:(v,T,i)=>{var r=i(21968);v.exports=r},21064:(v,T,i)=>{var r=i(87259);v.exports=r},65641:(v,T,i)=>{var r=i(62021);v.exports=r},21693:(v,T,i)=>{var r=i(57682);v.exports=r},88907:(v,T,i)=>{var r=i(94222);v.exports=r},41432:(v,T,i)=>{var r=i(1162);v.exports=r},7398:(v,T,i)=>{var r=i(82805);v.exports=r},67221:(v,T,i)=>{var r=i(26498);v.exports=r},67447:(v,T,i)=>{var r=i(44850);v.exports=r},58811:(v,T,i)=>{var r=i(9634);v.exports=r},19573:(v,T,i)=>{var r=i(96551);v.exports=r},10226:(v,T,i)=>{var r=i(98908);v.exports=r},74771:(v,T,i)=>{i(3934),i(261);var r=i(13544);v.exports=r.Array.from},8412:(v,T,i)=>{i(2862);var r=i(13544);v.exports=r.Array.isArray},77377:(v,T,i)=>{i(1625);var r=i(97911);v.exports=r("Array").concat},399:(v,T,i)=>{i(1285),i(17221);var r=i(97911);v.exports=r("Array").entries},66933:(v,T,i)=>{i(70466);var r=i(97911);v.exports=r("Array").every},9504:(v,T,i)=>{i(24990);var r=i(97911);v.exports=r("Array").fill},82168:(v,T,i)=>{i(56534);var r=i(97911);v.exports=r("Array").filter},65618:(v,T,i)=>{i(12773);var r=i(97911);v.exports=r("Array").findIndex},9186:(v,T,i)=>{i(60326);var r=i(97911);v.exports=r("Array").find},98812:(v,T,i)=>{i(98792);var r=i(97911);v.exports=r("Array").forEach},58479:(v,T,i)=>{i(77059);var r=i(97911);v.exports=r("Array").includes},43207:(v,T,i)=>{i(2795);var r=i(97911);v.exports=r("Array").indexOf},33195:(v,T,i)=>{i(1285),i(17221);var r=i(97911);v.exports=r("Array").keys},63033:(v,T,i)=>{i(74926);var r=i(97911);v.exports=r("Array").lastIndexOf},5736:(v,T,i)=>{i(88119);var r=i(97911);v.exports=r("Array").map},7198:(v,T,i)=>{i(46250);var r=i(97911);v.exports=r("Array").reduce},84302:(v,T,i)=>{i(32836);var r=i(97911);v.exports=r("Array").reverse},86693:(v,T,i)=>{i(72999);var r=i(97911);v.exports=r("Array").slice},24273:(v,T,i)=>{i(50733);var r=i(97911);v.exports=r("Array").some},45974:(v,T,i)=>{i(93639);var r=i(97911);v.exports=r("Array").sort},68012:(v,T,i)=>{i(63117);var r=i(97911);v.exports=r("Array").splice},46332:(v,T,i)=>{i(1285),i(17221);var r=i(97911);v.exports=r("Array").values},42618:(v,T,i)=>{i(34699);var r=i(13544);v.exports=r.Date.now},97724:(v,T,i)=>{i(33379);var r=i(97911);v.exports=r("Function").bind},63791:(v,T,i)=>{i(1285),i(3934);var r=i(34014);v.exports=r},27959:(v,T,i)=>{i(87404),v.exports=i(70009)},69029:(v,T,i)=>{var r=i(23336),u=i(97724),p=Function.prototype;v.exports=function(d){var e=d.bind;return d===p||r(p,d)&&e===p.bind?u:e}},28924:(v,T,i)=>{var r=i(23336),u=i(77377),p=Array.prototype;v.exports=function(d){var e=d.concat;return d===p||r(p,d)&&e===p.concat?u:e}},98709:(v,T,i)=>{var r=i(23336),u=i(66933),p=Array.prototype;v.exports=function(d){var e=d.every;return d===p||r(p,d)&&e===p.every?u:e}},65991:(v,T,i)=>{var r=i(23336),u=i(9504),p=Array.prototype;v.exports=function(d){var e=d.fill;return d===p||r(p,d)&&e===p.fill?u:e}},64158:(v,T,i)=>{var r=i(23336),u=i(82168),p=Array.prototype;v.exports=function(d){var e=d.filter;return d===p||r(p,d)&&e===p.filter?u:e}},91799:(v,T,i)=>{var r=i(23336),u=i(65618),p=Array.prototype;v.exports=function(d){var e=d.findIndex;return d===p||r(p,d)&&e===p.findIndex?u:e}},26155:(v,T,i)=>{var r=i(23336),u=i(9186),p=Array.prototype;v.exports=function(d){var e=d.find;return d===p||r(p,d)&&e===p.find?u:e}},33758:(v,T,i)=>{var r=i(23336),u=i(58479),p=i(85136),d=Array.prototype,e=String.prototype;v.exports=function(_){var y=_.includes;return _===d||r(d,_)&&y===d.includes?u:"string"==typeof _||_===e||r(e,_)&&y===e.includes?p:y}},7592:(v,T,i)=>{var r=i(23336),u=i(43207),p=Array.prototype;v.exports=function(d){var e=d.indexOf;return d===p||r(p,d)&&e===p.indexOf?u:e}},17480:(v,T,i)=>{var r=i(23336),u=i(63033),p=Array.prototype;v.exports=function(d){var e=d.lastIndexOf;return d===p||r(p,d)&&e===p.lastIndexOf?u:e}},20681:(v,T,i)=>{var r=i(23336),u=i(5736),p=Array.prototype;v.exports=function(d){var e=d.map;return d===p||r(p,d)&&e===p.map?u:e}},90949:(v,T,i)=>{var r=i(23336),u=i(7198),p=Array.prototype;v.exports=function(d){var e=d.reduce;return d===p||r(p,d)&&e===p.reduce?u:e}},99316:(v,T,i)=>{var r=i(23336),u=i(96302),p=String.prototype;v.exports=function(d){var e=d.repeat;return"string"==typeof d||d===p||r(p,d)&&e===p.repeat?u:e}},62212:(v,T,i)=>{var r=i(23336),u=i(84302),p=Array.prototype;v.exports=function(d){var e=d.reverse;return d===p||r(p,d)&&e===p.reverse?u:e}},49073:(v,T,i)=>{var r=i(23336),u=i(86693),p=Array.prototype;v.exports=function(d){var e=d.slice;return d===p||r(p,d)&&e===p.slice?u:e}},24146:(v,T,i)=>{var r=i(23336),u=i(24273),p=Array.prototype;v.exports=function(d){var e=d.some;return d===p||r(p,d)&&e===p.some?u:e}},40104:(v,T,i)=>{var r=i(23336),u=i(45974),p=Array.prototype;v.exports=function(d){var e=d.sort;return d===p||r(p,d)&&e===p.sort?u:e}},3555:(v,T,i)=>{var r=i(23336),u=i(68012),p=Array.prototype;v.exports=function(d){var e=d.splice;return d===p||r(p,d)&&e===p.splice?u:e}},68333:(v,T,i)=>{var r=i(23336),u=i(98720),p=String.prototype;v.exports=function(d){var e=d.startsWith;return"string"==typeof d||d===p||r(p,d)&&e===p.startsWith?u:e}},65786:(v,T,i)=>{var r=i(23336),u=i(75998),p=String.prototype;v.exports=function(d){var e=d.trim;return"string"==typeof d||d===p||r(p,d)&&e===p.trim?u:e}},66306:(v,T,i)=>{i(75071);var r=i(13544),u=i(2543);r.JSON||(r.JSON={stringify:JSON.stringify}),v.exports=function(d,e,_){return u(r.JSON.stringify,null,arguments)}},31845:(v,T,i)=>{i(1285),i(85140),i(17221),i(3934);var r=i(13544);v.exports=r.Map},44168:(v,T,i)=>{i(67234);var r=i(13544);v.exports=r.Object.assign},25852:(v,T,i)=>{i(86516);var u=i(13544).Object;v.exports=function(d,e){return u.create(d,e)}},24457:(v,T,i)=>{i(36255);var u=i(13544).Object,p=v.exports=function(e,_){return u.defineProperties(e,_)};u.defineProperties.sham&&(p.sham=!0)},99671:(v,T,i)=>{i(84468);var u=i(13544).Object,p=v.exports=function(e,_,y){return u.defineProperty(e,_,y)};u.defineProperty.sham&&(p.sham=!0)},35161:(v,T,i)=>{i(54989);var r=i(13544);v.exports=r.Object.entries},38007:(v,T,i)=>{i(86627);var u=i(13544).Object,p=v.exports=function(e,_){return u.getOwnPropertyDescriptor(e,_)};u.getOwnPropertyDescriptor.sham&&(p.sham=!0)},57432:(v,T,i)=>{i(78275);var r=i(13544);v.exports=r.Object.getOwnPropertyDescriptors},36541:(v,T,i)=>{i(56728);var r=i(13544);v.exports=r.Object.getOwnPropertySymbols},17303:(v,T,i)=>{i(31193);var r=i(13544);v.exports=r.Object.getPrototypeOf},62149:(v,T,i)=>{i(56557);var r=i(13544);v.exports=r.Object.keys},86537:(v,T,i)=>{i(17971);var r=i(13544);v.exports=r.Object.setPrototypeOf},79553:(v,T,i)=>{i(88923);var r=i(13544);v.exports=r.Object.values},80092:(v,T,i)=>{i(10901),i(1285),i(17221),i(66793),i(84798),i(98857),i(30185),i(3934);var r=i(13544);v.exports=r.Promise},472:(v,T,i)=>{i(19539);var r=i(13544);v.exports=r.Reflect.construct},4678:(v,T,i)=>{i(60851);var r=i(13544);v.exports=r.Reflect.get},85136:(v,T,i)=>{i(97764);var r=i(97911);v.exports=r("String").includes},96302:(v,T,i)=>{i(3588);var r=i(97911);v.exports=r("String").repeat},98720:(v,T,i)=>{i(24655);var r=i(97911);v.exports=r("String").startsWith},75998:(v,T,i)=>{i(90451);var r=i(97911);v.exports=r("String").trim},61697:(v,T,i)=>{i(1625),i(17221),i(56728),i(16426),i(1172),i(99579),i(41258),i(2383),i(44339),i(64776),i(88215),i(65389),i(12733),i(97977),i(59792),i(60242),i(26291),i(32300),i(63603),i(44864);var r=i(13544);v.exports=r.Symbol},42497:(v,T,i)=>{i(1285),i(17221),i(3934),i(2383);var r=i(89734);v.exports=r.f("iterator")},58255:(v,T,i)=>{i(1285),i(17221),i(90770);var r=i(13544);v.exports=r.WeakMap},56286:(v,T,i)=>{v.exports=i(73875)},54153:(v,T,i)=>{v.exports=i(91700)},90755:(v,T,i)=>{v.exports=i(70589)},60833:(v,T,i)=>{v.exports=i(6324)},98235:(v,T,i)=>{v.exports=i(71432)},78096:(v,T,i)=>{v.exports=i(73712)},31236:(v,T,i)=>{v.exports=i(58044)},15819:(v,T,i)=>{v.exports=i(55451)},44948:(v,T,i)=>{v.exports=i(61483)},96471:(v,T,i)=>{v.exports=i(46815)},5228:(v,T,i)=>{v.exports=i(28296)},50182:(v,T,i)=>{v.exports=i(96973)},41171:(v,T,i)=>{v.exports=i(47194)},54585:(v,T,i)=>{v.exports=i(56805)},62005:(v,T,i)=>{v.exports=i(32944)},39964:(v,T,i)=>{v.exports=i(70729)},70326:(v,T,i)=>{v.exports=i(48299)},98162:(v,T,i)=>{v.exports=i(33969)},42346:(v,T,i)=>{v.exports=i(26421)},4204:(v,T,i)=>{v.exports=i(37785)},24329:(v,T,i)=>{v.exports=i(15123)},2793:(v,T,i)=>{v.exports=i(49745)},42700:(v,T,i)=>{v.exports=i(29044)},70269:(v,T,i)=>{v.exports=i(20611)},88819:(v,T,i)=>{v.exports=i(65861)},55912:(v,T,i)=>{v.exports=i(63816)},73875:(v,T,i)=>{var r=i(43987);v.exports=r},91700:(v,T,i)=>{var r=i(99556);v.exports=r},70589:(v,T,i)=>{var r=i(39287);v.exports=r},6324:(v,T,i)=>{i(65237);var r=i(52964);v.exports=r},71432:(v,T,i)=>{var r=i(25272);v.exports=r},73712:(v,T,i)=>{var r=i(54450);v.exports=r},58044:(v,T,i)=>{var r=i(39557);v.exports=r},55451:(v,T,i)=>{var r=i(61611);v.exports=r},61483:(v,T,i)=>{var r=i(22549);v.exports=r},46815:(v,T,i)=>{var r=i(47646);v.exports=r},28296:(v,T,i)=>{var r=i(78663);i(78271),i(60854),i(10509),i(30887),i(54547),i(68996),i(1530),i(60176),i(41554),i(41688),i(92847),i(17316),i(58786),i(35517),i(12783),i(69773),i(22337),i(40199),i(69046),i(84131),v.exports=r},96973:(v,T,i)=>{var r=i(48498);v.exports=r},47194:(v,T,i)=>{var r=i(4922);v.exports=r},56805:(v,T,i)=>{var r=i(95190);v.exports=r},32944:(v,T,i)=>{var r=i(78525);v.exports=r},70729:(v,T,i)=>{var r=i(21064);v.exports=r},48299:(v,T,i)=>{var r=i(65641);v.exports=r},33969:(v,T,i)=>{var r=i(21693);v.exports=r},26421:(v,T,i)=>{var r=i(88907);v.exports=r},37785:(v,T,i)=>{var r=i(41432);v.exports=r},15123:(v,T,i)=>{var r=i(7398);v.exports=r},49745:(v,T,i)=>{var r=i(67221);i(67670),i(61127),i(93114),i(45975),v.exports=r},29044:(v,T,i)=>{var r=i(67447);v.exports=r},20611:(v,T,i)=>{var r=i(58811);v.exports=r},65861:(v,T,i)=>{var r=i(19573);i(55461),i(5737),i(87097),i(29559),i(90212),i(71985),i(93770),i(47743),v.exports=r},63816:(v,T,i)=>{var r=i(10226);v.exports=r},61812:(v,T,i)=>{var r=i(52208),u=i(7378),p=TypeError;v.exports=function(d){if(r(d))return d;throw p(u(d)+" is not a function")}},54356:(v,T,i)=>{var r=i(81177),u=i(7378),p=TypeError;v.exports=function(d){if(r(d))return d;throw p(u(d)+" is not a constructor")}},93221:(v,T,i)=>{var r=i(52208),u=String,p=TypeError;v.exports=function(d){if("object"==typeof d||r(d))return d;throw p("Can't set "+u(d)+" as a prototype")}},82196:v=>{v.exports=function(){}},54849:(v,T,i)=>{var r=i(23336),u=TypeError;v.exports=function(p,d){if(r(d,p))return p;throw u("Incorrect invocation")}},64562:(v,T,i)=>{var r=i(77293),u=String,p=TypeError;v.exports=function(d){if(r(d))return d;throw p(u(d)+" is not an object")}},76318:(v,T,i)=>{var r=i(55756);v.exports=r(function(){if("function"==typeof ArrayBuffer){var u=new ArrayBuffer(8);Object.isExtensible(u)&&Object.defineProperty(u,"a",{value:8})}})},35277:(v,T,i)=>{"use strict";var r=i(70267),u=i(19401),p=i(6381);v.exports=function(e){for(var _=r(this),y=p(_),S=arguments.length,A=u(S>1?arguments[1]:void 0,y),N=S>2?arguments[2]:void 0,L=void 0===N?y:u(N,y);L>A;)_[A++]=e;return _}},8366:(v,T,i)=>{"use strict";var r=i(68607).forEach,p=i(33620)("forEach");v.exports=p?[].forEach:function(e){return r(this,e,arguments.length>1?arguments[1]:void 0)}},51923:(v,T,i)=>{"use strict";var r=i(76781),u=i(25401),p=i(70267),d=i(93463),e=i(39918),_=i(81177),y=i(6381),S=i(46751),A=i(88055),N=i(34014),L=Array;v.exports=function(J){var K=p(J),ee=_(this),ue=arguments.length,ae=ue>1?arguments[1]:void 0,H=void 0!==ae;H&&(ae=r(ae,ue>2?arguments[2]:void 0));var ie,he,ge,De,ce,lt,se=N(K),Ee=0;if(!se||this===L&&e(se))for(ie=y(K),he=ee?new this(ie):L(ie);ie>Ee;Ee++)lt=H?ae(K[Ee],Ee):K[Ee],S(he,Ee,lt);else for(ce=(De=A(K,se)).next,he=ee?new this:[];!(ge=u(ce,De)).done;Ee++)lt=H?d(De,ae,[ge.value,Ee],!0):ge.value,S(he,Ee,lt);return he.length=Ee,he}},95171:(v,T,i)=>{var r=i(81010),u=i(19401),p=i(6381),d=function(e){return function(_,y,S){var Z,A=r(_),N=p(A),L=u(S,N);if(e&&y!=y){for(;N>L;)if((Z=A[L++])!=Z)return!0}else for(;N>L;L++)if((e||L in A)&&A[L]===y)return e||L||0;return!e&&-1}};v.exports={includes:d(!0),indexOf:d(!1)}},68607:(v,T,i)=>{var r=i(76781),u=i(23634),p=i(20973),d=i(70267),e=i(6381),_=i(2103),y=u([].push),S=function(A){var N=1==A,L=2==A,Z=3==A,J=4==A,K=6==A,ee=7==A,ue=5==A||K;return function(ae,H,se,Ee){for(var ze,Be,ie=d(ae),he=p(ie),ge=r(H,se),De=e(he),ce=0,lt=Ee||_,Ve=N?lt(ae,De):L||ee?lt(ae,0):void 0;De>ce;ce++)if((ue||ce in he)&&(Be=ge(ze=he[ce],ce,ie),A))if(N)Ve[ce]=Be;else if(Be)switch(A){case 3:return!0;case 5:return ze;case 6:return ce;case 2:y(Ve,ze)}else switch(A){case 4:return!1;case 7:y(Ve,ze)}return K?-1:Z||J?J:Ve}};v.exports={forEach:S(0),map:S(1),filter:S(2),some:S(3),every:S(4),find:S(5),findIndex:S(6),filterReject:S(7)}},78375:(v,T,i)=>{"use strict";var r=i(2543),u=i(81010),p=i(33912),d=i(6381),e=i(33620),_=Math.min,y=[].lastIndexOf,S=!!y&&1/[1].lastIndexOf(1,-0)<0,A=e("lastIndexOf");v.exports=S||!A?function(Z){if(S)return r(y,this,arguments)||0;var J=u(this),K=d(J),ee=K-1;for(arguments.length>1&&(ee=_(ee,p(arguments[1]))),ee<0&&(ee=K+ee);ee>=0;ee--)if(ee in J&&J[ee]===Z)return ee||0;return-1}:y},95913:(v,T,i)=>{var r=i(55756),u=i(91840),p=i(63556),d=u("species");v.exports=function(e){return p>=51||!r(function(){var _=[];return(_.constructor={})[d]=function(){return{foo:1}},1!==_[e](Boolean).foo})}},33620:(v,T,i)=>{"use strict";var r=i(55756);v.exports=function(u,p){var d=[][u];return!!d&&r(function(){d.call(null,p||function(){return 1},1)})}},88908:(v,T,i)=>{var r=i(61812),u=i(70267),p=i(20973),d=i(6381),e=TypeError,_=function(y){return function(S,A,N,L){r(A);var Z=u(S),J=p(Z),K=d(Z),ee=y?K-1:0,ue=y?-1:1;if(N<2)for(;;){if(ee in J){L=J[ee],ee+=ue;break}if(ee+=ue,y?ee<0:K<=ee)throw e("Reduce of empty array with no initial value")}for(;y?ee>=0:K>ee;ee+=ue)ee in J&&(L=A(L,J[ee],ee,Z));return L}};v.exports={left:_(!1),right:_(!0)}},54716:(v,T,i)=>{"use strict";var r=i(49642),u=i(89735),p=TypeError,d=Object.getOwnPropertyDescriptor,e=r&&!function(){if(void 0!==this)return!0;try{Object.defineProperty([],"length",{writable:!1}).length=1}catch(_){return _ instanceof TypeError}}();v.exports=e?function(_,y){if(u(_)&&!d(_,"length").writable)throw p("Cannot set read only .length");return _.length=y}:function(_,y){return _.length=y}},8681:(v,T,i)=>{var r=i(19401),u=i(6381),p=i(46751),d=Array,e=Math.max;v.exports=function(_,y,S){for(var A=u(_),N=r(y,A),L=r(void 0===S?A:S,A),Z=d(e(L-N,0)),J=0;N{var r=i(23634);v.exports=r([].slice)},84865:(v,T,i)=>{var r=i(8681),u=Math.floor,p=function(_,y){var S=_.length,A=u(S/2);return S<8?d(_,y):e(_,p(r(_,0,A),y),p(r(_,A),y),y)},d=function(_,y){for(var N,L,S=_.length,A=1;A0;)_[L]=_[--L];L!==A++&&(_[L]=N)}return _},e=function(_,y,S,A){for(var N=y.length,L=S.length,Z=0,J=0;Z{var r=i(89735),u=i(81177),p=i(77293),e=i(91840)("species"),_=Array;v.exports=function(y){var S;return r(y)&&(u(S=y.constructor)&&(S===_||r(S.prototype))||p(S)&&null===(S=S[e]))&&(S=void 0),void 0===S?_:S}},2103:(v,T,i)=>{var r=i(48045);v.exports=function(u,p){return new(r(u))(0===p?0:p)}},93463:(v,T,i)=>{var r=i(64562),u=i(40798);v.exports=function(p,d,e,_){try{return _?d(r(e)[0],e[1]):d(e)}catch(y){u(p,"throw",y)}}},5253:(v,T,i)=>{var u=i(91840)("iterator"),p=!1;try{var d=0,e={next:function(){return{done:!!d++}},return:function(){p=!0}};e[u]=function(){return this},Array.from(e,function(){throw 2})}catch(_){}v.exports=function(_,y){if(!y&&!p)return!1;var S=!1;try{var A={};A[u]=function(){return{next:function(){return{done:S=!0}}}},_(A)}catch(N){}return S}},49806:(v,T,i)=>{var r=i(23634),u=r({}.toString),p=r("".slice);v.exports=function(d){return p(u(d),8,-1)}},35329:(v,T,i)=>{var r=i(25014),u=i(52208),p=i(49806),e=i(91840)("toStringTag"),_=Object,y="Arguments"==p(function(){return arguments}());v.exports=r?p:function(A){var N,L,Z;return void 0===A?"Undefined":null===A?"Null":"string"==typeof(L=function(A,N){try{return A[N]}catch(L){}}(N=_(A),e))?L:y?p(N):"Object"==(Z=p(N))&&u(N.callee)?"Arguments":Z}},37353:(v,T,i)=>{"use strict";var r=i(25401),u=i(61812),p=i(64562);v.exports=function(){for(var S,e=p(this),_=u(e.delete),y=!0,A=0,N=arguments.length;A{"use strict";var r=i(76781),u=i(25401),p=i(61812),d=i(54356),e=i(43550),_=i(41605),y=[].push;v.exports=function(A){var Z,J,K,ee,N=arguments.length,L=N>1?arguments[1]:void 0;return d(this),(Z=void 0!==L)&&p(L),e(A)?new this:(J=[],Z?(K=0,ee=r(L,N>2?arguments[2]:void 0),_(A,function(ue){u(y,J,ee(ue,K++))})):_(A,y,{that:J}),new this(J))}},13067:(v,T,i)=>{"use strict";var r=i(37591);v.exports=function(){return new this(r(arguments))}},26650:(v,T,i)=>{"use strict";var r=i(48011).f,u=i(83272),p=i(84604),d=i(76781),e=i(54849),_=i(43550),y=i(41605),S=i(79077),A=i(28738),N=i(58014),L=i(49642),Z=i(57867).fastKey,J=i(91093),K=J.set,ee=J.getterFor;v.exports={getConstructor:function(ue,ae,H,se){var Ee=ue(function(ce,lt){e(ce,ie),K(ce,{type:ae,index:u(null),first:void 0,last:void 0,size:0}),L||(ce.size=0),_(lt)||y(lt,ce[se],{that:ce,AS_ENTRIES:H})}),ie=Ee.prototype,he=ee(ae),ge=function(ce,lt,Ve){var Pe,je,ze=he(ce),Be=De(ce,lt);return Be?Be.value=Ve:(ze.last=Be={index:je=Z(lt,!0),key:lt,value:Ve,previous:Pe=ze.last,next:void 0,removed:!1},ze.first||(ze.first=Be),Pe&&(Pe.next=Be),L?ze.size++:ce.size++,"F"!==je&&(ze.index[je]=Be)),ce},De=function(ce,lt){var Be,Ve=he(ce),ze=Z(lt);if("F"!==ze)return Ve.index[ze];for(Be=Ve.first;Be;Be=Be.next)if(Be.key==lt)return Be};return p(ie,{clear:function(){for(var Ve=he(this),ze=Ve.index,Be=Ve.first;Be;)Be.removed=!0,Be.previous&&(Be.previous=Be.previous.next=void 0),delete ze[Be.index],Be=Be.next;Ve.first=Ve.last=void 0,L?Ve.size=0:this.size=0},delete:function(ce){var Ve=he(this),ze=De(this,ce);if(ze){var Be=ze.next,Pe=ze.previous;delete Ve.index[ze.index],ze.removed=!0,Pe&&(Pe.next=Be),Be&&(Be.previous=Pe),Ve.first==ze&&(Ve.first=Be),Ve.last==ze&&(Ve.last=Pe),L?Ve.size--:this.size--}return!!ze},forEach:function(lt){for(var Be,Ve=he(this),ze=d(lt,arguments.length>1?arguments[1]:void 0);Be=Be?Be.next:Ve.first;)for(ze(Be.value,Be.key,this);Be&&Be.removed;)Be=Be.previous},has:function(lt){return!!De(this,lt)}}),p(ie,H?{get:function(lt){var Ve=De(this,lt);return Ve&&Ve.value},set:function(lt,Ve){return ge(this,0===lt?0:lt,Ve)}}:{add:function(lt){return ge(this,lt=0===lt?0:lt,lt)}}),L&&r(ie,"size",{get:function(){return he(this).size}}),Ee},setStrong:function(ue,ae,H){var se=ae+" Iterator",Ee=ee(ae),ie=ee(se);S(ue,ae,function(he,ge){K(this,{type:se,target:he,state:Ee(he),kind:ge,last:void 0})},function(){for(var he=ie(this),ge=he.kind,De=he.last;De&&De.removed;)De=De.previous;return he.target&&(he.last=De=De?De.next:he.state.first)?A("keys"==ge?De.key:"values"==ge?De.value:[De.key,De.value],!1):(he.target=void 0,A(void 0,!0))},H?"entries":"values",!H,!0),N(ae)}}},84049:(v,T,i)=>{"use strict";var r=i(23634),u=i(84604),p=i(57867).getWeakData,d=i(54849),e=i(64562),_=i(43550),y=i(77293),S=i(41605),A=i(68607),N=i(80112),L=i(91093),Z=L.set,J=L.getterFor,K=A.find,ee=A.findIndex,ue=r([].splice),ae=0,H=function(ie){return ie.frozen||(ie.frozen=new se)},se=function(){this.entries=[]},Ee=function(ie,he){return K(ie.entries,function(ge){return ge[0]===he})};se.prototype={get:function(ie){var he=Ee(this,ie);if(he)return he[1]},has:function(ie){return!!Ee(this,ie)},set:function(ie,he){var ge=Ee(this,ie);ge?ge[1]=he:this.entries.push([ie,he])},delete:function(ie){var he=ee(this.entries,function(ge){return ge[0]===ie});return~he&&ue(this.entries,he,1),!!~he}},v.exports={getConstructor:function(ie,he,ge,De){var ce=ie(function(Be,Pe){d(Be,lt),Z(Be,{type:he,id:ae++,frozen:void 0}),_(Pe)||S(Pe,Be[De],{that:Be,AS_ENTRIES:ge})}),lt=ce.prototype,Ve=J(he),ze=function(Be,Pe,je){var He=Ve(Be),Vt=p(e(Pe),!0);return!0===Vt?H(He).set(Pe,je):Vt[He.id]=je,Be};return u(lt,{delete:function(Be){var Pe=Ve(this);if(!y(Be))return!1;var je=p(Be);return!0===je?H(Pe).delete(Be):je&&N(je,Pe.id)&&delete je[Pe.id]},has:function(Pe){var je=Ve(this);if(!y(Pe))return!1;var He=p(Pe);return!0===He?H(je).has(Pe):He&&N(He,je.id)}}),u(lt,ge?{get:function(Pe){var je=Ve(this);if(y(Pe)){var He=p(Pe);return!0===He?H(je).get(Pe):He?He[je.id]:void 0}},set:function(Pe,je){return ze(this,Pe,je)}}:{add:function(Pe){return ze(this,Pe,!0)}}),ce}}},85116:(v,T,i)=>{"use strict";var r=i(90513),u=i(70009),p=i(57867),d=i(55756),e=i(65162),_=i(41605),y=i(54849),S=i(52208),A=i(77293),N=i(85681),L=i(48011).f,Z=i(68607).forEach,J=i(49642),K=i(91093),ee=K.set,ue=K.getterFor;v.exports=function(ae,H,se){var lt,Ee=-1!==ae.indexOf("Map"),ie=-1!==ae.indexOf("Weak"),he=Ee?"set":"add",ge=u[ae],De=ge&&ge.prototype,ce={};if(J&&S(ge)&&(ie||De.forEach&&!d(function(){(new ge).entries().next()}))){var Ve=(lt=H(function(Be,Pe){ee(y(Be,Ve),{type:ae,collection:new ge}),null!=Pe&&_(Pe,Be[he],{that:Be,AS_ENTRIES:Ee})})).prototype,ze=ue(ae);Z(["add","clear","delete","forEach","get","has","set","keys","values","entries"],function(Be){var Pe="add"==Be||"set"==Be;Be in De&&(!ie||"clear"!=Be)&&e(Ve,Be,function(je,He){var Vt=ze(this).collection;if(!Pe&&ie&&!A(je))return"get"==Be&&void 0;var it=Vt[Be](0===je?0:je,He);return Pe?this:it})}),ie||L(Ve,"size",{configurable:!0,get:function(){return ze(this).collection.size}})}else lt=se.getConstructor(H,ae,Ee,he),p.enable();return N(lt,ae,!1,!0),ce[ae]=lt,r({global:!0,forced:!0},ce),ie||se.setStrong(lt,ae,Ee),lt}},65031:(v,T,i)=>{var r=i(80112),u=i(59823),p=i(25525),d=i(48011);v.exports=function(e,_,y){for(var S=u(_),A=d.f,N=p.f,L=0;L{var u=i(91840)("match");v.exports=function(p){var d=/./;try{"/./"[p](d)}catch(e){try{return d[u]=!1,"/./"[p](d)}catch(_){}}return!1}},37112:(v,T,i)=>{var r=i(55756);v.exports=!r(function(){function u(){}return u.prototype.constructor=null,Object.getPrototypeOf(new u)!==u.prototype})},28738:v=>{v.exports=function(T,i){return{value:T,done:i}}},65162:(v,T,i)=>{var r=i(49642),u=i(48011),p=i(51361);v.exports=r?function(d,e,_){return u.f(d,e,p(1,_))}:function(d,e,_){return d[e]=_,d}},51361:v=>{v.exports=function(T,i){return{enumerable:!(1&T),configurable:!(2&T),writable:!(4&T),value:i}}},46751:(v,T,i)=>{"use strict";var r=i(62939),u=i(48011),p=i(51361);v.exports=function(d,e,_){var y=r(e);y in d?u.f(d,y,p(0,_)):d[y]=_}},1707:(v,T,i)=>{var r=i(48011);v.exports=function(u,p,d){return r.f(u,p,d)}},42915:(v,T,i)=>{var r=i(65162);v.exports=function(u,p,d,e){return e&&e.enumerable?u[p]=d:r(u,p,d),u}},84604:(v,T,i)=>{var r=i(42915);v.exports=function(u,p,d){for(var e in p)d&&d.unsafe&&u[e]?u[e]=p[e]:r(u,e,p[e],d);return u}},34056:(v,T,i)=>{var r=i(70009),u=Object.defineProperty;v.exports=function(p,d){try{u(r,p,{value:d,configurable:!0,writable:!0})}catch(e){r[p]=d}return d}},67236:(v,T,i)=>{"use strict";var r=i(7378),u=TypeError;v.exports=function(p,d){if(!delete p[d])throw u("Cannot delete property "+r(d)+" of "+r(p))}},49642:(v,T,i)=>{var r=i(55756);v.exports=!r(function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]})},96682:(v,T,i)=>{var r=i(70009),u=i(77293),p=r.document,d=u(p)&&u(p.createElement);v.exports=function(e){return d?p.createElement(e):{}}},11594:v=>{var T=TypeError;v.exports=function(r){if(r>9007199254740991)throw T("Maximum allowed index exceeded");return r}},44125:v=>{v.exports={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0}},36410:(v,T,i)=>{var u=i(86053).match(/firefox\/(\d+)/i);v.exports=!!u&&+u[1]},34008:(v,T,i)=>{var r=i(31813),u=i(3787);v.exports=!r&&!u&&"object"==typeof window&&"object"==typeof document},31813:v=>{v.exports="object"==typeof Deno&&Deno&&"object"==typeof Deno.version},5329:(v,T,i)=>{var r=i(86053);v.exports=/MSIE|Trident/.test(r)},16137:(v,T,i)=>{var r=i(86053),u=i(70009);v.exports=/ipad|iphone|ipod/i.test(r)&&void 0!==u.Pebble},3877:(v,T,i)=>{var r=i(86053);v.exports=/(?:ipad|iphone|ipod).*applewebkit/i.test(r)},3787:(v,T,i)=>{var r=i(49806),u=i(70009);v.exports="process"==r(u.process)},85308:(v,T,i)=>{var r=i(86053);v.exports=/web0s(?!.*chrome)/i.test(r)},86053:(v,T,i)=>{var r=i(7365);v.exports=r("navigator","userAgent")||""},63556:(v,T,i)=>{var y,S,r=i(70009),u=i(86053),p=r.process,d=r.Deno,e=p&&p.versions||d&&d.version,_=e&&e.v8;_&&(S=(y=_.split("."))[0]>0&&y[0]<4?1:+(y[0]+y[1])),!S&&u&&(!(y=u.match(/Edge\/(\d+)/))||y[1]>=74)&&(y=u.match(/Chrome\/(\d+)/))&&(S=+y[1]),v.exports=S},34545:(v,T,i)=>{var u=i(86053).match(/AppleWebKit\/(\d+)\./);v.exports=!!u&&+u[1]},97911:(v,T,i)=>{var r=i(13544);v.exports=function(u){return r[u+"Prototype"]}},44939:v=>{v.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},40039:(v,T,i)=>{var r=i(23634),u=Error,p=r("".replace),d=String(u("zxcasd").stack),e=/\n\s*at [^:]*:[^\n]*/,_=e.test(d);v.exports=function(y,S){if(_&&"string"==typeof y&&!u.prepareStackTrace)for(;S--;)y=p(y,e,"");return y}},50499:(v,T,i)=>{var r=i(55756),u=i(51361);v.exports=!r(function(){var p=Error("a");return!("stack"in p)||(Object.defineProperty(p,"stack",u(1,7)),7!==p.stack)})},90513:(v,T,i)=>{"use strict";var r=i(70009),u=i(2543),p=i(23634),d=i(52208),e=i(25525).f,_=i(79482),y=i(13544),S=i(76781),A=i(65162),N=i(80112),L=function(Z){var J=function(K,ee,ue){if(this instanceof J){switch(arguments.length){case 0:return new Z;case 1:return new Z(K);case 2:return new Z(K,ee)}return new Z(K,ee,ue)}return u(Z,this,arguments)};return J.prototype=Z.prototype,J};v.exports=function(Z,J){var he,ge,De,ce,lt,Ve,ze,Be,K=Z.target,ee=Z.global,ue=Z.stat,ae=Z.proto,H=ee?r:ue?r[K]:(r[K]||{}).prototype,se=ee?y:y[K]||A(y,K,{})[K],Ee=se.prototype;for(De in J)he=!_(ee?De:K+(ue?".":"#")+De,Z.forced)&&H&&N(H,De),lt=se[De],he&&(Ve=Z.dontCallGetSet?(Be=e(H,De))&&Be.value:H[De]),ce=he&&Ve?Ve:J[De],(!he||typeof lt!=typeof ce)&&(ze=Z.bind&&he?S(ce,r):Z.wrap&&he?L(ce):ae&&d(ce)?p(ce):ce,(Z.sham||ce&&ce.sham||lt&<.sham)&&A(ze,"sham",!0),A(se,De,ze),ae&&(N(y,ge=K+"Prototype")||A(y,ge,{}),A(y[ge],De,ce),Z.real&&Ee&&!Ee[De]&&A(Ee,De,ce)))}},55756:v=>{v.exports=function(T){try{return!!T()}catch(i){return!0}}},3124:(v,T,i)=>{var r=i(55756);v.exports=!r(function(){return Object.isExtensible(Object.preventExtensions({}))})},2543:(v,T,i)=>{var r=i(29046),u=Function.prototype,p=u.apply,d=u.call;v.exports="object"==typeof Reflect&&Reflect.apply||(r?d.bind(p):function(){return d.apply(p,arguments)})},76781:(v,T,i)=>{var r=i(23634),u=i(61812),p=i(29046),d=r(r.bind);v.exports=function(e,_){return u(e),void 0===_?e:p?d(e,_):function(){return e.apply(_,arguments)}}},29046:(v,T,i)=>{var r=i(55756);v.exports=!r(function(){var u=function(){}.bind();return"function"!=typeof u||u.hasOwnProperty("prototype")})},44197:(v,T,i)=>{"use strict";var r=i(23634),u=i(61812),p=i(77293),d=i(80112),e=i(37591),_=i(29046),y=Function,S=r([].concat),A=r([].join),N={},L=function(Z,J,K){if(!d(N,J)){for(var ee=[],ue=0;ue{var r=i(29046),u=Function.prototype.call;v.exports=r?u.bind(u):function(){return u.apply(u,arguments)}},29862:(v,T,i)=>{var r=i(49642),u=i(80112),p=Function.prototype,d=r&&Object.getOwnPropertyDescriptor,e=u(p,"name"),_=e&&"something"===function(){}.name,y=e&&(!r||r&&d(p,"name").configurable);v.exports={EXISTS:e,PROPER:_,CONFIGURABLE:y}},23634:(v,T,i)=>{var r=i(29046),u=Function.prototype,d=u.call,e=r&&u.bind.bind(d,d);v.exports=r?function(_){return _&&e(_)}:function(_){return _&&function(){return d.apply(_,arguments)}}},7365:(v,T,i)=>{var r=i(13544),u=i(70009),p=i(52208),d=function(e){return p(e)?e:void 0};v.exports=function(e,_){return arguments.length<2?d(r[e])||d(u[e]):r[e]&&r[e][_]||u[e]&&u[e][_]}},34014:(v,T,i)=>{var r=i(35329),u=i(34778),p=i(43550),d=i(84394),_=i(91840)("iterator");v.exports=function(y){if(!p(y))return u(y,_)||u(y,"@@iterator")||d[r(y)]}},88055:(v,T,i)=>{var r=i(25401),u=i(61812),p=i(64562),d=i(7378),e=i(34014),_=TypeError;v.exports=function(y,S){var A=arguments.length<2?e(y):S;if(u(A))return p(r(A,y));throw _(d(y)+" is not iterable")}},37444:(v,T,i)=>{var r=i(88055);v.exports=r},34778:(v,T,i)=>{var r=i(61812),u=i(43550);v.exports=function(p,d){var e=p[d];return u(e)?void 0:r(e)}},70009:v=>{var T=function(i){return i&&i.Math==Math&&i};v.exports=T("object"==typeof globalThis&&globalThis)||T("object"==typeof window&&window)||T("object"==typeof self&&self)||T("object"==typeof global&&global)||function(){return this}()||Function("return this")()},80112:(v,T,i)=>{var r=i(23634),u=i(70267),p=r({}.hasOwnProperty);v.exports=Object.hasOwn||function(e,_){return p(u(e),_)}},45599:v=>{v.exports={}},52912:(v,T,i)=>{var r=i(70009);v.exports=function(u,p){var d=r.console;d&&d.error&&(1==arguments.length?d.error(u):d.error(u,p))}},55690:(v,T,i)=>{var r=i(7365);v.exports=r("document","documentElement")},50495:(v,T,i)=>{var r=i(49642),u=i(55756),p=i(96682);v.exports=!r&&!u(function(){return 7!=Object.defineProperty(p("div"),"a",{get:function(){return 7}}).a})},20973:(v,T,i)=>{var r=i(23634),u=i(55756),p=i(49806),d=Object,e=r("".split);v.exports=u(function(){return!d("z").propertyIsEnumerable(0)})?function(_){return"String"==p(_)?e(_,""):d(_)}:d},26699:(v,T,i)=>{var r=i(23634),u=i(52208),p=i(24766),d=r(Function.toString);u(p.inspectSource)||(p.inspectSource=function(e){return d(e)}),v.exports=p.inspectSource},33411:(v,T,i)=>{var r=i(77293),u=i(65162);v.exports=function(p,d){r(d)&&"cause"in d&&u(p,"cause",d.cause)}},57867:(v,T,i)=>{var r=i(90513),u=i(23634),p=i(45599),d=i(77293),e=i(80112),_=i(48011).f,y=i(51518),S=i(62469),A=i(46401),N=i(13708),L=i(3124),Z=!1,J=N("meta"),K=0,ee=function(ie){_(ie,J,{value:{objectID:"O"+K++,weakData:{}}})},Ee=v.exports={enable:function(){Ee.enable=function(){},Z=!0;var ie=y.f,he=u([].splice),ge={};ge[J]=1,ie(ge).length&&(y.f=function(De){for(var ce=ie(De),lt=0,Ve=ce.length;lt{var J,K,ee,r=i(81101),u=i(70009),p=i(23634),d=i(77293),e=i(65162),_=i(80112),y=i(24766),S=i(86066),A=i(45599),N="Object already initialized",L=u.TypeError;if(r||y.state){var H=y.state||(y.state=new(0,u.WeakMap)),se=p(H.get),Ee=p(H.has),ie=p(H.set);J=function(ge,De){if(Ee(H,ge))throw L(N);return De.facade=ge,ie(H,ge,De),De},K=function(ge){return se(H,ge)||{}},ee=function(ge){return Ee(H,ge)}}else{var he=S("state");A[he]=!0,J=function(ge,De){if(_(ge,he))throw L(N);return De.facade=ge,e(ge,he,De),De},K=function(ge){return _(ge,he)?ge[he]:{}},ee=function(ge){return _(ge,he)}}v.exports={set:J,get:K,has:ee,enforce:function(ge){return ee(ge)?K(ge):J(ge,{})},getterFor:function(ge){return function(De){var ce;if(!d(De)||(ce=K(De)).type!==ge)throw L("Incompatible receiver, "+ge+" required");return ce}}}},39918:(v,T,i)=>{var r=i(91840),u=i(84394),p=r("iterator"),d=Array.prototype;v.exports=function(e){return void 0!==e&&(u.Array===e||d[p]===e)}},89735:(v,T,i)=>{var r=i(49806);v.exports=Array.isArray||function(p){return"Array"==r(p)}},52208:v=>{v.exports=function(T){return"function"==typeof T}},81177:(v,T,i)=>{var r=i(23634),u=i(55756),p=i(52208),d=i(35329),e=i(7365),_=i(26699),y=function(){},S=[],A=e("Reflect","construct"),N=/^\s*(?:class|function)\b/,L=r(N.exec),Z=!N.exec(y),J=function(ue){if(!p(ue))return!1;try{return A(y,S,ue),!0}catch(ae){return!1}},K=function(ue){if(!p(ue))return!1;switch(d(ue)){case"AsyncFunction":case"GeneratorFunction":case"AsyncGeneratorFunction":return!1}try{return Z||!!L(N,_(ue))}catch(ae){return!0}};K.sham=!0,v.exports=!A||u(function(){var ee;return J(J.call)||!J(Object)||!J(function(){ee=!0})||ee})?K:J},27029:(v,T,i)=>{var r=i(80112);v.exports=function(u){return void 0!==u&&(r(u,"value")||r(u,"writable"))}},79482:(v,T,i)=>{var r=i(55756),u=i(52208),p=/#|\.prototype\./,d=function(A,N){var L=_[e(A)];return L==S||L!=y&&(u(N)?r(N):!!N)},e=d.normalize=function(A){return String(A).replace(p,".").toLowerCase()},_=d.data={},y=d.NATIVE="N",S=d.POLYFILL="P";v.exports=d},43550:v=>{v.exports=function(T){return null==T}},77293:(v,T,i)=>{var r=i(52208),u="object"==typeof document&&document.all;v.exports=void 0===u&&void 0!==u?function(d){return"object"==typeof d?null!==d:r(d)||d===u}:function(d){return"object"==typeof d?null!==d:r(d)}},81124:v=>{v.exports=!0},60373:(v,T,i)=>{var r=i(77293),u=i(49806),d=i(91840)("match");v.exports=function(e){var _;return r(e)&&(void 0!==(_=e[d])?!!_:"RegExp"==u(e))}},74717:(v,T,i)=>{var r=i(7365),u=i(52208),p=i(23336),d=i(99554),e=Object;v.exports=d?function(_){return"symbol"==typeof _}:function(_){var y=r("Symbol");return u(y)&&p(y.prototype,e(_))}},41605:(v,T,i)=>{var r=i(76781),u=i(25401),p=i(64562),d=i(7378),e=i(39918),_=i(6381),y=i(23336),S=i(88055),A=i(34014),N=i(40798),L=TypeError,Z=function(K,ee){this.stopped=K,this.result=ee},J=Z.prototype;v.exports=function(K,ee,ue){var ge,De,ce,lt,Ve,ze,Be,H=!(!ue||!ue.AS_ENTRIES),se=!(!ue||!ue.IS_RECORD),Ee=!(!ue||!ue.IS_ITERATOR),ie=!(!ue||!ue.INTERRUPTED),he=r(ee,ue&&ue.that),Pe=function(He){return ge&&N(ge,"normal",He),new Z(!0,He)},je=function(He){return H?(p(He),ie?he(He[0],He[1],Pe):he(He[0],He[1])):ie?he(He,Pe):he(He)};if(se)ge=K.iterator;else if(Ee)ge=K;else{if(!(De=A(K)))throw L(d(K)+" is not iterable");if(e(De)){for(ce=0,lt=_(K);lt>ce;ce++)if((Ve=je(K[ce]))&&y(J,Ve))return Ve;return new Z(!1)}ge=S(K,De)}for(ze=se?K.next:ge.next;!(Be=u(ze,ge)).done;){try{Ve=je(Be.value)}catch(He){N(ge,"throw",He)}if("object"==typeof Ve&&Ve&&y(J,Ve))return Ve}return new Z(!1)}},40798:(v,T,i)=>{var r=i(25401),u=i(64562),p=i(34778);v.exports=function(d,e,_){var y,S;u(d);try{if(!(y=p(d,"return"))){if("throw"===e)throw _;return _}y=r(y,d)}catch(A){S=!0,y=A}if("throw"===e)throw _;if(S)throw y;return u(y),_}},14554:(v,T,i)=>{"use strict";var r=i(38432).IteratorPrototype,u=i(83272),p=i(51361),d=i(85681),e=i(84394),_=function(){return this};v.exports=function(y,S,A,N){var L=S+" Iterator";return y.prototype=u(r,{next:p(+!N,A)}),d(y,L,!1,!0),e[L]=_,y}},79077:(v,T,i)=>{"use strict";var r=i(90513),u=i(25401),p=i(81124),d=i(29862),e=i(52208),_=i(14554),y=i(31426),S=i(54945),A=i(85681),N=i(65162),L=i(42915),Z=i(91840),J=i(84394),K=i(38432),ee=d.PROPER,ue=d.CONFIGURABLE,ae=K.IteratorPrototype,H=K.BUGGY_SAFARI_ITERATORS,se=Z("iterator"),ie="values",he="entries",ge=function(){return this};v.exports=function(De,ce,lt,Ve,ze,Be,Pe){_(lt,ce,Ve);var Ut,Bt,bt,je=function(Gt){if(Gt===ze&&It)return It;if(!H&&Gt in it)return it[Gt];switch(Gt){case"keys":case ie:case he:return function(){return new lt(this,Gt)}}return function(){return new lt(this)}},He=ce+" Iterator",Vt=!1,it=De.prototype,tn=it[se]||it["@@iterator"]||ze&&it[ze],It=!H&&tn||je(ze),Zt="Array"==ce&&it.entries||tn;if(Zt&&(Ut=y(Zt.call(new De)))!==Object.prototype&&Ut.next&&(!p&&y(Ut)!==ae&&(S?S(Ut,ae):e(Ut[se])||L(Ut,se,ge)),A(Ut,He,!0,!0),p&&(J[He]=ge)),ee&&ze==ie&&tn&&tn.name!==ie&&(!p&&ue?N(it,"name",ie):(Vt=!0,It=function(){return u(tn,this)})),ze)if(Bt={values:je(ie),keys:Be?It:je("keys"),entries:je(he)},Pe)for(bt in Bt)(H||Vt||!(bt in it))&&L(it,bt,Bt[bt]);else r({target:ce,proto:!0,forced:H||Vt},Bt);return(!p||Pe)&&it[se]!==It&&L(it,se,It,{name:ze}),J[ce]=It,Bt}},38432:(v,T,i)=>{"use strict";var L,Z,J,r=i(55756),u=i(52208),p=i(77293),d=i(83272),e=i(31426),_=i(42915),y=i(91840),S=i(81124),A=y("iterator"),N=!1;[].keys&&("next"in(J=[].keys())?(Z=e(e(J)))!==Object.prototype&&(L=Z):N=!0),!p(L)||r(function(){var ee={};return L[A].call(ee)!==ee})?L={}:S&&(L=d(L)),u(L[A])||_(L,A,function(){return this}),v.exports={IteratorPrototype:L,BUGGY_SAFARI_ITERATORS:N}},84394:v=>{v.exports={}},6381:(v,T,i)=>{var r=i(48869);v.exports=function(u){return r(u.length)}},12864:(v,T,i)=>{"use strict";var r=i(25401),u=i(61812),p=i(64562);v.exports=function(e,_){var L,Z,y=p(this),S=u(y.get),A=u(y.has),N=u(y.set);return r(A,y,e)?(L=r(S,y,e),"update"in _&&(L=_.update(L,e,y),r(N,y,e,L)),L):(Z=_.insert(e,y),r(N,y,e,Z),Z)}},57729:(v,T,i)=>{"use strict";var r=i(25401),u=i(61812),p=i(52208),d=i(64562),e=TypeError;v.exports=function(y,S){var K,A=d(this),N=u(A.get),L=u(A.has),Z=u(A.set),J=arguments.length>2?arguments[2]:void 0;if(!p(S)&&!p(J))throw e("At least one callback required");return r(L,A,y)?(K=r(N,A,y),p(S)&&(K=S(K),r(Z,A,y,K))):p(J)&&(K=J(),r(Z,A,y,K)),K}},8651:v=>{var T=Math.ceil,i=Math.floor;v.exports=Math.trunc||function(u){var p=+u;return(p>0?i:T)(p)}},58991:(v,T,i)=>{var ee,ue,ae,H,se,Ee,ie,he,r=i(70009),u=i(76781),p=i(25525).f,d=i(37352).set,e=i(3877),_=i(16137),y=i(85308),S=i(3787),A=r.MutationObserver||r.WebKitMutationObserver,N=r.document,L=r.process,Z=r.Promise,J=p(r,"queueMicrotask"),K=J&&J.value;K||(ee=function(){var ge,De;for(S&&(ge=L.domain)&&ge.exit();ue;){De=ue.fn,ue=ue.next;try{De()}catch(ce){throw ue?H():ae=void 0,ce}}ae=void 0,ge&&ge.enter()},e||S||y||!A||!N?!_&&Z&&Z.resolve?((ie=Z.resolve(void 0)).constructor=Z,he=u(ie.then,ie),H=function(){he(ee)}):S?H=function(){L.nextTick(ee)}:(d=u(d,r),H=function(){d(ee)}):(se=!0,Ee=N.createTextNode(""),new A(ee).observe(Ee,{characterData:!0}),H=function(){Ee.data=se=!se})),v.exports=K||function(ge){var De={fn:ge,next:void 0};ae&&(ae.next=De),ue||(ue=De,H()),ae=De}},54256:(v,T,i)=>{"use strict";var r=i(61812),u=TypeError,p=function(d){var e,_;this.promise=new d(function(y,S){if(void 0!==e||void 0!==_)throw u("Bad Promise constructor");e=y,_=S}),this.resolve=r(e),this.reject=r(_)};v.exports.f=function(d){return new p(d)}},63313:(v,T,i)=>{var r=i(41433);v.exports=function(u,p){return void 0===u?arguments.length<2?"":p:r(u)}},56421:(v,T,i)=>{var r=i(60373),u=TypeError;v.exports=function(p){if(r(p))throw u("The method doesn't accept regular expressions");return p}},75791:(v,T,i)=>{"use strict";var r=i(49642),u=i(23634),p=i(25401),d=i(55756),e=i(28474),_=i(47238),y=i(25558),S=i(70267),A=i(20973),N=Object.assign,L=Object.defineProperty,Z=u([].concat);v.exports=!N||d(function(){if(r&&1!==N({b:1},N(L({},"a",{enumerable:!0,get:function(){L(this,"b",{value:3,enumerable:!1})}}),{b:2})).b)return!0;var J={},K={},ee=Symbol(),ue="abcdefghijklmnopqrst";return J[ee]=7,ue.split("").forEach(function(ae){K[ae]=ae}),7!=N({},J)[ee]||e(N({},K)).join("")!=ue})?function(K,ee){for(var ue=S(K),ae=arguments.length,H=1,se=_.f,Ee=y.f;ae>H;)for(var ce,ie=A(arguments[H++]),he=se?Z(e(ie),se(ie)):e(ie),ge=he.length,De=0;ge>De;)ce=he[De++],(!r||p(Ee,ie,ce))&&(ue[ce]=ie[ce]);return ue}:N},83272:(v,T,i)=>{var ae,r=i(64562),u=i(25913),p=i(44939),d=i(45599),e=i(55690),_=i(96682),y=i(86066),N="prototype",L="script",Z=y("IE_PROTO"),J=function(){},K=function(se){return"<"+L+">"+se+""},ee=function(se){se.write(K("")),se.close();var Ee=se.parentWindow.Object;return se=null,Ee},H=function(){try{ae=new ActiveXObject("htmlfile")}catch(Ee){}H="undefined"!=typeof document?document.domain&&ae?ee(ae):function(){var ie,se=_("iframe");return se.style.display="none",e.appendChild(se),se.src=String("javascript:"),(ie=se.contentWindow.document).open(),ie.write(K("document.F=Object")),ie.close(),ie.F}():ee(ae);for(var se=p.length;se--;)delete H[N][p[se]];return H()};d[Z]=!0,v.exports=Object.create||function(Ee,ie){var he;return null!==Ee?(J[N]=r(Ee),he=new J,J[N]=null,he[Z]=Ee):he=H(),void 0===ie?he:u.f(he,ie)}},25913:(v,T,i)=>{var r=i(49642),u=i(47960),p=i(48011),d=i(64562),e=i(81010),_=i(28474);T.f=r&&!u?Object.defineProperties:function(S,A){d(S);for(var K,N=e(A),L=_(A),Z=L.length,J=0;Z>J;)p.f(S,K=L[J++],N[K]);return S}},48011:(v,T,i)=>{var r=i(49642),u=i(50495),p=i(47960),d=i(64562),e=i(62939),_=TypeError,y=Object.defineProperty,S=Object.getOwnPropertyDescriptor,A="enumerable",N="configurable",L="writable";T.f=r?p?function(J,K,ee){if(d(J),K=e(K),d(ee),"function"==typeof J&&"prototype"===K&&"value"in ee&&L in ee&&!ee[L]){var ue=S(J,K);ue&&ue[L]&&(J[K]=ee.value,ee={configurable:N in ee?ee[N]:ue[N],enumerable:A in ee?ee[A]:ue[A],writable:!1})}return y(J,K,ee)}:y:function(J,K,ee){if(d(J),K=e(K),d(ee),u)try{return y(J,K,ee)}catch(ue){}if("get"in ee||"set"in ee)throw _("Accessors not supported");return"value"in ee&&(J[K]=ee.value),J}},25525:(v,T,i)=>{var r=i(49642),u=i(25401),p=i(25558),d=i(51361),e=i(81010),_=i(62939),y=i(80112),S=i(50495),A=Object.getOwnPropertyDescriptor;T.f=r?A:function(L,Z){if(L=e(L),Z=_(Z),S)try{return A(L,Z)}catch(J){}if(y(L,Z))return d(!u(p.f,L,Z),L[Z])}},62469:(v,T,i)=>{var r=i(49806),u=i(81010),p=i(51518).f,d=i(8681),e="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[];v.exports.f=function(S){return e&&"Window"==r(S)?function(y){try{return p(y)}catch(S){return d(e)}}(S):p(u(S))}},51518:(v,T,i)=>{var r=i(66250),p=i(44939).concat("length","prototype");T.f=Object.getOwnPropertyNames||function(e){return r(e,p)}},47238:(v,T)=>{T.f=Object.getOwnPropertySymbols},31426:(v,T,i)=>{var r=i(80112),u=i(52208),p=i(70267),d=i(86066),e=i(37112),_=d("IE_PROTO"),y=Object,S=y.prototype;v.exports=e?y.getPrototypeOf:function(A){var N=p(A);if(r(N,_))return N[_];var L=N.constructor;return u(L)&&N instanceof L?L.prototype:N instanceof y?S:null}},46401:(v,T,i)=>{var r=i(55756),u=i(77293),p=i(49806),d=i(76318),e=Object.isExtensible,_=r(function(){e(1)});v.exports=_||d?function(S){return!(!u(S)||d&&"ArrayBuffer"==p(S))&&(!e||e(S))}:e},23336:(v,T,i)=>{var r=i(23634);v.exports=r({}.isPrototypeOf)},66250:(v,T,i)=>{var r=i(23634),u=i(80112),p=i(81010),d=i(95171).indexOf,e=i(45599),_=r([].push);v.exports=function(y,S){var Z,A=p(y),N=0,L=[];for(Z in A)!u(e,Z)&&u(A,Z)&&_(L,Z);for(;S.length>N;)u(A,Z=S[N++])&&(~d(L,Z)||_(L,Z));return L}},28474:(v,T,i)=>{var r=i(66250),u=i(44939);v.exports=Object.keys||function(d){return r(d,u)}},25558:(v,T)=>{"use strict";var i={}.propertyIsEnumerable,r=Object.getOwnPropertyDescriptor,u=r&&!i.call({1:2},1);T.f=u?function(d){var e=r(this,d);return!!e&&e.enumerable}:i},54945:(v,T,i)=>{var r=i(23634),u=i(64562),p=i(93221);v.exports=Object.setPrototypeOf||("__proto__"in{}?function(){var _,d=!1,e={};try{(_=r(Object.getOwnPropertyDescriptor(Object.prototype,"__proto__").set))(e,[]),d=e instanceof Array}catch(y){}return function(S,A){return u(S),p(A),d?_(S,A):S.__proto__=A,S}}():void 0)},36805:(v,T,i)=>{var r=i(49642),u=i(23634),p=i(28474),d=i(81010),_=u(i(25558).f),y=u([].push),S=function(A){return function(N){for(var ue,L=d(N),Z=p(L),J=Z.length,K=0,ee=[];J>K;)ue=Z[K++],(!r||_(L,ue))&&y(ee,A?[ue,L[ue]]:L[ue]);return ee}};v.exports={entries:S(!0),values:S(!1)}},97686:(v,T,i)=>{"use strict";var r=i(25014),u=i(35329);v.exports=r?{}.toString:function(){return"[object "+u(this)+"]"}},71689:(v,T,i)=>{var r=i(25401),u=i(52208),p=i(77293),d=TypeError;v.exports=function(e,_){var y,S;if("string"===_&&u(y=e.toString)&&!p(S=r(y,e))||u(y=e.valueOf)&&!p(S=r(y,e))||"string"!==_&&u(y=e.toString)&&!p(S=r(y,e)))return S;throw d("Can't convert object to primitive value")}},59823:(v,T,i)=>{var r=i(7365),u=i(23634),p=i(51518),d=i(47238),e=i(64562),_=u([].concat);v.exports=r("Reflect","ownKeys")||function(S){var A=p.f(e(S)),N=d.f;return N?_(A,N(S)):A}},13544:v=>{v.exports={}},26975:v=>{v.exports=function(T){try{return{error:!1,value:T()}}catch(i){return{error:!0,value:i}}}},9936:(v,T,i)=>{var r=i(70009),u=i(46456),p=i(52208),d=i(79482),e=i(26699),_=i(91840),y=i(34008),S=i(31813),A=i(81124),N=i(63556),L=u&&u.prototype,Z=_("species"),J=!1,K=p(r.PromiseRejectionEvent),ee=d("Promise",function(){var ue=e(u),ae=ue!==String(u);if(!ae&&66===N||A&&(!L.catch||!L.finally))return!0;if(!N||N<51||!/native code/.test(ue)){var H=new u(function(ie){ie(1)}),se=function(ie){ie(function(){},function(){})};if((H.constructor={})[Z]=se,!(J=H.then(function(){})instanceof se))return!0}return!ae&&(y||S)&&!K});v.exports={CONSTRUCTOR:ee,REJECTION_EVENT:K,SUBCLASSING:J}},46456:(v,T,i)=>{var r=i(70009);v.exports=r.Promise},25524:(v,T,i)=>{var r=i(64562),u=i(77293),p=i(54256);v.exports=function(d,e){if(r(d),u(e)&&e.constructor===d)return e;var _=p.f(d);return(0,_.resolve)(e),_.promise}},95758:(v,T,i)=>{var r=i(46456),u=i(5253),p=i(9936).CONSTRUCTOR;v.exports=p||!u(function(d){r.all(d).then(void 0,function(){})})},70918:v=>{var T=function(){this.head=null,this.tail=null};T.prototype={add:function(i){var r={item:i,next:null};this.head?this.tail.next=r:this.head=r,this.tail=r},get:function(){var i=this.head;if(i)return this.head=i.next,this.tail===i&&(this.tail=null),i.item}},v.exports=T},67917:(v,T,i)=>{var r=i(43550),u=TypeError;v.exports=function(p){if(r(p))throw u("Can't call method on "+p);return p}},29627:v=>{v.exports=function(T,i){return T===i||T!=T&&i!=i}},53814:(v,T,i)=>{var r=i(70009),u=i(2543),p=i(52208),d=i(86053),e=i(37591),_=i(15086),y=/MSIE .\./.test(d),S=r.Function,A=function(N){return y?function(L,Z){var J=_(arguments.length,1)>2,K=p(L)?L:S(L),ee=J?e(arguments,2):void 0;return N(J?function(){u(K,this,ee)}:K,Z)}:N};v.exports={setTimeout:A(r.setTimeout),setInterval:A(r.setInterval)}},58014:(v,T,i)=>{"use strict";var r=i(7365),u=i(48011),p=i(91840),d=i(49642),e=p("species");v.exports=function(_){var y=r(_);d&&y&&!y[e]&&(0,u.f)(y,e,{configurable:!0,get:function(){return this}})}},85681:(v,T,i)=>{var r=i(25014),u=i(48011).f,p=i(65162),d=i(80112),e=i(97686),y=i(91840)("toStringTag");v.exports=function(S,A,N,L){if(S){var Z=N?S:S.prototype;d(Z,y)||u(Z,y,{configurable:!0,value:A}),L&&!r&&p(Z,"toString",e)}}},86066:(v,T,i)=>{var r=i(64579),u=i(13708),p=r("keys");v.exports=function(d){return p[d]||(p[d]=u(d))}},24766:(v,T,i)=>{var r=i(70009),u=i(34056),p="__core-js_shared__",d=r[p]||u(p,{});v.exports=d},64579:(v,T,i)=>{var r=i(81124),u=i(24766);(v.exports=function(p,d){return u[p]||(u[p]=void 0!==d?d:{})})("versions",[]).push({version:"3.25.1",mode:r?"pure":"global",copyright:"\xa9 2014-2022 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.25.1/LICENSE",source:"https://github.com/zloirock/core-js"})},95869:(v,T,i)=>{var r=i(64562),u=i(54356),p=i(43550),e=i(91840)("species");v.exports=function(_,y){var A,S=r(_).constructor;return void 0===S||p(A=r(S)[e])?y:u(A)}},61557:(v,T,i)=>{var r=i(23634),u=i(33912),p=i(41433),d=i(67917),e=r("".charAt),_=r("".charCodeAt),y=r("".slice),S=function(A){return function(N,L){var ee,ue,Z=p(d(N)),J=u(L),K=Z.length;return J<0||J>=K?A?"":void 0:(ee=_(Z,J))<55296||ee>56319||J+1===K||(ue=_(Z,J+1))<56320||ue>57343?A?e(Z,J):ee:A?y(Z,J,J+2):ue-56320+(ee-55296<<10)+65536}};v.exports={codeAt:S(!1),charAt:S(!0)}},26662:(v,T,i)=>{"use strict";var r=i(23634),u=2147483647,L=/[^\0-\u007E]/,Z=/[.\u3002\uFF0E\uFF61]/g,J="Overflow: input needs wider integers to process",ee=RangeError,ue=r(Z.exec),ae=Math.floor,H=String.fromCharCode,se=r("".charCodeAt),Ee=r([].join),ie=r([].push),he=r("".replace),ge=r("".split),De=r("".toLowerCase),lt=function(Be){return Be+22+75*(Be<26)},Ve=function(Be,Pe,je){var He=0;for(Be=je?ae(Be/700):Be>>1,Be+=ae(Be/Pe);Be>455;)Be=ae(Be/35),He+=36;return ae(He+36*Be/(Be+38))},ze=function(Be){var tn,It,Pe=[],je=(Be=function(Be){for(var Pe=[],je=0,He=Be.length;je=55296&&Vt<=56319&&je=He&&Itae((u-Vt)/bt))throw ee(J);for(Vt+=(Bt-He)*bt,He=Bt,tn=0;tnu)throw ee(J);if(It==He){for(var Gt=Vt,xt=36;;){var Xt=xt<=it?1:xt>=it+26?26:xt-it;if(Gt{"use strict";var r=i(33912),u=i(41433),p=i(67917),d=RangeError;v.exports=function(_){var y=u(p(this)),S="",A=r(_);if(A<0||A==1/0)throw d("Wrong number of repetitions");for(;A>0;(A>>>=1)&&(y+=y))1&A&&(S+=y);return S}},85462:(v,T,i)=>{var r=i(29862).PROPER,u=i(55756),p=i(88185);v.exports=function(e){return u(function(){return!!p[e]()||"\u200b\x85\u180e"!=="\u200b\x85\u180e"[e]()||r&&p[e].name!==e})}},89858:(v,T,i)=>{var r=i(23634),u=i(67917),p=i(41433),d=i(88185),e=r("".replace),_="["+d+"]",y=RegExp("^"+_+_+"*"),S=RegExp(_+_+"*$"),A=function(N){return function(L){var Z=p(u(L));return 1&N&&(Z=e(Z,y,"")),2&N&&(Z=e(Z,S,"")),Z}};v.exports={start:A(1),end:A(2),trim:A(3)}},98535:(v,T,i)=>{var r=i(63556),u=i(55756);v.exports=!!Object.getOwnPropertySymbols&&!u(function(){var p=Symbol();return!String(p)||!(Object(p)instanceof Symbol)||!Symbol.sham&&r&&r<41})},56992:(v,T,i)=>{var r=i(25401),u=i(7365),p=i(91840),d=i(42915);v.exports=function(){var e=u("Symbol"),_=e&&e.prototype,y=_&&_.valueOf,S=p("toPrimitive");_&&!_[S]&&d(_,S,function(A){return r(y,this)},{arity:1})}},56709:(v,T,i)=>{var r=i(98535);v.exports=r&&!!Symbol.for&&!!Symbol.keyFor},37352:(v,T,i)=>{var ge,De,ce,lt,r=i(70009),u=i(2543),p=i(76781),d=i(52208),e=i(80112),_=i(55756),y=i(55690),S=i(37591),A=i(96682),N=i(15086),L=i(3877),Z=i(3787),J=r.setImmediate,K=r.clearImmediate,ee=r.process,ue=r.Dispatch,ae=r.Function,H=r.MessageChannel,se=r.String,Ee=0,ie={},he="onreadystatechange";try{ge=r.location}catch(je){}var Ve=function(je){if(e(ie,je)){var He=ie[je];delete ie[je],He()}},ze=function(je){return function(){Ve(je)}},Be=function(je){Ve(je.data)},Pe=function(je){r.postMessage(se(je),ge.protocol+"//"+ge.host)};(!J||!K)&&(J=function(He){N(arguments.length,1);var Vt=d(He)?He:ae(He),it=S(arguments,1);return ie[++Ee]=function(){u(Vt,void 0,it)},De(Ee),Ee},K=function(He){delete ie[He]},Z?De=function(je){ee.nextTick(ze(je))}:ue&&ue.now?De=function(je){ue.now(ze(je))}:H&&!L?(lt=(ce=new H).port2,ce.port1.onmessage=Be,De=p(lt.postMessage,lt)):r.addEventListener&&d(r.postMessage)&&!r.importScripts&&ge&&"file:"!==ge.protocol&&!_(Pe)?(De=Pe,r.addEventListener("message",Be,!1)):De=he in A("script")?function(je){y.appendChild(A("script"))[he]=function(){y.removeChild(this),Ve(je)}}:function(je){setTimeout(ze(je),0)}),v.exports={set:J,clear:K}},19401:(v,T,i)=>{var r=i(33912),u=Math.max,p=Math.min;v.exports=function(d,e){var _=r(d);return _<0?u(_+e,0):p(_,e)}},81010:(v,T,i)=>{var r=i(20973),u=i(67917);v.exports=function(p){return r(u(p))}},33912:(v,T,i)=>{var r=i(8651);v.exports=function(u){var p=+u;return p!=p||0===p?0:r(p)}},48869:(v,T,i)=>{var r=i(33912),u=Math.min;v.exports=function(p){return p>0?u(r(p),9007199254740991):0}},70267:(v,T,i)=>{var r=i(67917),u=Object;v.exports=function(p){return u(r(p))}},1645:(v,T,i)=>{var r=i(25401),u=i(77293),p=i(74717),d=i(34778),e=i(71689),_=i(91840),y=TypeError,S=_("toPrimitive");v.exports=function(A,N){if(!u(A)||p(A))return A;var Z,L=d(A,S);if(L){if(void 0===N&&(N="default"),Z=r(L,A,N),!u(Z)||p(Z))return Z;throw y("Can't convert object to primitive value")}return void 0===N&&(N="number"),e(A,N)}},62939:(v,T,i)=>{var r=i(1645),u=i(74717);v.exports=function(p){var d=r(p,"string");return u(d)?d:d+""}},25014:(v,T,i)=>{var p={};p[i(91840)("toStringTag")]="z",v.exports="[object z]"===String(p)},41433:(v,T,i)=>{var r=i(35329),u=String;v.exports=function(p){if("Symbol"===r(p))throw TypeError("Cannot convert a Symbol value to a string");return u(p)}},7378:v=>{var T=String;v.exports=function(i){try{return T(i)}catch(r){return"Object"}}},13708:(v,T,i)=>{var r=i(23634),u=0,p=Math.random(),d=r(1..toString);v.exports=function(e){return"Symbol("+(void 0===e?"":e)+")_"+d(++u+p,36)}},54933:(v,T,i)=>{var r=i(55756),u=i(91840),p=i(81124),d=u("iterator");v.exports=!r(function(){var e=new URL("b?a=1&b=2&c=3","http://a"),_=e.searchParams,y="";return e.pathname="c%20d",_.forEach(function(S,A){_.delete("b"),y+=A+S}),p&&!e.toJSON||!_.sort||"http://a/c%20d?a=1&c=3"!==e.href||"3"!==_.get("c")||"a=1"!==String(new URLSearchParams("?a=1"))||!_[d]||"a"!==new URL("https://a@b").username||"b"!==new URLSearchParams(new URLSearchParams("a=b")).get("a")||"xn--e1aybc"!==new URL("http://\u0442\u0435\u0441\u0442").host||"#%D0%B1"!==new URL("http://a#\u0431").hash||"a1c3"!==y||"x"!==new URL("http://x",void 0).host})},99554:(v,T,i)=>{var r=i(98535);v.exports=r&&!Symbol.sham&&"symbol"==typeof Symbol.iterator},47960:(v,T,i)=>{var r=i(49642),u=i(55756);v.exports=r&&u(function(){return 42!=Object.defineProperty(function(){},"prototype",{value:42,writable:!1}).prototype})},15086:v=>{var T=TypeError;v.exports=function(i,r){if(i{var r=i(70009),u=i(52208),p=r.WeakMap;v.exports=u(p)&&/native code/.test(String(p))},25374:(v,T,i)=>{var r=i(13544),u=i(80112),p=i(89734),d=i(48011).f;v.exports=function(e){var _=r.Symbol||(r.Symbol={});u(_,e)||d(_,e,{value:p.f(e)})}},89734:(v,T,i)=>{var r=i(91840);T.f=r},91840:(v,T,i)=>{var r=i(70009),u=i(64579),p=i(80112),d=i(13708),e=i(98535),_=i(99554),y=u("wks"),S=r.Symbol,A=S&&S.for,N=_?S:S&&S.withoutSetter||d;v.exports=function(L){if(!p(y,L)||!e&&"string"!=typeof y[L]){var Z="Symbol."+L;y[L]=e&&p(S,L)?S[L]:_&&A?A(Z):N(Z)}return y[L]}},88185:v=>{v.exports="\t\n\v\f\r \xa0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\ufeff"},70210:(v,T,i)=>{"use strict";var r=i(90513),u=i(23336),p=i(31426),d=i(54945),e=i(65031),_=i(83272),y=i(65162),S=i(51361),A=i(40039),N=i(33411),L=i(41605),Z=i(63313),J=i(91840),K=i(50499),ee=J("toStringTag"),ue=Error,ae=[].push,H=function(ie,he){var ce,ge=arguments.length>2?arguments[2]:void 0,De=u(se,this);d?ce=d(ue(),De?p(this):se):(ce=De?this:_(se),y(ce,ee,"Error")),void 0!==he&&y(ce,"message",Z(he)),K&&y(ce,"stack",A(ce.stack,1)),N(ce,ge);var lt=[];return L(ie,ae,{that:lt}),y(ce,"errors",lt),ce};d?d(H,ue):e(H,ue,{name:!0});var se=H.prototype=_(ue.prototype,{constructor:S(1,H),message:S(1,""),name:S(1,"AggregateError")});r({global:!0,constructor:!0,arity:2},{AggregateError:H})},10901:(v,T,i)=>{i(70210)},1625:(v,T,i)=>{"use strict";var r=i(90513),u=i(55756),p=i(89735),d=i(77293),e=i(70267),_=i(6381),y=i(11594),S=i(46751),A=i(2103),N=i(95913),L=i(91840),Z=i(63556),J=L("isConcatSpreadable"),K=Z>=51||!u(function(){var H=[];return H[J]=!1,H.concat()[0]!==H}),ee=N("concat"),ue=function(H){if(!d(H))return!1;var se=H[J];return void 0!==se?!!se:p(H)};r({target:"Array",proto:!0,arity:1,forced:!K||!ee},{concat:function(se){var ge,De,ce,lt,Ve,Ee=e(this),ie=A(Ee,0),he=0;for(ge=-1,ce=arguments.length;ge{"use strict";var r=i(90513),u=i(68607).every;r({target:"Array",proto:!0,forced:!i(33620)("every")},{every:function(_){return u(this,_,arguments.length>1?arguments[1]:void 0)}})},24990:(v,T,i)=>{var r=i(90513),u=i(35277),p=i(82196);r({target:"Array",proto:!0},{fill:u}),p("fill")},56534:(v,T,i)=>{"use strict";var r=i(90513),u=i(68607).filter;r({target:"Array",proto:!0,forced:!i(95913)("filter")},{filter:function(_){return u(this,_,arguments.length>1?arguments[1]:void 0)}})},12773:(v,T,i)=>{"use strict";var r=i(90513),u=i(68607).findIndex,p=i(82196),d="findIndex",e=!0;d in[]&&Array(1)[d](function(){e=!1}),r({target:"Array",proto:!0,forced:e},{findIndex:function(y){return u(this,y,arguments.length>1?arguments[1]:void 0)}}),p(d)},60326:(v,T,i)=>{"use strict";var r=i(90513),u=i(68607).find,p=i(82196),d="find",e=!0;d in[]&&Array(1)[d](function(){e=!1}),r({target:"Array",proto:!0,forced:e},{find:function(y){return u(this,y,arguments.length>1?arguments[1]:void 0)}}),p(d)},98792:(v,T,i)=>{"use strict";var r=i(90513),u=i(8366);r({target:"Array",proto:!0,forced:[].forEach!=u},{forEach:u})},261:(v,T,i)=>{var r=i(90513),u=i(51923);r({target:"Array",stat:!0,forced:!i(5253)(function(e){Array.from(e)})},{from:u})},77059:(v,T,i)=>{"use strict";var r=i(90513),u=i(95171).includes,p=i(55756),d=i(82196);r({target:"Array",proto:!0,forced:p(function(){return!Array(1).includes()})},{includes:function(y){return u(this,y,arguments.length>1?arguments[1]:void 0)}}),d("includes")},2795:(v,T,i)=>{"use strict";var r=i(90513),u=i(23634),p=i(95171).indexOf,d=i(33620),e=u([].indexOf),_=!!e&&1/e([1],1,-0)<0,y=d("indexOf");r({target:"Array",proto:!0,forced:_||!y},{indexOf:function(A){var N=arguments.length>1?arguments[1]:void 0;return _?e(this,A,N)||0:p(this,A,N)}})},2862:(v,T,i)=>{i(90513)({target:"Array",stat:!0},{isArray:i(89735)})},1285:(v,T,i)=>{"use strict";var r=i(81010),u=i(82196),p=i(84394),d=i(91093),e=i(48011).f,_=i(79077),y=i(28738),S=i(81124),A=i(49642),N="Array Iterator",L=d.set,Z=d.getterFor(N);v.exports=_(Array,"Array",function(K,ee){L(this,{type:N,target:r(K),index:0,kind:ee})},function(){var K=Z(this),ee=K.target,ue=K.kind,ae=K.index++;return!ee||ae>=ee.length?(K.target=void 0,y(void 0,!0)):y("keys"==ue?ae:"values"==ue?ee[ae]:[ae,ee[ae]],!1)},"values");var J=p.Arguments=p.Array;if(u("keys"),u("values"),u("entries"),!S&&A&&"values"!==J.name)try{e(J,"name",{value:"values"})}catch(K){}},74926:(v,T,i)=>{var r=i(90513),u=i(78375);r({target:"Array",proto:!0,forced:u!==[].lastIndexOf},{lastIndexOf:u})},88119:(v,T,i)=>{"use strict";var r=i(90513),u=i(68607).map;r({target:"Array",proto:!0,forced:!i(95913)("map")},{map:function(_){return u(this,_,arguments.length>1?arguments[1]:void 0)}})},46250:(v,T,i)=>{"use strict";var r=i(90513),u=i(88908).left,p=i(33620),d=i(63556),e=i(3787);r({target:"Array",proto:!0,forced:!p("reduce")||!e&&d>79&&d<83},{reduce:function(A){var N=arguments.length;return u(this,A,N,N>1?arguments[1]:void 0)}})},32836:(v,T,i)=>{"use strict";var r=i(90513),u=i(23634),p=i(89735),d=u([].reverse),e=[1,2];r({target:"Array",proto:!0,forced:String(e)===String(e.reverse())},{reverse:function(){return p(this)&&(this.length=this.length),d(this)}})},72999:(v,T,i)=>{"use strict";var r=i(90513),u=i(89735),p=i(81177),d=i(77293),e=i(19401),_=i(6381),y=i(81010),S=i(46751),A=i(91840),N=i(95913),L=i(37591),Z=N("slice"),J=A("species"),K=Array,ee=Math.max;r({target:"Array",proto:!0,forced:!Z},{slice:function(ae,H){var ge,De,ce,se=y(this),Ee=_(se),ie=e(ae,Ee),he=e(void 0===H?Ee:H,Ee);if(u(se)&&((p(ge=se.constructor)&&(ge===K||u(ge.prototype))||d(ge)&&null===(ge=ge[J]))&&(ge=void 0),ge===K||void 0===ge))return L(se,ie,he);for(De=new(void 0===ge?K:ge)(ee(he-ie,0)),ce=0;ie{"use strict";var r=i(90513),u=i(68607).some;r({target:"Array",proto:!0,forced:!i(33620)("some")},{some:function(_){return u(this,_,arguments.length>1?arguments[1]:void 0)}})},93639:(v,T,i)=>{"use strict";var r=i(90513),u=i(23634),p=i(61812),d=i(70267),e=i(6381),_=i(67236),y=i(41433),S=i(55756),A=i(84865),N=i(33620),L=i(36410),Z=i(5329),J=i(63556),K=i(34545),ee=[],ue=u(ee.sort),ae=u(ee.push),H=S(function(){ee.sort(void 0)}),se=S(function(){ee.sort(null)}),Ee=N("sort"),ie=!S(function(){if(J)return J<70;if(!(L&&L>3)){if(Z)return!0;if(K)return K<603;var ce,lt,Ve,ze,De="";for(ce=65;ce<76;ce++){switch(lt=String.fromCharCode(ce),ce){case 66:case 69:case 70:case 72:Ve=3;break;case 68:case 71:Ve=4;break;default:Ve=2}for(ze=0;ze<47;ze++)ee.push({k:lt+ze,v:Ve})}for(ee.sort(function(Be,Pe){return Pe.v-Be.v}),ze=0;zey(lt)?1:-1}}(ce)),Be=e(Ve),Pe=0;Pe{"use strict";var r=i(90513),u=i(70267),p=i(19401),d=i(33912),e=i(6381),_=i(54716),y=i(11594),S=i(2103),A=i(46751),N=i(67236),Z=i(95913)("splice"),J=Math.max,K=Math.min;r({target:"Array",proto:!0,forced:!Z},{splice:function(ue,ae){var he,ge,De,ce,lt,Ve,H=u(this),se=e(H),Ee=p(ue,se),ie=arguments.length;for(0===ie?he=ge=0:1===ie?(he=0,ge=se-Ee):(he=ie-2,ge=K(J(d(ae),0),se-Ee)),y(se+he-ge),De=S(H,ge),ce=0;cese-ge+he;ce--)N(H,ce-1)}else if(he>ge)for(ce=se-ge;ce>Ee;ce--)Ve=ce+he-1,(lt=ce+ge-1)in H?H[Ve]=H[lt]:N(H,Ve);for(ce=0;ce{var r=i(90513),u=i(23634),p=Date,d=u(p.prototype.getTime);r({target:"Date",stat:!0},{now:function(){return d(new p)}})},33379:(v,T,i)=>{var r=i(90513),u=i(44197);r({target:"Function",proto:!0,forced:Function.bind!==u},{bind:u})},87404:(v,T,i)=>{var r=i(90513),u=i(70009);r({global:!0,forced:u.globalThis!==u},{globalThis:u})},75071:(v,T,i)=>{var r=i(90513),u=i(7365),p=i(2543),d=i(25401),e=i(23634),_=i(55756),y=i(89735),S=i(52208),A=i(77293),N=i(74717),L=i(37591),Z=i(98535),J=u("JSON","stringify"),K=e(/./.exec),ee=e("".charAt),ue=e("".charCodeAt),ae=e("".replace),H=e(1..toString),se=/[\uD800-\uDFFF]/g,Ee=/^[\uD800-\uDBFF]$/,ie=/^[\uDC00-\uDFFF]$/,he=!Z||_(function(){var lt=u("Symbol")();return"[null]"!=J([lt])||"{}"!=J({a:lt})||"{}"!=J(Object(lt))}),ge=_(function(){return'"\\udf06\\ud834"'!==J("\udf06\ud834")||'"\\udead"'!==J("\udead")}),De=function(lt,Ve){var ze=L(arguments),Be=Ve;if((A(Ve)||void 0!==lt)&&!N(lt))return y(Ve)||(Ve=function(Pe,je){if(S(Be)&&(je=d(Be,this,Pe,je)),!N(je))return je}),ze[1]=Ve,p(J,null,ze)},ce=function(lt,Ve,ze){var Be=ee(ze,Ve-1),Pe=ee(ze,Ve+1);return K(Ee,lt)&&!K(ie,Pe)||K(ie,lt)&&!K(Ee,Be)?"\\u"+H(ue(lt,0),16):lt};J&&r({target:"JSON",stat:!0,arity:3,forced:he||ge},{stringify:function(Ve,ze,Be){var Pe=L(arguments),je=p(he?De:J,null,Pe);return ge&&"string"==typeof je?ae(je,se,ce):je}})},32300:(v,T,i)=>{var r=i(70009);i(85681)(r.JSON,"JSON",!0)},83616:(v,T,i)=>{"use strict";i(85116)("Map",function(p){return function(){return p(this,arguments.length?arguments[0]:void 0)}},i(26650))},85140:(v,T,i)=>{i(83616)},63603:()=>{},67234:(v,T,i)=>{var r=i(90513),u=i(75791);r({target:"Object",stat:!0,arity:2,forced:Object.assign!==u},{assign:u})},86516:(v,T,i)=>{i(90513)({target:"Object",stat:!0,sham:!i(49642)},{create:i(83272)})},36255:(v,T,i)=>{var r=i(90513),u=i(49642),p=i(25913).f;r({target:"Object",stat:!0,forced:Object.defineProperties!==p,sham:!u},{defineProperties:p})},84468:(v,T,i)=>{var r=i(90513),u=i(49642),p=i(48011).f;r({target:"Object",stat:!0,forced:Object.defineProperty!==p,sham:!u},{defineProperty:p})},54989:(v,T,i)=>{var r=i(90513),u=i(36805).entries;r({target:"Object",stat:!0},{entries:function(d){return u(d)}})},86627:(v,T,i)=>{var r=i(90513),u=i(55756),p=i(81010),d=i(25525).f,e=i(49642),_=u(function(){d(1)});r({target:"Object",stat:!0,forced:!e||_,sham:!e},{getOwnPropertyDescriptor:function(A,N){return d(p(A),N)}})},78275:(v,T,i)=>{var r=i(90513),u=i(49642),p=i(59823),d=i(81010),e=i(25525),_=i(46751);r({target:"Object",stat:!0,sham:!u},{getOwnPropertyDescriptors:function(S){for(var K,ee,A=d(S),N=e.f,L=p(A),Z={},J=0;L.length>J;)void 0!==(ee=N(A,K=L[J++]))&&_(Z,K,ee);return Z}})},37764:(v,T,i)=>{var r=i(90513),u=i(98535),p=i(55756),d=i(47238),e=i(70267);r({target:"Object",stat:!0,forced:!u||p(function(){d.f(1)})},{getOwnPropertySymbols:function(S){var A=d.f;return A?A(e(S)):[]}})},31193:(v,T,i)=>{var r=i(90513),u=i(55756),p=i(70267),d=i(31426),e=i(37112);r({target:"Object",stat:!0,forced:u(function(){d(1)}),sham:!e},{getPrototypeOf:function(S){return d(p(S))}})},56557:(v,T,i)=>{var r=i(90513),u=i(70267),p=i(28474);r({target:"Object",stat:!0,forced:i(55756)(function(){p(1)})},{keys:function(y){return p(u(y))}})},17971:(v,T,i)=>{i(90513)({target:"Object",stat:!0},{setPrototypeOf:i(54945)})},17221:()=>{},88923:(v,T,i)=>{var r=i(90513),u=i(36805).values;r({target:"Object",stat:!0},{values:function(d){return u(d)}})},84798:(v,T,i)=>{"use strict";var r=i(90513),u=i(25401),p=i(61812),d=i(54256),e=i(26975),_=i(41605);r({target:"Promise",stat:!0},{allSettled:function(S){var A=this,N=d.f(A),L=N.resolve,Z=N.reject,J=e(function(){var K=p(A.resolve),ee=[],ue=0,ae=1;_(S,function(H){var se=ue++,Ee=!1;ae++,u(K,A,H).then(function(ie){Ee||(Ee=!0,ee[se]={status:"fulfilled",value:ie},--ae||L(ee))},function(ie){Ee||(Ee=!0,ee[se]={status:"rejected",reason:ie},--ae||L(ee))})}),--ae||L(ee)});return J.error&&Z(J.value),N.promise}})},58085:(v,T,i)=>{"use strict";var r=i(90513),u=i(25401),p=i(61812),d=i(54256),e=i(26975),_=i(41605);r({target:"Promise",stat:!0,forced:i(95758)},{all:function(A){var N=this,L=d.f(N),Z=L.resolve,J=L.reject,K=e(function(){var ee=p(N.resolve),ue=[],ae=0,H=1;_(A,function(se){var Ee=ae++,ie=!1;H++,u(ee,N,se).then(function(he){ie||(ie=!0,ue[Ee]=he,--H||Z(ue))},J)}),--H||Z(ue)});return K.error&&J(K.value),L.promise}})},98857:(v,T,i)=>{"use strict";var r=i(90513),u=i(25401),p=i(61812),d=i(7365),e=i(54256),_=i(26975),y=i(41605),S="No one promise resolved";r({target:"Promise",stat:!0},{any:function(N){var L=this,Z=d("AggregateError"),J=e.f(L),K=J.resolve,ee=J.reject,ue=_(function(){var ae=p(L.resolve),H=[],se=0,Ee=1,ie=!1;y(N,function(he){var ge=se++,De=!1;Ee++,u(ae,L,he).then(function(ce){De||ie||(ie=!0,K(ce))},function(ce){De||ie||(De=!0,H[ge]=ce,--Ee||ee(new Z(H,S)))})}),--Ee||ee(new Z(H,S))});return ue.error&&ee(ue.value),J.promise}})},5846:(v,T,i)=>{"use strict";var r=i(90513),u=i(81124),p=i(9936).CONSTRUCTOR,d=i(46456),e=i(7365),_=i(52208),y=i(42915),S=d&&d.prototype;if(r({target:"Promise",proto:!0,forced:p,real:!0},{catch:function(N){return this.then(void 0,N)}}),!u&&_(d)){var A=e("Promise").prototype.catch;S.catch!==A&&y(S,"catch",A,{unsafe:!0})}},38206:(v,T,i)=>{"use strict";var di,Lr,Kr,r=i(90513),u=i(81124),p=i(3787),d=i(70009),e=i(25401),_=i(42915),y=i(54945),S=i(85681),A=i(58014),N=i(61812),L=i(52208),Z=i(77293),J=i(54849),K=i(95869),ee=i(37352).set,ue=i(58991),ae=i(52912),H=i(26975),se=i(70918),Ee=i(91093),ie=i(46456),he=i(9936),ge=i(54256),De="Promise",ce=he.CONSTRUCTOR,lt=he.REJECTION_EVENT,Ve=he.SUBCLASSING,ze=Ee.getterFor(De),Be=Ee.set,Pe=ie&&ie.prototype,je=ie,He=Pe,Vt=d.TypeError,it=d.document,tn=d.process,It=ge.f,Zt=It,Ut=!!(it&&it.createEvent&&d.dispatchEvent),Bt="unhandledrejection",ei=function(Wt){var zn;return!(!Z(Wt)||!L(zn=Wt.then))&&zn},Nn=function(Wt,zn){var br,Dr,gn,rr=zn.value,Fr=1==zn.state,Gn=Fr?Wt.ok:Wt.fail,Jr=Wt.resolve,_i=Wt.reject,wi=Wt.domain;try{Gn?(Fr||(2===zn.rejection&&ki(zn),zn.rejection=1),!0===Gn?br=rr:(wi&&wi.enter(),br=Gn(rr),wi&&(wi.exit(),gn=!0)),br===Wt.promise?_i(Vt("Promise-chain cycle")):(Dr=ei(br))?e(Dr,br,Jr,_i):Jr(br)):_i(rr)}catch(yn){wi&&!gn&&wi.exit(),_i(yn)}},$n=function(Wt,zn){Wt.notified||(Wt.notified=!0,ue(function(){for(var Fr,rr=Wt.reactions;Fr=rr.get();)Nn(Fr,Wt);Wt.notified=!1,zn&&!Wt.rejection&&Yr(Wt)}))},Br=function(Wt,zn,rr){var Fr,Gn;Ut?((Fr=it.createEvent("Event")).promise=zn,Fr.reason=rr,Fr.initEvent(Wt,!1,!0),d.dispatchEvent(Fr)):Fr={promise:zn,reason:rr},!lt&&(Gn=d["on"+Wt])?Gn(Fr):Wt===Bt&&ae("Unhandled promise rejection",rr)},Yr=function(Wt){e(ee,d,function(){var Gn,zn=Wt.facade,rr=Wt.value;if(fi(Wt)&&(Gn=H(function(){p?tn.emit("unhandledRejection",rr,zn):Br(Bt,zn,rr)}),Wt.rejection=p||fi(Wt)?2:1,Gn.error))throw Gn.value})},fi=function(Wt){return 1!==Wt.rejection&&!Wt.parent},ki=function(Wt){e(ee,d,function(){var zn=Wt.facade;p?tn.emit("rejectionHandled",zn):Br("rejectionhandled",zn,Wt.value)})},Hi=function(Wt,zn,rr){return function(Fr){Wt(zn,Fr,rr)}},Zr=function(Wt,zn,rr){Wt.done||(Wt.done=!0,rr&&(Wt=rr),Wt.value=zn,Wt.state=2,$n(Wt,!0))},Cn=function(Wt,zn,rr){if(!Wt.done){Wt.done=!0,rr&&(Wt=rr);try{if(Wt.facade===zn)throw Vt("Promise can't be resolved itself");var Fr=ei(zn);Fr?ue(function(){var Gn={done:!1};try{e(Fr,zn,Hi(Cn,Gn,Wt),Hi(Zr,Gn,Wt))}catch(Jr){Zr(Gn,Jr,Wt)}}):(Wt.value=zn,Wt.state=1,$n(Wt,!1))}catch(Gn){Zr({done:!1},Gn,Wt)}}};if(ce&&(je=function(zn){J(this,He),N(zn),e(di,this);var rr=ze(this);try{zn(Hi(Cn,rr),Hi(Zr,rr))}catch(Fr){Zr(rr,Fr)}},(di=function(zn){Be(this,{type:De,done:!1,notified:!1,parent:!1,reactions:new se,rejection:!1,state:0,value:void 0})}).prototype=_(He=je.prototype,"then",function(zn,rr){var Fr=ze(this),Gn=It(K(this,je));return Fr.parent=!0,Gn.ok=!L(zn)||zn,Gn.fail=L(rr)&&rr,Gn.domain=p?tn.domain:void 0,0==Fr.state?Fr.reactions.add(Gn):ue(function(){Nn(Gn,Fr)}),Gn.promise}),Lr=function(){var Wt=new di,zn=ze(Wt);this.promise=Wt,this.resolve=Hi(Cn,zn),this.reject=Hi(Zr,zn)},ge.f=It=function(Wt){return Wt===je||void 0===Wt?new Lr(Wt):Zt(Wt)},!u&&L(ie)&&Pe!==Object.prototype)){Kr=Pe.then,Ve||_(Pe,"then",function(zn,rr){var Fr=this;return new je(function(Gn,Jr){e(Kr,Fr,Gn,Jr)}).then(zn,rr)},{unsafe:!0});try{delete Pe.constructor}catch(Wt){}y&&y(Pe,He)}r({global:!0,constructor:!0,wrap:!0,forced:ce},{Promise:je}),S(je,De,!1,!0),A(De)},30185:(v,T,i)=>{"use strict";var r=i(90513),u=i(81124),p=i(46456),d=i(55756),e=i(7365),_=i(52208),y=i(95869),S=i(25524),A=i(42915),N=p&&p.prototype;if(r({target:"Promise",proto:!0,real:!0,forced:!!p&&d(function(){N.finally.call({then:function(){}},function(){})})},{finally:function(J){var K=y(this,e("Promise")),ee=_(J);return this.then(ee?function(ue){return S(K,J()).then(function(){return ue})}:J,ee?function(ue){return S(K,J()).then(function(){throw ue})}:J)}}),!u&&_(p)){var Z=e("Promise").prototype.finally;N.finally!==Z&&A(N,"finally",Z,{unsafe:!0})}},66793:(v,T,i)=>{i(38206),i(58085),i(5846),i(44738),i(74767),i(4991)},44738:(v,T,i)=>{"use strict";var r=i(90513),u=i(25401),p=i(61812),d=i(54256),e=i(26975),_=i(41605);r({target:"Promise",stat:!0,forced:i(95758)},{race:function(A){var N=this,L=d.f(N),Z=L.reject,J=e(function(){var K=p(N.resolve);_(A,function(ee){u(K,N,ee).then(L.resolve,Z)})});return J.error&&Z(J.value),L.promise}})},74767:(v,T,i)=>{"use strict";var r=i(90513),u=i(25401),p=i(54256);r({target:"Promise",stat:!0,forced:i(9936).CONSTRUCTOR},{reject:function(_){var y=p.f(this);return u(y.reject,void 0,_),y.promise}})},4991:(v,T,i)=>{"use strict";var r=i(90513),u=i(7365),p=i(81124),d=i(46456),e=i(9936).CONSTRUCTOR,_=i(25524),y=u("Promise"),S=p&&!e;r({target:"Promise",stat:!0,forced:p||e},{resolve:function(N){return _(S&&this===y?d:this,N)}})},19539:(v,T,i)=>{var r=i(90513),u=i(7365),p=i(2543),d=i(44197),e=i(54356),_=i(64562),y=i(77293),S=i(83272),A=i(55756),N=u("Reflect","construct"),L=Object.prototype,Z=[].push,J=A(function(){function ue(){}return!(N(function(){},[],ue)instanceof ue)}),K=!A(function(){N(function(){})}),ee=J||K;r({target:"Reflect",stat:!0,forced:ee,sham:ee},{construct:function(ae,H){e(ae),_(H);var se=arguments.length<3?ae:e(arguments[2]);if(K&&!J)return N(ae,H,se);if(ae==se){switch(H.length){case 0:return new ae;case 1:return new ae(H[0]);case 2:return new ae(H[0],H[1]);case 3:return new ae(H[0],H[1],H[2]);case 4:return new ae(H[0],H[1],H[2],H[3])}var Ee=[null];return p(Z,Ee,H),new(p(d,ae,Ee))}var ie=se.prototype,he=S(y(ie)?ie:L),ge=p(ae,he,H);return y(ge)?ge:he}})},60851:(v,T,i)=>{var r=i(90513),u=i(25401),p=i(77293),d=i(64562),e=i(27029),_=i(25525),y=i(31426);r({target:"Reflect",stat:!0},{get:function S(A,N){var Z,J,L=arguments.length<3?A:arguments[2];return d(A)===L?A[N]:(Z=_.f(A,N))?e(Z)?Z.value:void 0===Z.get?void 0:u(Z.get,L):p(J=y(A))?S(J,N,L):void 0}})},44864:()=>{},97764:(v,T,i)=>{"use strict";var r=i(90513),u=i(23634),p=i(56421),d=i(67917),e=i(41433),_=i(79668),y=u("".indexOf);r({target:"String",proto:!0,forced:!_("includes")},{includes:function(A){return!!~y(e(d(this)),e(p(A)),arguments.length>1?arguments[1]:void 0)}})},3934:(v,T,i)=>{"use strict";var r=i(61557).charAt,u=i(41433),p=i(91093),d=i(79077),e=i(28738),_="String Iterator",y=p.set,S=p.getterFor(_);d(String,"String",function(A){y(this,{type:_,string:u(A),index:0})},function(){var J,N=S(this),L=N.string,Z=N.index;return Z>=L.length?e(void 0,!0):(J=r(L,Z),N.index+=J.length,e(J,!1))})},3588:(v,T,i)=>{i(90513)({target:"String",proto:!0},{repeat:i(53411)})},24655:(v,T,i)=>{"use strict";var ee,r=i(90513),u=i(23634),p=i(25525).f,d=i(48869),e=i(41433),_=i(56421),y=i(67917),S=i(79668),A=i(81124),N=u("".startsWith),L=u("".slice),Z=Math.min,J=S("startsWith");r({target:"String",proto:!0,forced:!(!A&&!J&&(ee=p(String.prototype,"startsWith"),ee&&!ee.writable)||J)},{startsWith:function(ue){var ae=e(y(this));_(ue);var H=d(Z(arguments.length>1?arguments[1]:void 0,ae.length)),se=e(ue);return N?N(ae,se,H):L(ae,H,H+se.length)===se}})},90451:(v,T,i)=>{"use strict";var r=i(90513),u=i(89858).trim;r({target:"String",proto:!0,forced:i(85462)("trim")},{trim:function(){return u(this)}})},16426:(v,T,i)=>{i(25374)("asyncIterator")},17858:(v,T,i)=>{"use strict";var r=i(90513),u=i(70009),p=i(25401),d=i(23634),e=i(81124),_=i(49642),y=i(98535),S=i(55756),A=i(80112),N=i(23336),L=i(64562),Z=i(81010),J=i(62939),K=i(41433),ee=i(51361),ue=i(83272),ae=i(28474),H=i(51518),se=i(62469),Ee=i(47238),ie=i(25525),he=i(48011),ge=i(25913),De=i(25558),ce=i(42915),lt=i(64579),Ve=i(86066),ze=i(45599),Be=i(13708),Pe=i(91840),je=i(89734),He=i(25374),Vt=i(56992),it=i(85681),tn=i(91093),It=i(68607).forEach,Zt=Ve("hidden"),Ut="Symbol",Bt="prototype",bt=tn.set,Gt=tn.getterFor(Ut),xt=Object[Bt],Xt=u.Symbol,Zn=Xt&&Xt[Bt],Ur=u.TypeError,di=u.QObject,Lr=ie.f,Mr=he.f,Kr=se.f,ei=De.f,Nn=d([].push),$n=lt("symbols"),Br=lt("op-symbols"),Yr=lt("wks"),fi=!di||!di[Bt]||!di[Bt].findChild,ki=_&&S(function(){return 7!=ue(Mr({},"a",{get:function(){return Mr(this,"a",{value:7}).a}})).a})?function(Jr,_i,wi){var br=Lr(xt,_i);br&&delete xt[_i],Mr(Jr,_i,wi),br&&Jr!==xt&&Mr(xt,_i,br)}:Mr,Hi=function(Jr,_i){var wi=$n[Jr]=ue(Zn);return bt(wi,{type:Ut,tag:Jr,description:_i}),_||(wi.description=_i),wi},Zr=function(_i,wi,br){_i===xt&&Zr(Br,wi,br),L(_i);var Dr=J(wi);return L(br),A($n,Dr)?(br.enumerable?(A(_i,Zt)&&_i[Zt][Dr]&&(_i[Zt][Dr]=!1),br=ue(br,{enumerable:ee(0,!1)})):(A(_i,Zt)||Mr(_i,Zt,ee(1,{})),_i[Zt][Dr]=!0),ki(_i,Dr,br)):Mr(_i,Dr,br)},Cn=function(_i,wi){L(_i);var br=Z(wi),Dr=ae(br).concat(Gn(br));return It(Dr,function(gn){(!_||p(zn,br,gn))&&Zr(_i,gn,br[gn])}),_i},zn=function(_i){var wi=J(_i),br=p(ei,this,wi);return!(this===xt&&A($n,wi)&&!A(Br,wi))&&(!(br||!A(this,wi)||!A($n,wi)||A(this,Zt)&&this[Zt][wi])||br)},rr=function(_i,wi){var br=Z(_i),Dr=J(wi);if(br!==xt||!A($n,Dr)||A(Br,Dr)){var gn=Lr(br,Dr);return gn&&A($n,Dr)&&!(A(br,Zt)&&br[Zt][Dr])&&(gn.enumerable=!0),gn}},Fr=function(_i){var wi=Kr(Z(_i)),br=[];return It(wi,function(Dr){!A($n,Dr)&&!A(ze,Dr)&&Nn(br,Dr)}),br},Gn=function(Jr){var _i=Jr===xt,wi=Kr(_i?Br:Z(Jr)),br=[];return It(wi,function(Dr){A($n,Dr)&&(!_i||A(xt,Dr))&&Nn(br,$n[Dr])}),br};y||(ce(Zn=(Xt=function(){if(N(Zn,this))throw Ur("Symbol is not a constructor");var _i=arguments.length&&void 0!==arguments[0]?K(arguments[0]):void 0,wi=Be(_i),br=function(Dr){this===xt&&p(br,Br,Dr),A(this,Zt)&&A(this[Zt],wi)&&(this[Zt][wi]=!1),ki(this,wi,ee(1,Dr))};return _&&fi&&ki(xt,wi,{configurable:!0,set:br}),Hi(wi,_i)})[Bt],"toString",function(){return Gt(this).tag}),ce(Xt,"withoutSetter",function(Jr){return Hi(Be(Jr),Jr)}),De.f=zn,he.f=Zr,ge.f=Cn,ie.f=rr,H.f=se.f=Fr,Ee.f=Gn,je.f=function(Jr){return Hi(Pe(Jr),Jr)},_&&(Mr(Zn,"description",{configurable:!0,get:function(){return Gt(this).description}}),e||ce(xt,"propertyIsEnumerable",zn,{unsafe:!0}))),r({global:!0,constructor:!0,wrap:!0,forced:!y,sham:!y},{Symbol:Xt}),It(ae(Yr),function(Jr){He(Jr)}),r({target:Ut,stat:!0,forced:!y},{useSetter:function(){fi=!0},useSimple:function(){fi=!1}}),r({target:"Object",stat:!0,forced:!y,sham:!_},{create:function(_i,wi){return void 0===wi?ue(_i):Cn(ue(_i),wi)},defineProperty:Zr,defineProperties:Cn,getOwnPropertyDescriptor:rr}),r({target:"Object",stat:!0,forced:!y},{getOwnPropertyNames:Fr}),Vt(),it(Xt,Ut),ze[Zt]=!0},1172:()=>{},12353:(v,T,i)=>{var r=i(90513),u=i(7365),p=i(80112),d=i(41433),e=i(64579),_=i(56709),y=e("string-to-symbol-registry"),S=e("symbol-to-string-registry");r({target:"Symbol",stat:!0,forced:!_},{for:function(A){var N=d(A);if(p(y,N))return y[N];var L=u("Symbol")(N);return y[N]=L,S[L]=N,L}})},99579:(v,T,i)=>{i(25374)("hasInstance")},41258:(v,T,i)=>{i(25374)("isConcatSpreadable")},2383:(v,T,i)=>{i(25374)("iterator")},56728:(v,T,i)=>{i(17858),i(12353),i(27632),i(75071),i(37764)},27632:(v,T,i)=>{var r=i(90513),u=i(80112),p=i(74717),d=i(7378),e=i(64579),_=i(56709),y=e("symbol-to-string-registry");r({target:"Symbol",stat:!0,forced:!_},{keyFor:function(A){if(!p(A))throw TypeError(d(A)+" is not a symbol");if(u(y,A))return y[A]}})},64776:(v,T,i)=>{i(25374)("matchAll")},44339:(v,T,i)=>{i(25374)("match")},88215:(v,T,i)=>{i(25374)("replace")},65389:(v,T,i)=>{i(25374)("search")},12733:(v,T,i)=>{i(25374)("species")},97977:(v,T,i)=>{i(25374)("split")},59792:(v,T,i)=>{var r=i(25374),u=i(56992);r("toPrimitive"),u()},60242:(v,T,i)=>{var r=i(7365),u=i(25374),p=i(85681);u("toStringTag"),p(r("Symbol"),"Symbol")},26291:(v,T,i)=>{i(25374)("unscopables")},3119:(v,T,i)=>{"use strict";var Z,r=i(70009),u=i(23634),p=i(84604),d=i(57867),e=i(85116),_=i(84049),y=i(77293),S=i(46401),A=i(91093).enforce,N=i(81101),L=!r.ActiveXObject&&"ActiveXObject"in r,J=function(Ee){return function(){return Ee(this,arguments.length?arguments[0]:void 0)}},K=e("WeakMap",J,_);if(N&&L){Z=_.getConstructor(J,"WeakMap",!0),d.enable();var ee=K.prototype,ue=u(ee.delete),ae=u(ee.has),H=u(ee.get),se=u(ee.set);p(ee,{delete:function(Ee){if(y(Ee)&&!S(Ee)){var ie=A(this);return ie.frozen||(ie.frozen=new Z),ue(this,Ee)||ie.frozen.delete(Ee)}return ue(this,Ee)},has:function(ie){if(y(ie)&&!S(ie)){var he=A(this);return he.frozen||(he.frozen=new Z),ae(this,ie)||he.frozen.has(ie)}return ae(this,ie)},get:function(ie){if(y(ie)&&!S(ie)){var he=A(this);return he.frozen||(he.frozen=new Z),ae(this,ie)?H(this,ie):he.frozen.get(ie)}return H(this,ie)},set:function(ie,he){if(y(ie)&&!S(ie)){var ge=A(this);ge.frozen||(ge.frozen=new Z),ae(this,ie)?se(this,ie,he):ge.frozen.set(ie,he)}else se(this,ie,he);return this}})}},90770:(v,T,i)=>{i(3119)},67670:(v,T,i)=>{i(10901)},65237:(v,T,i)=>{i(87404)},10509:(v,T,i)=>{"use strict";i(90513)({target:"Map",proto:!0,real:!0,forced:!0},{deleteAll:i(37353)})},30887:(v,T,i)=>{"use strict";i(90513)({target:"Map",proto:!0,real:!0,forced:!0},{emplace:i(12864)})},54547:(v,T,i)=>{"use strict";var r=i(90513),u=i(64562),p=i(76781),d=i(37444),e=i(41605);r({target:"Map",proto:!0,real:!0,forced:!0},{every:function(y){var S=u(this),A=d(S),N=p(y,arguments.length>1?arguments[1]:void 0);return!e(A,function(L,Z,J){if(!N(Z,L,S))return J()},{AS_ENTRIES:!0,IS_ITERATOR:!0,INTERRUPTED:!0}).stopped}})},68996:(v,T,i)=>{"use strict";var r=i(90513),u=i(7365),p=i(76781),d=i(25401),e=i(61812),_=i(64562),y=i(95869),S=i(37444),A=i(41605);r({target:"Map",proto:!0,real:!0,forced:!0},{filter:function(L){var Z=_(this),J=S(Z),K=p(L,arguments.length>1?arguments[1]:void 0),ee=new(y(Z,u("Map"))),ue=e(ee.set);return A(J,function(ae,H){K(H,ae,Z)&&d(ue,ee,ae,H)},{AS_ENTRIES:!0,IS_ITERATOR:!0}),ee}})},60176:(v,T,i)=>{"use strict";var r=i(90513),u=i(64562),p=i(76781),d=i(37444),e=i(41605);r({target:"Map",proto:!0,real:!0,forced:!0},{findKey:function(y){var S=u(this),A=d(S),N=p(y,arguments.length>1?arguments[1]:void 0);return e(A,function(L,Z,J){if(N(Z,L,S))return J(L)},{AS_ENTRIES:!0,IS_ITERATOR:!0,INTERRUPTED:!0}).result}})},1530:(v,T,i)=>{"use strict";var r=i(90513),u=i(64562),p=i(76781),d=i(37444),e=i(41605);r({target:"Map",proto:!0,real:!0,forced:!0},{find:function(y){var S=u(this),A=d(S),N=p(y,arguments.length>1?arguments[1]:void 0);return e(A,function(L,Z,J){if(N(Z,L,S))return J(Z)},{AS_ENTRIES:!0,IS_ITERATOR:!0,INTERRUPTED:!0}).result}})},78271:(v,T,i)=>{i(90513)({target:"Map",stat:!0,forced:!0},{from:i(83483)})},41554:(v,T,i)=>{"use strict";var r=i(90513),u=i(25401),p=i(23634),d=i(61812),e=i(88055),_=i(41605),y=p([].push);r({target:"Map",stat:!0,forced:!0},{groupBy:function(A,N){d(N);var L=e(A),Z=new this,J=d(Z.has),K=d(Z.get),ee=d(Z.set);return _(L,function(ue){var ae=N(ue);u(J,Z,ae)?y(u(K,Z,ae),ue):u(ee,Z,ae,[ue])},{IS_ITERATOR:!0}),Z}})},41688:(v,T,i)=>{"use strict";var r=i(90513),u=i(64562),p=i(37444),d=i(29627),e=i(41605);r({target:"Map",proto:!0,real:!0,forced:!0},{includes:function(y){return e(p(u(this)),function(S,A,N){if(d(A,y))return N()},{AS_ENTRIES:!0,IS_ITERATOR:!0,INTERRUPTED:!0}).stopped}})},92847:(v,T,i)=>{"use strict";var r=i(90513),u=i(25401),p=i(41605),d=i(61812);r({target:"Map",stat:!0,forced:!0},{keyBy:function(_,y){var S=new this;d(y);var A=d(S.set);return p(_,function(N){u(A,S,y(N),N)}),S}})},17316:(v,T,i)=>{"use strict";var r=i(90513),u=i(64562),p=i(37444),d=i(41605);r({target:"Map",proto:!0,real:!0,forced:!0},{keyOf:function(_){return d(p(u(this)),function(y,S,A){if(S===_)return A(y)},{AS_ENTRIES:!0,IS_ITERATOR:!0,INTERRUPTED:!0}).result}})},58786:(v,T,i)=>{"use strict";var r=i(90513),u=i(7365),p=i(76781),d=i(25401),e=i(61812),_=i(64562),y=i(95869),S=i(37444),A=i(41605);r({target:"Map",proto:!0,real:!0,forced:!0},{mapKeys:function(L){var Z=_(this),J=S(Z),K=p(L,arguments.length>1?arguments[1]:void 0),ee=new(y(Z,u("Map"))),ue=e(ee.set);return A(J,function(ae,H){d(ue,ee,K(H,ae,Z),H)},{AS_ENTRIES:!0,IS_ITERATOR:!0}),ee}})},35517:(v,T,i)=>{"use strict";var r=i(90513),u=i(7365),p=i(76781),d=i(25401),e=i(61812),_=i(64562),y=i(95869),S=i(37444),A=i(41605);r({target:"Map",proto:!0,real:!0,forced:!0},{mapValues:function(L){var Z=_(this),J=S(Z),K=p(L,arguments.length>1?arguments[1]:void 0),ee=new(y(Z,u("Map"))),ue=e(ee.set);return A(J,function(ae,H){d(ue,ee,ae,K(H,ae,Z))},{AS_ENTRIES:!0,IS_ITERATOR:!0}),ee}})},12783:(v,T,i)=>{"use strict";var r=i(90513),u=i(61812),p=i(64562),d=i(41605);r({target:"Map",proto:!0,real:!0,arity:1,forced:!0},{merge:function(_){for(var y=p(this),S=u(y.set),A=arguments.length,N=0;N{i(90513)({target:"Map",stat:!0,forced:!0},{of:i(13067)})},69773:(v,T,i)=>{"use strict";var r=i(90513),u=i(64562),p=i(61812),d=i(37444),e=i(41605),_=TypeError;r({target:"Map",proto:!0,real:!0,forced:!0},{reduce:function(S){var A=u(this),N=d(A),L=arguments.length<2,Z=L?void 0:arguments[1];if(p(S),e(N,function(J,K){L?(L=!1,Z=K):Z=S(Z,K,J,A)},{AS_ENTRIES:!0,IS_ITERATOR:!0}),L)throw _("Reduce of empty map with no initial value");return Z}})},22337:(v,T,i)=>{"use strict";var r=i(90513),u=i(64562),p=i(76781),d=i(37444),e=i(41605);r({target:"Map",proto:!0,real:!0,forced:!0},{some:function(y){var S=u(this),A=d(S),N=p(y,arguments.length>1?arguments[1]:void 0);return e(A,function(L,Z,J){if(N(Z,L,S))return J()},{AS_ENTRIES:!0,IS_ITERATOR:!0,INTERRUPTED:!0}).stopped}})},84131:(v,T,i)=>{"use strict";i(90513)({target:"Map",proto:!0,real:!0,name:"upsert",forced:!0},{updateOrInsert:i(57729)})},40199:(v,T,i)=>{"use strict";var r=i(90513),u=i(25401),p=i(64562),d=i(61812),e=TypeError;r({target:"Map",proto:!0,real:!0,forced:!0},{update:function(y,S){var A=p(this),N=d(A.get),L=d(A.has),Z=d(A.set),J=arguments.length;d(S);var K=u(L,A,y);if(!K&&J<3)throw e("Updating absent value");var ee=K?u(N,A,y):d(J>2?arguments[2]:void 0)(y,A);return u(Z,A,y,S(ee,y,A)),A}})},69046:(v,T,i)=>{"use strict";i(90513)({target:"Map",proto:!0,real:!0,forced:!0},{upsert:i(57729)})},61127:(v,T,i)=>{i(84798)},45975:(v,T,i)=>{i(98857)},93114:(v,T,i)=>{"use strict";var r=i(90513),u=i(54256),p=i(26975);r({target:"Promise",stat:!0,forced:!0},{try:function(d){var e=u.f(this),_=p(d);return(_.error?e.reject:e.resolve)(_.value),e.promise}})},55461:(v,T,i)=>{i(25374)("asyncDispose")},5737:(v,T,i)=>{i(25374)("dispose")},87097:(v,T,i)=>{i(25374)("matcher")},29559:(v,T,i)=>{i(25374)("metadataKey")},71985:(v,T,i)=>{i(25374)("metadata")},90212:(v,T,i)=>{i(25374)("observable")},93770:(v,T,i)=>{i(25374)("patternMatch")},47743:(v,T,i)=>{i(25374)("replaceAll")},33089:(v,T,i)=>{i(1285);var r=i(44125),u=i(70009),p=i(35329),d=i(65162),e=i(84394),y=i(91840)("toStringTag");for(var S in r){var A=u[S],N=A&&A.prototype;N&&p(N)!==y&&d(N,y,S),e[S]=e.Array}},94784:(v,T,i)=>{var r=i(90513),u=i(70009),p=i(53814).setInterval;r({global:!0,bind:!0,forced:u.setInterval!==p},{setInterval:p})},36445:(v,T,i)=>{var r=i(90513),u=i(70009),p=i(53814).setTimeout;r({global:!0,bind:!0,forced:u.setTimeout!==p},{setTimeout:p})},69280:(v,T,i)=>{i(94784),i(36445)},73842:(v,T,i)=>{"use strict";i(1285);var r=i(90513),u=i(70009),p=i(25401),d=i(23634),e=i(49642),_=i(54933),y=i(42915),S=i(84604),A=i(85681),N=i(14554),L=i(91093),Z=i(54849),J=i(52208),K=i(80112),ee=i(76781),ue=i(35329),ae=i(64562),H=i(77293),se=i(41433),Ee=i(83272),ie=i(51361),he=i(88055),ge=i(34014),De=i(15086),ce=i(91840),lt=i(84865),Ve=ce("iterator"),ze="URLSearchParams",Be=ze+"Iterator",Pe=L.set,je=L.getterFor(ze),He=L.getterFor(Be),Vt=Object.getOwnPropertyDescriptor,it=function(Dr){if(!e)return u[Dr];var gn=Vt(u,Dr);return gn&&gn.value},tn=it("fetch"),It=it("Request"),Zt=it("Headers"),Ut=It&&It.prototype,Bt=Zt&&Zt.prototype,bt=u.RegExp,Gt=u.TypeError,xt=u.decodeURIComponent,Xt=u.encodeURIComponent,Zn=d("".charAt),Ur=d([].join),di=d([].push),Lr=d("".replace),Mr=d([].shift),Kr=d([].splice),ei=d("".split),Nn=d("".slice),$n=/\+/g,Br=Array(4),Yr=function(Dr){return Br[Dr-1]||(Br[Dr-1]=bt("((?:%[\\da-f]{2}){"+Dr+"})","gi"))},fi=function(Dr){try{return xt(Dr)}catch(gn){return Dr}},ki=function(Dr){var gn=Lr(Dr,$n," "),yn=4;try{return xt(gn)}catch(gr){for(;yn;)gn=Lr(gn,Yr(yn--),fi);return gn}},Hi=/[!'()~]|%20/g,Zr={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+"},Cn=function(Dr){return Zr[Dr]},Wt=function(Dr){return Lr(Xt(Dr),Hi,Cn)},zn=N(function(gn,yn){Pe(this,{type:Be,iterator:he(je(gn).entries),kind:yn})},"Iterator",function(){var gn=He(this),yn=gn.kind,gr=gn.iterator.next(),Jt=gr.value;return gr.done||(gr.value="keys"===yn?Jt.key:"values"===yn?Jt.value:[Jt.key,Jt.value]),gr},!0),rr=function(Dr){this.entries=[],this.url=null,void 0!==Dr&&(H(Dr)?this.parseObject(Dr):this.parseQuery("string"==typeof Dr?"?"===Zn(Dr,0)?Nn(Dr,1):Dr:se(Dr)))};rr.prototype={type:ze,bindURL:function(Dr){this.url=Dr,this.update()},parseObject:function(Dr){var yn,gr,Jt,Vn,mr,Dn,Pr,gn=ge(Dr);if(gn)for(gr=(yn=he(Dr,gn)).next;!(Jt=p(gr,yn)).done;){if(Vn=he(ae(Jt.value)),(Dn=p(mr=Vn.next,Vn)).done||(Pr=p(mr,Vn)).done||!p(mr,Vn).done)throw Gt("Expected sequence with length 2");di(this.entries,{key:se(Dn.value),value:se(Pr.value)})}else for(var Yt in Dr)K(Dr,Yt)&&di(this.entries,{key:Yt,value:se(Dr[Yt])})},parseQuery:function(Dr){if(Dr)for(var gr,Jt,gn=ei(Dr,"&"),yn=0;yn0?arguments[0]:void 0;Pe(this,new rr(gn))},Gn=Fr.prototype;if(S(Gn,{append:function(gn,yn){De(arguments.length,2);var gr=je(this);di(gr.entries,{key:se(gn),value:se(yn)}),gr.updateURL()},delete:function(Dr){De(arguments.length,1);for(var gn=je(this),yn=gn.entries,gr=se(Dr),Jt=0;Jtgr.key?1:-1}),gn.updateURL()},forEach:function(gn){for(var Vn,yn=je(this).entries,gr=ee(gn,arguments.length>1?arguments[1]:void 0),Jt=0;Jt1?wi(arguments[1]):{})}}),J(It)){var br=function(gn){return Z(this,Ut),new It(gn,arguments.length>1?wi(arguments[1]):{})};Ut.constructor=br,br.prototype=Ut,r({global:!0,constructor:!0,dontCallGetSet:!0,forced:!0},{Request:br})}}v.exports={URLSearchParams:Fr,getState:je}},26953:(v,T,i)=>{i(73842)},80504:(v,T,i)=>{"use strict";i(3934);var Hi,r=i(90513),u=i(49642),p=i(54933),d=i(70009),e=i(76781),_=i(23634),y=i(42915),S=i(1707),A=i(54849),N=i(80112),L=i(75791),Z=i(51923),J=i(8681),K=i(61557).codeAt,ee=i(26662),ue=i(41433),ae=i(85681),H=i(15086),se=i(73842),Ee=i(91093),ie=Ee.set,he=Ee.getterFor("URL"),ge=se.URLSearchParams,De=se.getState,ce=d.URL,lt=d.TypeError,Ve=d.parseInt,ze=Math.floor,Be=Math.pow,Pe=_("".charAt),je=_(/./.exec),He=_([].join),Vt=_(1..toString),it=_([].pop),tn=_([].push),It=_("".replace),Zt=_([].shift),Ut=_("".split),Bt=_("".slice),bt=_("".toLowerCase),Gt=_([].unshift),Xt="Invalid scheme",Zn="Invalid host",Ur="Invalid port",di=/[a-z]/i,Lr=/[\d+-.a-z]/i,Mr=/\d/,Kr=/^0x/i,ei=/^[0-7]+$/,Nn=/^\d+$/,$n=/^[\da-f]+$/i,Br=/[\0\t\n\r #%/:<>?@[\\\]^|]/,Yr=/[\0\t\n\r #/:<>?@[\\\]^|]/,fi=/^[\u0000-\u0020]+|[\u0000-\u0020]+$/g,ki=/[\t\n\r]/g,zn=function(Bn){var lr,vr,er,ri;if("number"==typeof Bn){for(lr=[],vr=0;vr<4;vr++)Gt(lr,Bn%256),Bn=ze(Bn/256);return He(lr,".")}if("object"==typeof Bn){for(lr="",er=function(Bn){for(var lr=null,vr=1,er=null,ri=0,uo=0;uo<8;uo++)0!==Bn[uo]?(ri>vr&&(lr=er,vr=ri),er=null,ri=0):(null===er&&(er=uo),++ri);return ri>vr&&(lr=er,vr=ri),lr}(Bn),vr=0;vr<8;vr++)ri&&0===Bn[vr]||(ri&&(ri=!1),er===vr?(lr+=vr?":":"::",ri=!0):(lr+=Vt(Bn[vr],16),vr<7&&(lr+=":")));return"["+lr+"]"}return Bn},rr={},Fr=L({},rr,{" ":1,'"':1,"<":1,">":1,"`":1}),Gn=L({},Fr,{"#":1,"?":1,"{":1,"}":1}),Jr=L({},Gn,{"/":1,":":1,";":1,"=":1,"@":1,"[":1,"\\":1,"]":1,"^":1,"|":1}),_i=function(Bn,lr){var vr=K(Bn,0);return vr>32&&vr<127&&!N(lr,Bn)?Bn:encodeURIComponent(Bn)},wi={ftp:21,file:null,http:80,https:443,ws:80,wss:443},br=function(Bn,lr){var vr;return 2==Bn.length&&je(di,Pe(Bn,0))&&(":"==(vr=Pe(Bn,1))||!lr&&"|"==vr)},Dr=function(Bn){var lr;return Bn.length>1&&br(Bt(Bn,0,2))&&(2==Bn.length||"/"===(lr=Pe(Bn,2))||"\\"===lr||"?"===lr||"#"===lr)},gn=function(Bn){return"."===Bn||"%2e"===bt(Bn)},yn=function(Bn){return".."===(Bn=bt(Bn))||"%2e."===Bn||".%2e"===Bn||"%2e%2e"===Bn},gr={},Jt={},Vn={},mr={},Dn={},Pr={},Yt={},_n={},Ge={},kr={},mi={},An={},Er={},Wr={},dr={},Fn={},ar={},Wi={},lo={},vo={},Co={},Gi=function(Bn,lr,vr){var ri,uo,Ci,er=ue(Bn);if(lr){if(uo=this.parse(er))throw lt(uo);this.searchParams=null}else{if(void 0!==vr&&(ri=new Gi(vr,!0)),uo=this.parse(er,null,ri))throw lt(uo);(Ci=De(new ge)).bindURL(this),this.searchParams=Ci}};Gi.prototype={type:"URL",parse:function(Bn,lr,vr){var Rt,_t,mt,jt,er=this,ri=lr||gr,uo=0,Ci="",$o=!1,tr=!1,Ar=!1;for(Bn=ue(Bn),lr||(er.scheme="",er.username="",er.password="",er.host=null,er.port=null,er.path=[],er.query=null,er.fragment=null,er.cannotBeABaseURL=!1,Bn=It(Bn,fi,"")),Bn=It(Bn,ki,""),Rt=Z(Bn);uo<=Rt.length;){switch(_t=Rt[uo],ri){case gr:if(!_t||!je(di,_t)){if(lr)return Xt;ri=Vn;continue}Ci+=bt(_t),ri=Jt;break;case Jt:if(_t&&(je(Lr,_t)||"+"==_t||"-"==_t||"."==_t))Ci+=bt(_t);else{if(":"!=_t){if(lr)return Xt;Ci="",ri=Vn,uo=0;continue}if(lr&&(er.isSpecial()!=N(wi,Ci)||"file"==Ci&&(er.includesCredentials()||null!==er.port)||"file"==er.scheme&&!er.host))return;if(er.scheme=Ci,lr)return void(er.isSpecial()&&wi[er.scheme]==er.port&&(er.port=null));Ci="","file"==er.scheme?ri=Wr:er.isSpecial()&&vr&&vr.scheme==er.scheme?ri=mr:er.isSpecial()?ri=_n:"/"==Rt[uo+1]?(ri=Dn,uo++):(er.cannotBeABaseURL=!0,tn(er.path,""),ri=lo)}break;case Vn:if(!vr||vr.cannotBeABaseURL&&"#"!=_t)return Xt;if(vr.cannotBeABaseURL&&"#"==_t){er.scheme=vr.scheme,er.path=J(vr.path),er.query=vr.query,er.fragment="",er.cannotBeABaseURL=!0,ri=Co;break}ri="file"==vr.scheme?Wr:Pr;continue;case mr:if("/"!=_t||"/"!=Rt[uo+1]){ri=Pr;continue}ri=Ge,uo++;break;case Dn:if("/"==_t){ri=kr;break}ri=Wi;continue;case Pr:if(er.scheme=vr.scheme,_t==Hi)er.username=vr.username,er.password=vr.password,er.host=vr.host,er.port=vr.port,er.path=J(vr.path),er.query=vr.query;else if("/"==_t||"\\"==_t&&er.isSpecial())ri=Yt;else if("?"==_t)er.username=vr.username,er.password=vr.password,er.host=vr.host,er.port=vr.port,er.path=J(vr.path),er.query="",ri=vo;else{if("#"!=_t){er.username=vr.username,er.password=vr.password,er.host=vr.host,er.port=vr.port,er.path=J(vr.path),er.path.length--,ri=Wi;continue}er.username=vr.username,er.password=vr.password,er.host=vr.host,er.port=vr.port,er.path=J(vr.path),er.query=vr.query,er.fragment="",ri=Co}break;case Yt:if(!er.isSpecial()||"/"!=_t&&"\\"!=_t){if("/"!=_t){er.username=vr.username,er.password=vr.password,er.host=vr.host,er.port=vr.port,ri=Wi;continue}ri=kr}else ri=Ge;break;case _n:if(ri=Ge,"/"!=_t||"/"!=Pe(Ci,uo+1))continue;uo++;break;case Ge:if("/"!=_t&&"\\"!=_t){ri=kr;continue}break;case kr:if("@"==_t){$o&&(Ci="%40"+Ci),$o=!0,mt=Z(Ci);for(var on=0;on65535)return Ur;er.port=er.isSpecial()&&_o===wi[er.scheme]?null:_o,Ci=""}if(lr)return;ri=ar;continue}return Ur}Ci+=_t;break;case Wr:if(er.scheme="file","/"==_t||"\\"==_t)ri=dr;else{if(!vr||"file"!=vr.scheme){ri=Wi;continue}if(_t==Hi)er.host=vr.host,er.path=J(vr.path),er.query=vr.query;else if("?"==_t)er.host=vr.host,er.path=J(vr.path),er.query="",ri=vo;else{if("#"!=_t){Dr(He(J(Rt,uo),""))||(er.host=vr.host,er.path=J(vr.path),er.shortenPath()),ri=Wi;continue}er.host=vr.host,er.path=J(vr.path),er.query=vr.query,er.fragment="",ri=Co}}break;case dr:if("/"==_t||"\\"==_t){ri=Fn;break}vr&&"file"==vr.scheme&&!Dr(He(J(Rt,uo),""))&&(br(vr.path[0],!0)?tn(er.path,vr.path[0]):er.host=vr.host),ri=Wi;continue;case Fn:if(_t==Hi||"/"==_t||"\\"==_t||"?"==_t||"#"==_t){if(!lr&&br(Ci))ri=Wi;else if(""==Ci){if(er.host="",lr)return;ri=ar}else{if(jt=er.parseHost(Ci))return jt;if("localhost"==er.host&&(er.host=""),lr)return;Ci="",ri=ar}continue}Ci+=_t;break;case ar:if(er.isSpecial()){if(ri=Wi,"/"!=_t&&"\\"!=_t)continue}else if(lr||"?"!=_t)if(lr||"#"!=_t){if(_t!=Hi&&(ri=Wi,"/"!=_t))continue}else er.fragment="",ri=Co;else er.query="",ri=vo;break;case Wi:if(_t==Hi||"/"==_t||"\\"==_t&&er.isSpecial()||!lr&&("?"==_t||"#"==_t)){if(yn(Ci)?(er.shortenPath(),"/"!=_t&&!("\\"==_t&&er.isSpecial())&&tn(er.path,"")):gn(Ci)?"/"!=_t&&!("\\"==_t&&er.isSpecial())&&tn(er.path,""):("file"==er.scheme&&!er.path.length&&br(Ci)&&(er.host&&(er.host=""),Ci=Pe(Ci,0)+":"),tn(er.path,Ci)),Ci="","file"==er.scheme&&(_t==Hi||"?"==_t||"#"==_t))for(;er.path.length>1&&""===er.path[0];)Zt(er.path);"?"==_t?(er.query="",ri=vo):"#"==_t&&(er.fragment="",ri=Co)}else Ci+=_i(_t,Gn);break;case lo:"?"==_t?(er.query="",ri=vo):"#"==_t?(er.fragment="",ri=Co):_t!=Hi&&(er.path[0]+=_i(_t,rr));break;case vo:lr||"#"!=_t?_t!=Hi&&("'"==_t&&er.isSpecial()?er.query+="%27":er.query+="#"==_t?"%23":_i(_t,rr)):(er.fragment="",ri=Co);break;case Co:_t!=Hi&&(er.fragment+=_i(_t,Fr))}uo++}},parseHost:function(Bn){var lr,vr,er;if("["==Pe(Bn,0)){if("]"!=Pe(Bn,Bn.length-1)||!(lr=function(Bn){var uo,Ci,$o,tr,Ar,Rt,_t,lr=[0,0,0,0,0,0,0,0],vr=0,er=null,ri=0,mt=function(){return Pe(Bn,ri)};if(":"==mt()){if(":"!=Pe(Bn,1))return;ri+=2,er=++vr}for(;mt();){if(8==vr)return;if(":"!=mt()){for(uo=Ci=0;Ci<4&&je($n,mt());)uo=16*uo+Ve(mt(),16),ri++,Ci++;if("."==mt()){if(0==Ci||(ri-=Ci,vr>6))return;for($o=0;mt();){if(tr=null,$o>0){if(!("."==mt()&&$o<4))return;ri++}if(!je(Mr,mt()))return;for(;je(Mr,mt());){if(Ar=Ve(mt(),10),null===tr)tr=Ar;else{if(0==tr)return;tr=10*tr+Ar}if(tr>255)return;ri++}lr[vr]=256*lr[vr]+tr,(2==++$o||4==$o)&&vr++}if(4!=$o)return;break}if(":"==mt()){if(ri++,!mt())return}else if(mt())return;lr[vr++]=uo}else{if(null!==er)return;ri++,er=++vr}}if(null!==er)for(Rt=vr-er,vr=7;0!=vr&&Rt>0;)_t=lr[vr],lr[vr--]=lr[er+Rt-1],lr[er+--Rt]=_t;else if(8!=vr)return;return lr}(Bt(Bn,1,-1))))return Zn;this.host=lr}else if(this.isSpecial()){if(Bn=ee(Bn),je(Br,Bn)||null===(lr=function(Bn){var vr,er,ri,uo,Ci,$o,tr,lr=Ut(Bn,".");if(lr.length&&""==lr[lr.length-1]&&lr.length--,(vr=lr.length)>4)return Bn;for(er=[],ri=0;ri1&&"0"==Pe(uo,0)&&(Ci=je(Kr,uo)?16:8,uo=Bt(uo,8==Ci?1:2)),""===uo)$o=0;else{if(!je(10==Ci?Nn:8==Ci?ei:$n,uo))return Bn;$o=Ve(uo,Ci)}tn(er,$o)}for(ri=0;ri=Be(256,5-vr))return null}else if($o>255)return null;for(tr=it(er),ri=0;ri1?arguments[1]:void 0,ri=ie(vr,new Gi(lr,!1,er));u||(vr.href=ri.serialize(),vr.origin=ri.getOrigin(),vr.protocol=ri.getProtocol(),vr.username=ri.getUsername(),vr.password=ri.getPassword(),vr.host=ri.getHost(),vr.hostname=ri.getHostname(),vr.port=ri.getPort(),vr.pathname=ri.getPathname(),vr.search=ri.getSearch(),vr.searchParams=ri.getSearchParams(),vr.hash=ri.getHash())},jo=os.prototype,To=function(Bn,lr){return{get:function(){return he(this)[Bn]()},set:lr&&function(vr){return he(this)[lr](vr)},configurable:!0,enumerable:!0}};if(u&&(S(jo,"href",To("serialize","setHref")),S(jo,"origin",To("getOrigin")),S(jo,"protocol",To("getProtocol","setProtocol")),S(jo,"username",To("getUsername","setUsername")),S(jo,"password",To("getPassword","setPassword")),S(jo,"host",To("getHost","setHost")),S(jo,"hostname",To("getHostname","setHostname")),S(jo,"port",To("getPort","setPort")),S(jo,"pathname",To("getPathname","setPathname")),S(jo,"search",To("getSearch","setSearch")),S(jo,"searchParams",To("getSearchParams")),S(jo,"hash",To("getHash","setHash"))),y(jo,"toJSON",function(){return he(this).serialize()},{enumerable:!0}),y(jo,"toString",function(){return he(this).serialize()},{enumerable:!0}),ce){var Mi=ce.createObjectURL,li=ce.revokeObjectURL;Mi&&y(os,"createObjectURL",e(Mi,ce)),li&&y(os,"revokeObjectURL",e(li,ce))}ae(os,"URL"),r({global:!0,constructor:!0,forced:!p,sham:!u},{URL:os})},95981:(v,T,i)=>{i(80504)},71324:()=>{},75242:(v,T,i)=>{var r=i(74771);v.exports=r},10323:(v,T,i)=>{var r=i(8412);v.exports=r},99940:(v,T,i)=>{var r=i(399);v.exports=r},89919:(v,T,i)=>{var r=i(98812);v.exports=r},14869:(v,T,i)=>{var r=i(33195);v.exports=r},4475:(v,T,i)=>{var r=i(46332);v.exports=r},38762:(v,T,i)=>{var r=i(42618);v.exports=r},8748:(v,T,i)=>{var r=i(63791);i(33089),v.exports=r},47506:(v,T,i)=>{var r=i(27959);v.exports=r},71873:(v,T,i)=>{var r=i(69029);v.exports=r},61599:(v,T,i)=>{var r=i(28924);v.exports=r},34097:(v,T,i)=>{i(33089);var r=i(35329),u=i(80112),p=i(23336),d=i(99940),e=Array.prototype,_={DOMTokenList:!0,NodeList:!0};v.exports=function(y){var S=y.entries;return y===e||p(e,y)&&S===e.entries||u(_,r(y))?d:S}},15149:(v,T,i)=>{var r=i(98709);v.exports=r},83361:(v,T,i)=>{var r=i(65991);v.exports=r},19095:(v,T,i)=>{var r=i(64158);v.exports=r},71420:(v,T,i)=>{var r=i(91799);v.exports=r},13178:(v,T,i)=>{var r=i(26155);v.exports=r},52049:(v,T,i)=>{i(33089);var r=i(35329),u=i(80112),p=i(23336),d=i(89919),e=Array.prototype,_={DOMTokenList:!0,NodeList:!0};v.exports=function(y){var S=y.forEach;return y===e||p(e,y)&&S===e.forEach||u(_,r(y))?d:S}},83655:(v,T,i)=>{var r=i(33758);v.exports=r},87054:(v,T,i)=>{var r=i(7592);v.exports=r},51946:(v,T,i)=>{i(33089);var r=i(35329),u=i(80112),p=i(23336),d=i(14869),e=Array.prototype,_={DOMTokenList:!0,NodeList:!0};v.exports=function(y){var S=y.keys;return y===e||p(e,y)&&S===e.keys||u(_,r(y))?d:S}},40764:(v,T,i)=>{var r=i(17480);v.exports=r},81214:(v,T,i)=>{var r=i(20681);v.exports=r},50881:(v,T,i)=>{var r=i(90949);v.exports=r},38813:(v,T,i)=>{var r=i(99316);v.exports=r},45284:(v,T,i)=>{var r=i(62212);v.exports=r},70157:(v,T,i)=>{var r=i(49073);v.exports=r},3502:(v,T,i)=>{var r=i(24146);v.exports=r},81610:(v,T,i)=>{var r=i(40104);v.exports=r},19543:(v,T,i)=>{var r=i(3555);v.exports=r},74046:(v,T,i)=>{var r=i(68333);v.exports=r},13731:(v,T,i)=>{var r=i(65786);v.exports=r},80129:(v,T,i)=>{i(33089);var r=i(35329),u=i(80112),p=i(23336),d=i(4475),e=Array.prototype,_={DOMTokenList:!0,NodeList:!0};v.exports=function(y){var S=y.values;return y===e||p(e,y)&&S===e.values||u(_,r(y))?d:S}},43720:(v,T,i)=>{var r=i(66306);v.exports=r},640:(v,T,i)=>{var r=i(31845);i(33089),v.exports=r},50320:(v,T,i)=>{var r=i(44168);v.exports=r},93006:(v,T,i)=>{var r=i(25852);v.exports=r},36226:(v,T,i)=>{var r=i(24457);v.exports=r},21968:(v,T,i)=>{var r=i(99671);v.exports=r},15554:(v,T,i)=>{var r=i(35161);v.exports=r},87259:(v,T,i)=>{var r=i(38007);v.exports=r},62021:(v,T,i)=>{var r=i(57432);v.exports=r},57682:(v,T,i)=>{var r=i(36541);v.exports=r},94222:(v,T,i)=>{var r=i(17303);v.exports=r},1162:(v,T,i)=>{var r=i(62149);v.exports=r},82805:(v,T,i)=>{var r=i(86537);v.exports=r},70809:(v,T,i)=>{var r=i(79553);v.exports=r},26498:(v,T,i)=>{var r=i(80092);i(33089),v.exports=r},44850:(v,T,i)=>{var r=i(472);v.exports=r},9634:(v,T,i)=>{var r=i(4678);v.exports=r},12118:(v,T,i)=>{i(69280);var r=i(13544);v.exports=r.setTimeout},96551:(v,T,i)=>{var r=i(61697);i(33089),v.exports=r},98908:(v,T,i)=>{var r=i(42497);i(33089),v.exports=r},44675:(v,T,i)=>{var r=i(41530);i(33089),v.exports=r},70906:(v,T,i)=>{var r=i(75081);v.exports=r},95050:(v,T,i)=>{var r=i(58255);i(33089),v.exports=r},41530:(v,T,i)=>{i(26953);var r=i(13544);v.exports=r.URLSearchParams},75081:(v,T,i)=>{i(95981),i(71324),i(26953);var r=i(13544);v.exports=r.URL},71577:function(){var v;v="undefined"!=typeof self?self:this,function(i){var r_searchParams="URLSearchParams"in v,r_iterable="Symbol"in v&&"iterator"in Symbol,r_blob="FileReader"in v&&"Blob"in v&&function(){try{return new Blob,!0}catch(De){return!1}}(),r_formData="FormData"in v,r_arrayBuffer="ArrayBuffer"in v;if(r_arrayBuffer)var p=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],d=ArrayBuffer.isView||function(De){return De&&p.indexOf(Object.prototype.toString.call(De))>-1};function e(De){if("string"!=typeof De&&(De=String(De)),/[^a-z0-9\-#$%&'*+.^_`|~]/i.test(De))throw new TypeError("Invalid character in header field name");return De.toLowerCase()}function _(De){return"string"!=typeof De&&(De=String(De)),De}function y(De){var ce={next:function(){var lt=De.shift();return{done:void 0===lt,value:lt}}};return r_iterable&&(ce[Symbol.iterator]=function(){return ce}),ce}function S(De){this.map={},De instanceof S?De.forEach(function(ce,lt){this.append(lt,ce)},this):Array.isArray(De)?De.forEach(function(ce){this.append(ce[0],ce[1])},this):De&&Object.getOwnPropertyNames(De).forEach(function(ce){this.append(ce,De[ce])},this)}function A(De){if(De.bodyUsed)return Promise.reject(new TypeError("Already read"));De.bodyUsed=!0}function N(De){return new Promise(function(ce,lt){De.onload=function(){ce(De.result)},De.onerror=function(){lt(De.error)}})}function L(De){var ce=new FileReader,lt=N(ce);return ce.readAsArrayBuffer(De),lt}function K(De){if(De.slice)return De.slice(0);var ce=new Uint8Array(De.byteLength);return ce.set(new Uint8Array(De)),ce.buffer}function ee(){return this.bodyUsed=!1,this._initBody=function(De){this._bodyInit=De,De?"string"==typeof De?this._bodyText=De:r_blob&&Blob.prototype.isPrototypeOf(De)?this._bodyBlob=De:r_formData&&FormData.prototype.isPrototypeOf(De)?this._bodyFormData=De:r_searchParams&&URLSearchParams.prototype.isPrototypeOf(De)?this._bodyText=De.toString():r_arrayBuffer&&r_blob&&function(De){return De&&DataView.prototype.isPrototypeOf(De)}(De)?(this._bodyArrayBuffer=K(De.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):r_arrayBuffer&&(ArrayBuffer.prototype.isPrototypeOf(De)||d(De))?this._bodyArrayBuffer=K(De):this._bodyText=De=Object.prototype.toString.call(De):this._bodyText="",this.headers.get("content-type")||("string"==typeof De?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type?this.headers.set("content-type",this._bodyBlob.type):r_searchParams&&URLSearchParams.prototype.isPrototypeOf(De)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))},r_blob&&(this.blob=function(){var De=A(this);if(De)return De;if(this._bodyBlob)return Promise.resolve(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(new Blob([this._bodyArrayBuffer]));if(this._bodyFormData)throw new Error("could not read FormData body as blob");return Promise.resolve(new Blob([this._bodyText]))},this.arrayBuffer=function(){return this._bodyArrayBuffer?A(this)||Promise.resolve(this._bodyArrayBuffer):this.blob().then(L)}),this.text=function(){var De=A(this);if(De)return De;if(this._bodyBlob)return function(De){var ce=new FileReader,lt=N(ce);return ce.readAsText(De),lt}(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(function(De){for(var ce=new Uint8Array(De),lt=new Array(ce.length),Ve=0;Ve-1?ce:De}(ce.method||this.method||"GET"),this.mode=ce.mode||this.mode||null,this.signal=ce.signal||this.signal,this.referrer=null,("GET"===this.method||"HEAD"===this.method)&<)throw new TypeError("Body not allowed for GET or HEAD requests");this._initBody(lt)}function se(De){var ce=new FormData;return De.trim().split("&").forEach(function(lt){if(lt){var Ve=lt.split("="),ze=Ve.shift().replace(/\+/g," "),Be=Ve.join("=").replace(/\+/g," ");ce.append(decodeURIComponent(ze),decodeURIComponent(Be))}}),ce}function Ee(De){var ce=new S;return De.replace(/\r?\n[\t ]+/g," ").split(/\r?\n/).forEach(function(Ve){var ze=Ve.split(":"),Be=ze.shift().trim();if(Be){var Pe=ze.join(":").trim();ce.append(Be,Pe)}}),ce}function ie(De,ce){ce||(ce={}),this.type="default",this.status=void 0===ce.status?200:ce.status,this.ok=this.status>=200&&this.status<300,this.statusText="statusText"in ce?ce.statusText:"OK",this.headers=new S(ce.headers),this.url=ce.url||"",this._initBody(De)}H.prototype.clone=function(){return new H(this,{body:this._bodyInit})},ee.call(H.prototype),ee.call(ie.prototype),ie.prototype.clone=function(){return new ie(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new S(this.headers),url:this.url})},ie.error=function(){var De=new ie(null,{status:0,statusText:""});return De.type="error",De};var he=[301,302,303,307,308];ie.redirect=function(De,ce){if(-1===he.indexOf(ce))throw new RangeError("Invalid status code");return new ie(null,{status:ce,headers:{location:De}})},i.DOMException=v.DOMException;try{new i.DOMException}catch(De){i.DOMException=function(ce,lt){this.message=ce,this.name=lt;var Ve=Error(ce);this.stack=Ve.stack},i.DOMException.prototype=Object.create(Error.prototype),i.DOMException.prototype.constructor=i.DOMException}function ge(De,ce){return new Promise(function(lt,Ve){var ze=new H(De,ce);if(ze.signal&&ze.signal.aborted)return Ve(new i.DOMException("Aborted","AbortError"));var Be=new XMLHttpRequest;function Pe(){Be.abort()}Be.onload=function(){var je={status:Be.status,statusText:Be.statusText,headers:Ee(Be.getAllResponseHeaders()||"")};je.url="responseURL"in Be?Be.responseURL:je.headers.get("X-Request-URL"),lt(new ie("response"in Be?Be.response:Be.responseText,je))},Be.onerror=function(){Ve(new TypeError("Network request failed"))},Be.ontimeout=function(){Ve(new TypeError("Network request failed"))},Be.onabort=function(){Ve(new i.DOMException("Aborted","AbortError"))},Be.open(ze.method,ze.url,!0),"include"===ze.credentials?Be.withCredentials=!0:"omit"===ze.credentials&&(Be.withCredentials=!1),"responseType"in Be&&r_blob&&(Be.responseType="blob"),ze.headers.forEach(function(je,He){Be.setRequestHeader(He,je)}),ze.signal&&(ze.signal.addEventListener("abort",Pe),Be.onreadystatechange=function(){4===Be.readyState&&ze.signal.removeEventListener("abort",Pe)}),Be.send(void 0===ze._bodyInit?null:ze._bodyInit)})}ge.polyfill=!0,v.fetch||(v.fetch=ge,v.Headers=S,v.Request=H,v.Response=ie),i.Headers=S,i.Request=H,i.Response=ie,i.fetch=ge,Object.defineProperty(i,"__esModule",{value:!0})}({})},52243:function(v){var T;T="undefined"!=typeof global?global:this,v.exports=function(T){if(T.CSS&&T.CSS.escape)return T.CSS.escape;var i=function(r){if(0==arguments.length)throw new TypeError("`CSS.escape` requires an argument.");for(var e,u=String(r),p=u.length,d=-1,_="",y=u.charCodeAt(0);++d=1&&e<=31||127==e||0==d&&e>=48&&e<=57||1==d&&e>=48&&e<=57&&45==y?"\\"+e.toString(16)+" ":0==d&&1==p&&45==e||!(e>=128||45==e||95==e||e>=48&&e<=57||e>=65&&e<=90||e>=97&&e<=122)?"\\"+u.charAt(d):u.charAt(d):_+="\ufffd";return _};return T.CSS||(T.CSS={}),T.CSS.escape=i,i}(T)},89225:v=>{"use strict";var T=function(ae){return!(!(ue=ae)||"object"!=typeof ue||function(ue){var ae=Object.prototype.toString.call(ue);return"[object RegExp]"===ae||"[object Date]"===ae||function(ue){return ue.$$typeof===p}(ue)}(ae));var ue},p="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function _(ue,ae){return!1!==ae.clone&&ae.isMergeableObject(ue)?K(function(ue){return Array.isArray(ue)?[]:{}}(ue),ue,ae):ue}function y(ue,ae,H){return ue.concat(ae).map(function(se){return _(se,H)})}function N(ue){return Object.keys(ue).concat(function(ue){return Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(ue).filter(function(ae){return ue.propertyIsEnumerable(ae)}):[]}(ue))}function L(ue,ae){try{return ae in ue}catch(H){return!1}}function K(ue,ae,H){(H=H||{}).arrayMerge=H.arrayMerge||y,H.isMergeableObject=H.isMergeableObject||T,H.cloneUnlessOtherwiseSpecified=_;var se=Array.isArray(ae);return se===Array.isArray(ue)?se?H.arrayMerge(ue,ae,H):function(ue,ae,H){var se={};return H.isMergeableObject(ue)&&N(ue).forEach(function(Ee){se[Ee]=_(ue[Ee],H)}),N(ae).forEach(function(Ee){(function(ue,ae){return L(ue,ae)&&!(Object.hasOwnProperty.call(ue,ae)&&Object.propertyIsEnumerable.call(ue,ae))})(ue,Ee)||(se[Ee]=L(ue,Ee)&&H.isMergeableObject(ae[Ee])?function(ue,ae){if(!ae.customMerge)return K;var H=ae.customMerge(ue);return"function"==typeof H?H:K}(Ee,H)(ue[Ee],ae[Ee],H):_(ae[Ee],H))}),se}(ue,ae,H):_(ae,H)}K.all=function(ae,H){if(!Array.isArray(ae))throw new Error("first argument should be an array");return ae.reduce(function(se,Ee){return K(se,Ee,H)},{})},v.exports=K},97057:(v,T,i)=>{"use strict";i.d(T,{qY:()=>Z});var u=function(Ee,ie,he){this.name=Ee,this.version=ie,this.os=he,this.type="browser"},p=function(Ee){this.version=Ee,this.type="node",this.name="node",this.os=process.platform},d=function(Ee,ie,he,ge){this.name=Ee,this.version=ie,this.os=he,this.bot=ge,this.type="bot-device"},e=function(){this.type="bot",this.bot=!0,this.name="bot",this.version=null,this.os=null},_=function(){this.type="react-native",this.name="react-native",this.version=null,this.os=null},S=/(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask\ Jeeves\/Teoma|ia_archiver)/,N=[["aol",/AOLShield\/([0-9\._]+)/],["edge",/Edge\/([0-9\._]+)/],["edge-ios",/EdgiOS\/([0-9\._]+)/],["yandexbrowser",/YaBrowser\/([0-9\._]+)/],["kakaotalk",/KAKAOTALK\s([0-9\.]+)/],["samsung",/SamsungBrowser\/([0-9\.]+)/],["silk",/\bSilk\/([0-9._-]+)\b/],["miui",/MiuiBrowser\/([0-9\.]+)$/],["beaker",/BeakerBrowser\/([0-9\.]+)/],["edge-chromium",/EdgA?\/([0-9\.]+)/],["chromium-webview",/(?!Chrom.*OPR)wv\).*Chrom(?:e|ium)\/([0-9\.]+)(:?\s|$)/],["chrome",/(?!Chrom.*OPR)Chrom(?:e|ium)\/([0-9\.]+)(:?\s|$)/],["phantomjs",/PhantomJS\/([0-9\.]+)(:?\s|$)/],["crios",/CriOS\/([0-9\.]+)(:?\s|$)/],["firefox",/Firefox\/([0-9\.]+)(?:\s|$)/],["fxios",/FxiOS\/([0-9\.]+)/],["opera-mini",/Opera Mini.*Version\/([0-9\.]+)/],["opera",/Opera\/([0-9\.]+)(?:\s|$)/],["opera",/OPR\/([0-9\.]+)(:?\s|$)/],["ie",/Trident\/7\.0.*rv\:([0-9\.]+).*\).*Gecko$/],["ie",/MSIE\s([0-9\.]+);.*Trident\/[4-7].0/],["ie",/MSIE\s(7\.0)/],["bb10",/BB10;\sTouch.*Version\/([0-9\.]+)/],["android",/Android\s([0-9\.]+)/],["ios",/Version\/([0-9\._]+).*Mobile.*Safari.*/],["safari",/Version\/([0-9\._]+).*Safari/],["facebook",/FBAV\/([0-9\.]+)/],["instagram",/Instagram\s([0-9\.]+)/],["ios-webview",/AppleWebKit\/([0-9\.]+).*Mobile/],["ios-webview",/AppleWebKit\/([0-9\.]+).*Gecko\)$/],["searchbot",/alexa|bot|crawl(er|ing)|facebookexternalhit|feedburner|google web preview|nagios|postrank|pingdom|slurp|spider|yahoo!|yandex/]],L=[["iOS",/iP(hone|od|ad)/],["Android OS",/Android/],["BlackBerry OS",/BlackBerry|BB10/],["Windows Mobile",/IEMobile/],["Amazon OS",/Kindle/],["Windows 3.11",/Win16/],["Windows 95",/(Windows 95)|(Win95)|(Windows_95)/],["Windows 98",/(Windows 98)|(Win98)/],["Windows 2000",/(Windows NT 5.0)|(Windows 2000)/],["Windows XP",/(Windows NT 5.1)|(Windows XP)/],["Windows Server 2003",/(Windows NT 5.2)/],["Windows Vista",/(Windows NT 6.0)/],["Windows 7",/(Windows NT 6.1)/],["Windows 8",/(Windows NT 6.2)/],["Windows 8.1",/(Windows NT 6.3)/],["Windows 10",/(Windows NT 10.0)/],["Windows ME",/Windows ME/],["Open BSD",/OpenBSD/],["Sun OS",/SunOS/],["Chrome OS",/CrOS/],["Linux",/(Linux)|(X11)/],["Mac OS",/(Mac_PowerPC)|(Macintosh)/],["QNX",/QNX/],["BeOS",/BeOS/],["OS/2",/OS\/2/]];function Z(se){return se?ee(se):"undefined"==typeof document&&"undefined"!=typeof navigator&&"ReactNative"===navigator.product?new _:"undefined"!=typeof navigator?ee(navigator.userAgent):"undefined"!=typeof process&&process.version?new p(process.version.slice(1)):null}function ee(se){var Ee=function(se){return""!==se&&N.reduce(function(Ee,ie){var he=ie[0];if(Ee)return Ee;var De=ie[1].exec(se);return!!De&&[he,De]},!1)}(se);if(!Ee)return null;var ie=Ee[0],he=Ee[1];if("searchbot"===ie)return new e;var ge=he[1]&&he[1].split(/[._]/).slice(0,3);ge?ge.length<3&&(ge=function(){for(var se=0,Ee=0,ie=arguments.length;Ee1?$n-1:0),Yr=1;Yr<$n;Yr++)Br[Yr-1]=arguments[Yr];return A(ei,Nn,Br)}}function ge(ei,Nn){r&&r(ei,null);for(var $n=Nn.length;$n--;){var Br=Nn[$n];if("string"==typeof Br){var Yr=K(Br);Yr!==Br&&(u(Nn)||(Nn[$n]=Yr),Br=Yr)}ei[Br]=!0}return ei}function De(ei){var Nn=y(null),$n=void 0;for($n in ei)A(i,ei,[$n])&&(Nn[$n]=ei[$n]);return Nn}function ce(ei,Nn){for(;null!==ei;){var $n=d(ei,Nn);if($n){if($n.get)return ie($n.get);if("function"==typeof $n.value)return ie($n.value)}ei=p(ei)}return function(Yr){return console.warn("fallback value for",Yr),null}}var lt=e(["a","abbr","acronym","address","area","article","aside","audio","b","bdi","bdo","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","content","data","datalist","dd","decorator","del","details","dfn","dialog","dir","div","dl","dt","element","em","fieldset","figcaption","figure","font","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","img","input","ins","kbd","label","legend","li","main","map","mark","marquee","menu","menuitem","meter","nav","nobr","ol","optgroup","option","output","p","picture","pre","progress","q","rp","rt","ruby","s","samp","section","select","shadow","small","source","spacer","span","strike","strong","style","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","time","tr","track","tt","u","ul","var","video","wbr"]),Ve=e(["svg","a","altglyph","altglyphdef","altglyphitem","animatecolor","animatemotion","animatetransform","circle","clippath","defs","desc","ellipse","filter","font","g","glyph","glyphref","hkern","image","line","lineargradient","marker","mask","metadata","mpath","path","pattern","polygon","polyline","radialgradient","rect","stop","style","switch","symbol","text","textpath","title","tref","tspan","view","vkern"]),ze=e(["feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence"]),Be=e(["animate","color-profile","cursor","discard","fedropshadow","feimage","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","foreignobject","hatch","hatchpath","mesh","meshgradient","meshpatch","meshrow","missing-glyph","script","set","solidcolor","unknown","use"]),Pe=e(["math","menclose","merror","mfenced","mfrac","mglyph","mi","mlabeledtr","mmultiscripts","mn","mo","mover","mpadded","mphantom","mroot","mrow","ms","mspace","msqrt","mstyle","msub","msup","msubsup","mtable","mtd","mtext","mtr","munder","munderover"]),je=e(["maction","maligngroup","malignmark","mlongdiv","mscarries","mscarry","msgroup","mstack","msline","msrow","semantics","annotation","annotation-xml","mprescripts","none"]),He=e(["#text"]),Vt=e(["accept","action","align","alt","autocapitalize","autocomplete","autopictureinpicture","autoplay","background","bgcolor","border","capture","cellpadding","cellspacing","checked","cite","class","clear","color","cols","colspan","controls","controlslist","coords","crossorigin","datetime","decoding","default","dir","disabled","disablepictureinpicture","disableremoteplayback","download","draggable","enctype","enterkeyhint","face","for","headers","height","hidden","high","href","hreflang","id","inputmode","integrity","ismap","kind","label","lang","list","loading","loop","low","max","maxlength","media","method","min","minlength","multiple","muted","name","noshade","novalidate","nowrap","open","optimum","pattern","placeholder","playsinline","poster","preload","pubdate","radiogroup","readonly","rel","required","rev","reversed","role","rows","rowspan","spellcheck","scope","selected","shape","size","sizes","span","srclang","start","src","srcset","step","style","summary","tabindex","title","translate","type","usemap","valign","value","width","xmlns","slot"]),it=e(["accent-height","accumulate","additive","alignment-baseline","ascent","attributename","attributetype","azimuth","basefrequency","baseline-shift","begin","bias","by","class","clip","clippathunits","clip-path","clip-rule","color","color-interpolation","color-interpolation-filters","color-profile","color-rendering","cx","cy","d","dx","dy","diffuseconstant","direction","display","divisor","dur","edgemode","elevation","end","fill","fill-opacity","fill-rule","filter","filterunits","flood-color","flood-opacity","font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","fx","fy","g1","g2","glyph-name","glyphref","gradientunits","gradienttransform","height","href","id","image-rendering","in","in2","k","k1","k2","k3","k4","kerning","keypoints","keysplines","keytimes","lang","lengthadjust","letter-spacing","kernelmatrix","kernelunitlength","lighting-color","local","marker-end","marker-mid","marker-start","markerheight","markerunits","markerwidth","maskcontentunits","maskunits","max","mask","media","method","mode","min","name","numoctaves","offset","operator","opacity","order","orient","orientation","origin","overflow","paint-order","path","pathlength","patterncontentunits","patterntransform","patternunits","points","preservealpha","preserveaspectratio","primitiveunits","r","rx","ry","radius","refx","refy","repeatcount","repeatdur","restart","result","rotate","scale","seed","shape-rendering","specularconstant","specularexponent","spreadmethod","startoffset","stddeviation","stitchtiles","stop-color","stop-opacity","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke","stroke-width","style","surfacescale","systemlanguage","tabindex","targetx","targety","transform","text-anchor","text-decoration","text-rendering","textlength","type","u1","u2","unicode","values","viewbox","visibility","version","vert-adv-y","vert-origin-x","vert-origin-y","width","word-spacing","wrap","writing-mode","xchannelselector","ychannelselector","x","x1","x2","xmlns","y","y1","y2","z","zoomandpan"]),tn=e(["accent","accentunder","align","bevelled","close","columnsalign","columnlines","columnspan","denomalign","depth","dir","display","displaystyle","encoding","fence","frame","height","href","id","largeop","length","linethickness","lspace","lquote","mathbackground","mathcolor","mathsize","mathvariant","maxsize","minsize","movablelimits","notation","numalign","open","rowalign","rowlines","rowspacing","rowspan","rspace","rquote","scriptlevel","scriptminsize","scriptsizemultiplier","selection","separator","separators","stretchy","subscriptshift","supscriptshift","symmetric","voffset","width","xmlns"]),It=e(["xlink:href","xml:id","xlink:title","xml:space","xmlns:xlink"]),Zt=_(/\{\{[\s\S]*|[\s\S]*\}\}/gm),Ut=_(/<%[\s\S]*|[\s\S]*%>/gm),Bt=_(/^data-[\-\w.\u00B7-\uFFFF]/),bt=_(/^aria-[\-\w]+$/),Gt=_(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),xt=_(/^(?:\w+script|data):/i),Xt=_(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),Zn="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(ei){return typeof ei}:function(ei){return ei&&"function"==typeof Symbol&&ei.constructor===Symbol&&ei!==Symbol.prototype?"symbol":typeof ei};function Ur(ei){if(Array.isArray(ei)){for(var Nn=0,$n=Array(ei.length);Nn0&&void 0!==arguments[0]?arguments[0]:di(),Nn=function($r){return Mr($r)};if(Nn.version="2.3.3",Nn.removed=[],!ei||!ei.document||9!==ei.document.nodeType)return Nn.isSupported=!1,Nn;var $n=ei.document,Br=ei.document,Yr=ei.DocumentFragment,fi=ei.HTMLTemplateElement,ki=ei.Node,Hi=ei.Element,Zr=ei.NodeFilter,Cn=ei.NamedNodeMap,Wt=void 0===Cn?ei.NamedNodeMap||ei.MozNamedAttrMap:Cn,zn=ei.Text,rr=ei.Comment,Fr=ei.DOMParser,Gn=ei.trustedTypes,Jr=Hi.prototype,_i=ce(Jr,"cloneNode"),wi=ce(Jr,"nextSibling"),br=ce(Jr,"childNodes"),Dr=ce(Jr,"parentNode");if("function"==typeof fi){var gn=Br.createElement("template");gn.content&&gn.content.ownerDocument&&(Br=gn.content.ownerDocument)}var yn=Lr(Gn,$n),gr=yn&&uo?yn.createHTML(""):"",Vn=Br.implementation,mr=Br.createNodeIterator,Dn=Br.createDocumentFragment,Pr=Br.getElementsByTagName,Yt=$n.importNode,_n={};try{_n=De(Br).documentMode?Br.documentMode:{}}catch(So){}var Ge={};Nn.isSupported="function"==typeof Dr&&Vn&&void 0!==Vn.createHTMLDocument&&9!==_n;var kr=Zt,mi=Ut,An=Bt,Er=bt,Wr=xt,dr=Xt,Fn=Gt,ar=null,Wi=ge({},[].concat(Ur(lt),Ur(Ve),Ur(ze),Ur(Pe),Ur(He))),lo=null,vo=ge({},[].concat(Ur(Vt),Ur(it),Ur(tn),Ur(It))),Co=null,Gi=null,os=!0,jo=!0,To=!1,Mi=!1,li=!1,Bn=!1,lr=!1,vr=!1,er=!1,ri=!0,uo=!1,Ci=!0,$o=!0,tr=!1,Ar={},Rt=null,_t=ge({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]),mt=null,jt=ge({},["audio","video","img","source","image","track"]),on=null,si=ge({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Vi="http://www.w3.org/1998/Math/MathML",_o="http://www.w3.org/2000/svg",co="http://www.w3.org/1999/xhtml",Es=co,Zs=!1,ls=void 0,La=["application/xhtml+xml","text/html"],ta="text/html",Is=void 0,us=null,ya=Br.createElement("form"),el=function($r){us&&us===$r||((!$r||"object"!==(void 0===$r?"undefined":Zn($r)))&&($r={}),$r=De($r),ar="ALLOWED_TAGS"in $r?ge({},$r.ALLOWED_TAGS):Wi,lo="ALLOWED_ATTR"in $r?ge({},$r.ALLOWED_ATTR):vo,on="ADD_URI_SAFE_ATTR"in $r?ge(De(si),$r.ADD_URI_SAFE_ATTR):si,mt="ADD_DATA_URI_TAGS"in $r?ge(De(jt),$r.ADD_DATA_URI_TAGS):jt,Rt="FORBID_CONTENTS"in $r?ge({},$r.FORBID_CONTENTS):_t,Co="FORBID_TAGS"in $r?ge({},$r.FORBID_TAGS):{},Gi="FORBID_ATTR"in $r?ge({},$r.FORBID_ATTR):{},Ar="USE_PROFILES"in $r&&$r.USE_PROFILES,os=!1!==$r.ALLOW_ARIA_ATTR,jo=!1!==$r.ALLOW_DATA_ATTR,To=$r.ALLOW_UNKNOWN_PROTOCOLS||!1,Mi=$r.SAFE_FOR_TEMPLATES||!1,li=$r.WHOLE_DOCUMENT||!1,vr=$r.RETURN_DOM||!1,er=$r.RETURN_DOM_FRAGMENT||!1,ri=!1!==$r.RETURN_DOM_IMPORT,uo=$r.RETURN_TRUSTED_TYPE||!1,lr=$r.FORCE_BODY||!1,Ci=!1!==$r.SANITIZE_DOM,$o=!1!==$r.KEEP_CONTENT,tr=$r.IN_PLACE||!1,Fn=$r.ALLOWED_URI_REGEXP||Fn,Es=$r.NAMESPACE||co,ls=ls=-1===La.indexOf($r.PARSER_MEDIA_TYPE)?ta:$r.PARSER_MEDIA_TYPE,Is="application/xhtml+xml"===ls?function(to){return to}:K,Mi&&(jo=!1),er&&(vr=!0),Ar&&(ar=ge({},[].concat(Ur(He))),lo=[],!0===Ar.html&&(ge(ar,lt),ge(lo,Vt)),!0===Ar.svg&&(ge(ar,Ve),ge(lo,it),ge(lo,It)),!0===Ar.svgFilters&&(ge(ar,ze),ge(lo,it),ge(lo,It)),!0===Ar.mathMl&&(ge(ar,Pe),ge(lo,tn),ge(lo,It))),$r.ADD_TAGS&&(ar===Wi&&(ar=De(ar)),ge(ar,$r.ADD_TAGS)),$r.ADD_ATTR&&(lo===vo&&(lo=De(lo)),ge(lo,$r.ADD_ATTR)),$r.ADD_URI_SAFE_ATTR&&ge(on,$r.ADD_URI_SAFE_ATTR),$r.FORBID_CONTENTS&&(Rt===_t&&(Rt=De(Rt)),ge(Rt,$r.FORBID_CONTENTS)),$o&&(ar["#text"]=!0),li&&ge(ar,["html","head","body"]),ar.table&&(ge(ar,["tbody"]),delete Co.tbody),e&&e($r),us=$r)},ca=ge({},["mi","mo","mn","ms","mtext"]),$s=ge({},["foreignobject","desc","title","annotation-xml"]),da=ge({},Ve);ge(da,ze),ge(da,Be);var Il=ge({},Pe);ge(Il,je);var fo=function($r){var to=Dr($r);(!to||!to.tagName)&&(to={namespaceURI:co,tagName:"template"});var tt=K($r.tagName),sn=K(to.tagName);if($r.namespaceURI===_o)return to.namespaceURI===co?"svg"===tt:to.namespaceURI===Vi?"svg"===tt&&("annotation-xml"===sn||ca[sn]):Boolean(da[tt]);if($r.namespaceURI===Vi)return to.namespaceURI===co?"math"===tt:to.namespaceURI===_o?"math"===tt&&$s[sn]:Boolean(Il[tt]);if($r.namespaceURI===co){if(to.namespaceURI===_o&&!$s[sn]||to.namespaceURI===Vi&&!ca[sn])return!1;var ne=ge({},["title","style","font","a","script"]);return!Il[tt]&&(ne[tt]||!da[tt])}return!1},Ya=function($r){J(Nn.removed,{element:$r});try{$r.parentNode.removeChild($r)}catch(to){try{$r.outerHTML=gr}catch(tt){$r.remove()}}},Ao=function($r,to){try{J(Nn.removed,{attribute:to.getAttributeNode($r),from:to})}catch(tt){J(Nn.removed,{attribute:null,from:to})}if(to.removeAttribute($r),"is"===$r&&!lo[$r])if(vr||er)try{Ya(to)}catch(tt){}else try{to.setAttribute($r,"")}catch(tt){}},fs=function($r){var to=void 0,tt=void 0;if(lr)$r=""+$r;else{var sn=ee($r,/^[\r\n\t ]+/);tt=sn&&sn[0]}"application/xhtml+xml"===ls&&($r=''+$r+"");var ne=yn?yn.createHTML($r):$r;if(Es===co)try{to=(new Fr).parseFromString(ne,ls)}catch(Lt){}if(!to||!to.documentElement){to=Vn.createDocument(Es,"template",null);try{to.documentElement.innerHTML=Zs?"":ne}catch(Lt){}}var $e=to.body||to.documentElement;return $r&&tt&&$e.insertBefore(Br.createTextNode(tt),$e.childNodes[0]||null),Es===co?Pr.call(to,li?"html":"body")[0]:li?to.documentElement:$e},Ca=function($r){return mr.call($r.ownerDocument||$r,$r,Zr.SHOW_ELEMENT|Zr.SHOW_COMMENT|Zr.SHOW_TEXT,null,!1)},Ra=function($r){return!($r instanceof zn||$r instanceof rr||"string"==typeof $r.nodeName&&"string"==typeof $r.textContent&&"function"==typeof $r.removeChild&&$r.attributes instanceof Wt&&"function"==typeof $r.removeAttribute&&"function"==typeof $r.setAttribute&&"string"==typeof $r.namespaceURI&&"function"==typeof $r.insertBefore)},pl=function($r){return"object"===(void 0===ki?"undefined":Zn(ki))?$r instanceof ki:$r&&"object"===(void 0===$r?"undefined":Zn($r))&&"number"==typeof $r.nodeType&&"string"==typeof $r.nodeName},Ws=function($r,to,tt){!Ge[$r]||L(Ge[$r],function(sn){sn.call(Nn,to,tt,us)})},Po=function($r){var to=void 0;if(Ws("beforeSanitizeElements",$r,null),Ra($r)||ee($r.nodeName,/[\u0080-\uFFFF]/))return Ya($r),!0;var tt=Is($r.nodeName);if(Ws("uponSanitizeElement",$r,{tagName:tt,allowedTags:ar}),!pl($r.firstElementChild)&&(!pl($r.content)||!pl($r.content.firstElementChild))&&se(/<[/\w]/g,$r.innerHTML)&&se(/<[/\w]/g,$r.textContent)||"select"===tt&&se(/