From 393381d4568ea173c00e623d94796db657b2628f Mon Sep 17 00:00:00 2001 From: Mike Phares Date: Fri, 4 Apr 2025 19:06:29 -0700 Subject: [PATCH] Reset --- .dockerignore | 8 + .../README.md | 41 + .../docker-compose.yml | 42 + .../grafana/grafana.ini | 85 + .../provisioning/dashboards/dashboard.yml | 11 + .../provisioning/dashboards/gatus.json | 582 + .../provisioning/datasources/prometheus.yml | 10 + .../prometheus/prometheus.yml | 6 + .../docker-compose.yml | 23 + .../certs/client/client.crt | 29 + .../certs/client/client.key | 51 + .../docker-compose-mtls/certs/server/ca.crt | 29 + .../certs/server/server.crt | 30 + .../certs/server/server.key | 51 + .../docker-compose-mtls/docker-compose.yml | 27 + .../docker-compose-mtls/nginx/default.conf | 16 + .../config/backend.yaml | 21 + .../config/frontend.yaml | 8 + .../config/global.yaml | 7 + .../docker-compose.yml | 10 + .../docker-compose.yml | 33 + .../data/.gitkeep | 0 .../docker-compose.yml | 9 + .examples/docker-compose/docker-compose.yml | 8 + .examples/docker-minimal/Dockerfile | 2 + .examples/kubernetes/gatus.yaml | 115 + .gitattributes | 1 + .github/FUNDING.yml | 1 + .github/assets/dashboard-conditions.png | Bin 0 -> 43761 bytes .github/assets/dashboard-dark.png | Bin 0 -> 92251 bytes .github/assets/endpoint-groups.png | Bin 0 -> 39559 bytes .github/assets/example.png | Bin 0 -> 43633 bytes .github/assets/gatus-diagram.drawio | 1 + .github/assets/gatus-diagram.jpg | Bin 0 -> 77798 bytes .github/assets/gitea-alerts.png | Bin 0 -> 653495 bytes .github/assets/github-alerts.png | Bin 0 -> 19907 bytes .github/assets/gitlab-alerts.png | Bin 0 -> 258145 bytes .github/assets/gotify-alerts.png | Bin 0 -> 43954 bytes .github/assets/grafana-dashboard.png | Bin 0 -> 123479 bytes .github/assets/jetbrains-space-alerts.png | Bin 0 -> 31786 bytes .github/assets/logo-with-dark-text.png | Bin 0 -> 41374 bytes .github/assets/logo.png | Bin 0 -> 17608 bytes .github/assets/mattermost-alerts.png | Bin 0 -> 49649 bytes .github/assets/pagerduty-integration-key.png | Bin 0 -> 88555 bytes .github/assets/slack-alerts.png | Bin 0 -> 34441 bytes .github/assets/teams-alerts.png | Bin 0 -> 26659 bytes .github/assets/teams-workflows-alerts.png | Bin 0 -> 12548 bytes .github/assets/telegram-alerts.png | Bin 0 -> 37376 bytes .github/codecov.yml | 12 + .github/dependabot.yml | 13 + .github/workflows/benchmark.yml | 30 + .github/workflows/labeler.yml | 42 + .github/workflows/publish-custom.yml | 39 + .github/workflows/publish-experimental.yml | 34 + .github/workflows/publish-latest.yml | 52 + .github/workflows/publish-release.yml | 51 + .github/workflows/test-ui.yml | 18 + .github/workflows/test.yml | 34 + .gitignore | 23 + Dockerfile | 22 + LICENSE | 1 + Makefile | 48 + README.md | 2523 +- alerting/alert/alert.go | 128 + alerting/alert/alert_test.go | 192 + alerting/alert/type.go | 79 + alerting/config.go | 139 + alerting/provider/awsses/awsses.go | 228 + alerting/provider/awsses/awsses_test.go | 250 + alerting/provider/custom/custom.go | 179 + alerting/provider/custom/custom_test.go | 395 + alerting/provider/discord/discord.go | 199 + alerting/provider/discord/discord_test.go | 332 + alerting/provider/email/email.go | 206 + alerting/provider/email/email_test.go | 232 + alerting/provider/gitea/gitea.go | 217 + alerting/provider/gitea/gitea_test.go | 229 + alerting/provider/github/github.go | 190 + alerting/provider/github/github_test.go | 221 + alerting/provider/gitlab/gitlab.go | 204 + alerting/provider/gitlab/gitlab_test.go | 223 + alerting/provider/googlechat/googlechat.go | 248 + .../provider/googlechat/googlechat_test.go | 305 + alerting/provider/gotify/gotify.go | 161 + alerting/provider/gotify/gotify_test.go | 163 + alerting/provider/incidentio/incidentio.go | 210 + .../provider/incidentio/incidentio_test.go | 390 + .../provider/jetbrainsspace/jetbrainsspace.go | 223 + .../jetbrainsspace/jetbrainsspace_test.go | 318 + alerting/provider/matrix/matrix.go | 247 + alerting/provider/matrix/matrix_test.go | 387 + alerting/provider/mattermost/mattermost.go | 211 + .../provider/mattermost/mattermost_test.go | 287 + alerting/provider/messagebird/messagebird.go | 142 + .../provider/messagebird/messagebird_test.go | 212 + alerting/provider/ntfy/ntfy.go | 243 + alerting/provider/ntfy/ntfy_test.go | 414 + alerting/provider/opsgenie/opsgenie.go | 284 + alerting/provider/opsgenie/opsgenie_test.go | 361 + alerting/provider/pagerduty/pagerduty.go | 195 + alerting/provider/pagerduty/pagerduty_test.go | 265 + alerting/provider/provider.go | 123 + alerting/provider/provider_test.go | 153 + alerting/provider/pushover/pushover.go | 205 + alerting/provider/pushover/pushover_test.go | 252 + alerting/provider/slack/slack.go | 193 + alerting/provider/slack/slack_test.go | 319 + alerting/provider/teams/teams.go | 196 + alerting/provider/teams/teams_test.go | 298 + .../provider/teamsworkflows/teamsworkflows.go | 255 + .../teamsworkflows/teamsworkflows_test.go | 298 + alerting/provider/telegram/telegram.go | 191 + alerting/provider/telegram/telegram_test.go | 252 + alerting/provider/twilio/twilio.go | 141 + alerting/provider/twilio/twilio_test.go | 211 + alerting/provider/zulip/zulip.go | 183 + alerting/provider/zulip/zulip_test.go | 482 + api/api.go | 130 + api/api_test.go | 132 + api/badge.go | 373 + api/badge_test.go | 395 + api/cache.go | 15 + api/chart.go | 123 + api/chart_test.go | 83 + api/config.go | 25 + api/config_test.go | 46 + api/custom_css.go | 14 + api/endpoint_status.go | 107 + api/endpoint_status_test.go | 220 + api/external_endpoint.go | 70 + api/external_endpoint_test.go | 161 + api/raw.go | 43 + api/raw_test.go | 93 + api/spa.go | 41 + api/spa_test.go | 107 + api/util.go | 48 + api/util_test.go | 73 + client/client.go | 375 + client/client_test.go | 511 + client/config.go | 343 + client/config_test.go | 171 + config/config.go | 473 + config/config_test.go | 1940 ++ config/connectivity/connectivity.go | 53 + config/connectivity/connectivity_test.go | 62 + config/endpoint/common.go | 32 + config/endpoint/common_test.go | 51 + config/endpoint/condition.go | 364 + config/endpoint/condition_bench_test.go | 86 + config/endpoint/condition_result.go | 10 + config/endpoint/condition_test.go | 779 + config/endpoint/dns/dns.go | 38 + config/endpoint/dns/dns_test.go | 27 + config/endpoint/endpoint.go | 479 + config/endpoint/endpoint_test.go | 934 + config/endpoint/event.go | 39 + config/endpoint/event_test.go | 14 + config/endpoint/external_endpoint.go | 83 + config/endpoint/external_endpoint_test.go | 25 + config/endpoint/key.go | 19 + config/endpoint/key_bench_test.go | 11 + config/endpoint/key_test.go | 36 + config/endpoint/result.go | 69 + config/endpoint/result_test.go | 21 + config/endpoint/ssh/ssh.go | 33 + config/endpoint/ssh/ssh_test.go | 23 + config/endpoint/status.go | 38 + config/endpoint/status_test.go | 19 + config/endpoint/ui/ui.go | 69 + config/endpoint/ui/ui_test.go | 53 + config/endpoint/uptime.go | 24 + config/maintenance/maintenance.go | 162 + config/maintenance/maintenance_test.go | 360 + config/remote/remote.go | 37 + config/ui/ui.go | 112 + config/ui/ui_test.go | 77 + config/web/web.go | 107 + config/web/web_test.go | 187 + controller/controller.go | 49 + controller/controller_test.go | 102 + docs/pagerduty-integration-guide.md | 74 + go.mod | 92 + go.sum | 279 + jsonpath/jsonpath.go | 131 + jsonpath/jsonpath_bench_test.go | 11 + jsonpath/jsonpath_test.go | 196 + main.go | 197 + metrics/metrics.go | 84 + metrics/metrics_test.go | 127 + pattern/pattern.go | 20 + pattern/pattern_bench_test.go | 21 + pattern/pattern_test.go | 43 + security/basic.go | 16 + security/basic_test.go | 23 + security/config.go | 110 + security/config_test.go | 136 + security/oidc.go | 142 + security/oidc_test.go | 70 + security/sessions.go | 5 + storage/config.go | 42 + storage/store/common/errors.go | 8 + storage/store/common/limits.go | 9 + storage/store/common/paging/paging.go | 28 + storage/store/common/paging/paging_test.go | 72 + storage/store/memory/memory.go | 222 + storage/store/memory/memory_test.go | 135 + storage/store/memory/uptime.go | 43 + storage/store/memory/uptime_bench_test.go | 26 + storage/store/memory/uptime_test.go | 72 + storage/store/memory/util.go | 81 + storage/store/memory/util_bench_test.go | 21 + storage/store/memory/util_test.go | 66 + storage/store/sql/specific_postgres.go | 85 + storage/store/sql/specific_sqlite.go | 85 + storage/store/sql/sql.go | 1084 + storage/store/sql/sql_test.go | 853 + storage/store/store.go | 150 + storage/store/store_bench_test.go | 213 + storage/store/store_test.go | 653 + storage/type.go | 10 + test/mock.go | 9 + testdata/badcert.key | 3 + testdata/badcert.pem | 3 + testdata/cert.key | 5 + testdata/cert.pem | 10 + watchdog/alerting.go | 97 + watchdog/alerting_test.go | 538 + watchdog/watchdog.go | 113 + web/app/.gitignore | 23 + web/app/README.md | 24 + web/app/babel.config.js | 5 + web/app/package-lock.json | 20218 ++++++++++++++++ web/app/package.json | 50 + web/app/postcss.config.js | 8 + web/app/public/apple-touch-icon.png | Bin 0 -> 7677 bytes web/app/public/favicon-16x16.png | Bin 0 -> 1053 bytes web/app/public/favicon-32x32.png | Bin 0 -> 1703 bytes web/app/public/favicon.ico | Bin 0 -> 15086 bytes web/app/public/index.html | 27 + web/app/public/logo-192x192.png | Bin 0 -> 8659 bytes web/app/public/logo-512x512.png | Bin 0 -> 26692 bytes web/app/public/manifest.json | 24 + web/app/src/App.vue | 106 + web/app/src/assets/logo.png | Bin 0 -> 52184 bytes web/app/src/assets/logo.svg | 1 + web/app/src/components/Endpoint.vue | 186 + web/app/src/components/EndpointGroup.vue | 99 + web/app/src/components/Endpoints.vue | 74 + web/app/src/components/Loading.vue | 11 + web/app/src/components/Pagination.vue | 34 + web/app/src/components/Settings.vue | 104 + web/app/src/components/Social.vue | 36 + web/app/src/components/Tooltip.vue | 130 + web/app/src/index.css | 39 + web/app/src/main.js | 8 + web/app/src/mixins/helper.js | 38 + web/app/src/router/index.js | 23 + web/app/src/views/Details.vue | 231 + web/app/src/views/Home.vue | 76 + web/app/tailwind.config.js | 20 + web/app/vue.config.js | 6 + web/static.go | 13 + web/static/apple-touch-icon.png | Bin 0 -> 7677 bytes web/static/css/app.css | 5 + web/static/favicon-16x16.png | Bin 0 -> 1053 bytes web/static/favicon-32x32.png | Bin 0 -> 1703 bytes web/static/favicon.ico | Bin 0 -> 15086 bytes web/static/img/logo.svg | 1 + web/static/index.html | 1 + web/static/js/app.js | 1 + web/static/js/chunk-vendors.js | 7 + web/static/logo-192x192.png | Bin 0 -> 8659 bytes web/static/logo-512x512.png | Bin 0 -> 26692 bytes web/static/manifest.json | 24 + web/static_test.go | 74 + 275 files changed, 56094 insertions(+), 2 deletions(-) create mode 100644 .dockerignore create mode 100644 .examples/docker-compose-grafana-prometheus/README.md create mode 100644 .examples/docker-compose-grafana-prometheus/docker-compose.yml create mode 100644 .examples/docker-compose-grafana-prometheus/grafana/grafana.ini create mode 100644 .examples/docker-compose-grafana-prometheus/grafana/provisioning/dashboards/dashboard.yml create mode 100644 .examples/docker-compose-grafana-prometheus/grafana/provisioning/dashboards/gatus.json create mode 100644 .examples/docker-compose-grafana-prometheus/grafana/provisioning/datasources/prometheus.yml create mode 100644 .examples/docker-compose-grafana-prometheus/prometheus/prometheus.yml create mode 100644 .examples/docker-compose-mattermost/docker-compose.yml create mode 100644 .examples/docker-compose-mtls/certs/client/client.crt create mode 100644 .examples/docker-compose-mtls/certs/client/client.key create mode 100644 .examples/docker-compose-mtls/certs/server/ca.crt create mode 100644 .examples/docker-compose-mtls/certs/server/server.crt create mode 100644 .examples/docker-compose-mtls/certs/server/server.key create mode 100644 .examples/docker-compose-mtls/docker-compose.yml create mode 100644 .examples/docker-compose-mtls/nginx/default.conf create mode 100644 .examples/docker-compose-multiple-config-files/config/backend.yaml create mode 100644 .examples/docker-compose-multiple-config-files/config/frontend.yaml create mode 100644 .examples/docker-compose-multiple-config-files/config/global.yaml create mode 100644 .examples/docker-compose-multiple-config-files/docker-compose.yml create mode 100644 .examples/docker-compose-postgres-storage/docker-compose.yml create mode 100644 .examples/docker-compose-sqlite-storage/data/.gitkeep create mode 100644 .examples/docker-compose-sqlite-storage/docker-compose.yml create mode 100644 .examples/docker-compose/docker-compose.yml create mode 100644 .examples/docker-minimal/Dockerfile create mode 100644 .examples/kubernetes/gatus.yaml create mode 100644 .gitattributes create mode 100644 .github/FUNDING.yml create mode 100644 .github/assets/dashboard-conditions.png create mode 100644 .github/assets/dashboard-dark.png create mode 100644 .github/assets/endpoint-groups.png create mode 100644 .github/assets/example.png create mode 100644 .github/assets/gatus-diagram.drawio create mode 100644 .github/assets/gatus-diagram.jpg create mode 100644 .github/assets/gitea-alerts.png create mode 100644 .github/assets/github-alerts.png create mode 100644 .github/assets/gitlab-alerts.png create mode 100644 .github/assets/gotify-alerts.png create mode 100644 .github/assets/grafana-dashboard.png create mode 100644 .github/assets/jetbrains-space-alerts.png create mode 100644 .github/assets/logo-with-dark-text.png create mode 100644 .github/assets/logo.png create mode 100644 .github/assets/mattermost-alerts.png create mode 100644 .github/assets/pagerduty-integration-key.png create mode 100644 .github/assets/slack-alerts.png create mode 100644 .github/assets/teams-alerts.png create mode 100644 .github/assets/teams-workflows-alerts.png create mode 100644 .github/assets/telegram-alerts.png create mode 100644 .github/codecov.yml create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/benchmark.yml create mode 100644 .github/workflows/labeler.yml create mode 100644 .github/workflows/publish-custom.yml create mode 100644 .github/workflows/publish-experimental.yml create mode 100644 .github/workflows/publish-latest.yml create mode 100644 .github/workflows/publish-release.yml create mode 100644 .github/workflows/test-ui.yml create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 alerting/alert/alert.go create mode 100644 alerting/alert/alert_test.go create mode 100644 alerting/alert/type.go create mode 100644 alerting/config.go create mode 100644 alerting/provider/awsses/awsses.go create mode 100644 alerting/provider/awsses/awsses_test.go create mode 100644 alerting/provider/custom/custom.go create mode 100644 alerting/provider/custom/custom_test.go create mode 100644 alerting/provider/discord/discord.go create mode 100644 alerting/provider/discord/discord_test.go create mode 100644 alerting/provider/email/email.go create mode 100644 alerting/provider/email/email_test.go create mode 100644 alerting/provider/gitea/gitea.go create mode 100644 alerting/provider/gitea/gitea_test.go create mode 100644 alerting/provider/github/github.go create mode 100644 alerting/provider/github/github_test.go create mode 100644 alerting/provider/gitlab/gitlab.go create mode 100644 alerting/provider/gitlab/gitlab_test.go create mode 100644 alerting/provider/googlechat/googlechat.go create mode 100644 alerting/provider/googlechat/googlechat_test.go create mode 100644 alerting/provider/gotify/gotify.go create mode 100644 alerting/provider/gotify/gotify_test.go create mode 100644 alerting/provider/incidentio/incidentio.go create mode 100644 alerting/provider/incidentio/incidentio_test.go create mode 100644 alerting/provider/jetbrainsspace/jetbrainsspace.go create mode 100644 alerting/provider/jetbrainsspace/jetbrainsspace_test.go create mode 100644 alerting/provider/matrix/matrix.go create mode 100644 alerting/provider/matrix/matrix_test.go create mode 100644 alerting/provider/mattermost/mattermost.go create mode 100644 alerting/provider/mattermost/mattermost_test.go create mode 100644 alerting/provider/messagebird/messagebird.go create mode 100644 alerting/provider/messagebird/messagebird_test.go create mode 100644 alerting/provider/ntfy/ntfy.go create mode 100644 alerting/provider/ntfy/ntfy_test.go create mode 100644 alerting/provider/opsgenie/opsgenie.go create mode 100644 alerting/provider/opsgenie/opsgenie_test.go create mode 100644 alerting/provider/pagerduty/pagerduty.go create mode 100644 alerting/provider/pagerduty/pagerduty_test.go create mode 100644 alerting/provider/provider.go create mode 100644 alerting/provider/provider_test.go create mode 100644 alerting/provider/pushover/pushover.go create mode 100644 alerting/provider/pushover/pushover_test.go create mode 100644 alerting/provider/slack/slack.go create mode 100644 alerting/provider/slack/slack_test.go create mode 100644 alerting/provider/teams/teams.go create mode 100644 alerting/provider/teams/teams_test.go create mode 100644 alerting/provider/teamsworkflows/teamsworkflows.go create mode 100644 alerting/provider/teamsworkflows/teamsworkflows_test.go create mode 100644 alerting/provider/telegram/telegram.go create mode 100644 alerting/provider/telegram/telegram_test.go create mode 100644 alerting/provider/twilio/twilio.go create mode 100644 alerting/provider/twilio/twilio_test.go create mode 100644 alerting/provider/zulip/zulip.go create mode 100644 alerting/provider/zulip/zulip_test.go create mode 100644 api/api.go create mode 100644 api/api_test.go create mode 100644 api/badge.go create mode 100644 api/badge_test.go create mode 100644 api/cache.go create mode 100644 api/chart.go create mode 100644 api/chart_test.go create mode 100644 api/config.go create mode 100644 api/config_test.go create mode 100644 api/custom_css.go create mode 100644 api/endpoint_status.go create mode 100644 api/endpoint_status_test.go create mode 100644 api/external_endpoint.go create mode 100644 api/external_endpoint_test.go create mode 100644 api/raw.go create mode 100644 api/raw_test.go create mode 100644 api/spa.go create mode 100644 api/spa_test.go create mode 100644 api/util.go create mode 100644 api/util_test.go create mode 100644 client/client.go create mode 100644 client/client_test.go create mode 100644 client/config.go create mode 100644 client/config_test.go create mode 100644 config/config.go create mode 100644 config/config_test.go create mode 100644 config/connectivity/connectivity.go create mode 100644 config/connectivity/connectivity_test.go create mode 100644 config/endpoint/common.go create mode 100644 config/endpoint/common_test.go create mode 100644 config/endpoint/condition.go create mode 100644 config/endpoint/condition_bench_test.go create mode 100644 config/endpoint/condition_result.go create mode 100644 config/endpoint/condition_test.go create mode 100644 config/endpoint/dns/dns.go create mode 100644 config/endpoint/dns/dns_test.go create mode 100644 config/endpoint/endpoint.go create mode 100644 config/endpoint/endpoint_test.go create mode 100644 config/endpoint/event.go create mode 100644 config/endpoint/event_test.go create mode 100644 config/endpoint/external_endpoint.go create mode 100644 config/endpoint/external_endpoint_test.go create mode 100644 config/endpoint/key.go create mode 100644 config/endpoint/key_bench_test.go create mode 100644 config/endpoint/key_test.go create mode 100644 config/endpoint/result.go create mode 100644 config/endpoint/result_test.go create mode 100644 config/endpoint/ssh/ssh.go create mode 100644 config/endpoint/ssh/ssh_test.go create mode 100644 config/endpoint/status.go create mode 100644 config/endpoint/status_test.go create mode 100644 config/endpoint/ui/ui.go create mode 100644 config/endpoint/ui/ui_test.go create mode 100644 config/endpoint/uptime.go create mode 100644 config/maintenance/maintenance.go create mode 100644 config/maintenance/maintenance_test.go create mode 100644 config/remote/remote.go create mode 100644 config/ui/ui.go create mode 100644 config/ui/ui_test.go create mode 100644 config/web/web.go create mode 100644 config/web/web_test.go create mode 100644 controller/controller.go create mode 100644 controller/controller_test.go create mode 100644 docs/pagerduty-integration-guide.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 jsonpath/jsonpath.go create mode 100644 jsonpath/jsonpath_bench_test.go create mode 100644 jsonpath/jsonpath_test.go create mode 100644 main.go create mode 100644 metrics/metrics.go create mode 100644 metrics/metrics_test.go create mode 100644 pattern/pattern.go create mode 100644 pattern/pattern_bench_test.go create mode 100644 pattern/pattern_test.go create mode 100644 security/basic.go create mode 100644 security/basic_test.go create mode 100644 security/config.go create mode 100644 security/config_test.go create mode 100644 security/oidc.go create mode 100644 security/oidc_test.go create mode 100644 security/sessions.go create mode 100644 storage/config.go create mode 100644 storage/store/common/errors.go create mode 100644 storage/store/common/limits.go create mode 100644 storage/store/common/paging/paging.go create mode 100644 storage/store/common/paging/paging_test.go create mode 100644 storage/store/memory/memory.go create mode 100644 storage/store/memory/memory_test.go create mode 100644 storage/store/memory/uptime.go create mode 100644 storage/store/memory/uptime_bench_test.go create mode 100644 storage/store/memory/uptime_test.go create mode 100644 storage/store/memory/util.go create mode 100644 storage/store/memory/util_bench_test.go create mode 100644 storage/store/memory/util_test.go create mode 100644 storage/store/sql/specific_postgres.go create mode 100644 storage/store/sql/specific_sqlite.go create mode 100644 storage/store/sql/sql.go create mode 100644 storage/store/sql/sql_test.go create mode 100644 storage/store/store.go create mode 100644 storage/store/store_bench_test.go create mode 100644 storage/store/store_test.go create mode 100644 storage/type.go create mode 100644 test/mock.go create mode 100644 testdata/badcert.key create mode 100644 testdata/badcert.pem create mode 100644 testdata/cert.key create mode 100644 testdata/cert.pem create mode 100644 watchdog/alerting.go create mode 100644 watchdog/alerting_test.go create mode 100644 watchdog/watchdog.go create mode 100644 web/app/.gitignore create mode 100644 web/app/README.md create mode 100644 web/app/babel.config.js create mode 100644 web/app/package-lock.json create mode 100644 web/app/package.json create mode 100644 web/app/postcss.config.js create mode 100644 web/app/public/apple-touch-icon.png create mode 100644 web/app/public/favicon-16x16.png create mode 100644 web/app/public/favicon-32x32.png create mode 100644 web/app/public/favicon.ico create mode 100644 web/app/public/index.html create mode 100644 web/app/public/logo-192x192.png create mode 100644 web/app/public/logo-512x512.png create mode 100644 web/app/public/manifest.json create mode 100644 web/app/src/App.vue create mode 100644 web/app/src/assets/logo.png create mode 100644 web/app/src/assets/logo.svg create mode 100644 web/app/src/components/Endpoint.vue create mode 100644 web/app/src/components/EndpointGroup.vue create mode 100644 web/app/src/components/Endpoints.vue create mode 100644 web/app/src/components/Loading.vue create mode 100644 web/app/src/components/Pagination.vue create mode 100644 web/app/src/components/Settings.vue create mode 100644 web/app/src/components/Social.vue create mode 100644 web/app/src/components/Tooltip.vue create mode 100644 web/app/src/index.css create mode 100644 web/app/src/main.js create mode 100644 web/app/src/mixins/helper.js create mode 100644 web/app/src/router/index.js create mode 100644 web/app/src/views/Details.vue create mode 100644 web/app/src/views/Home.vue create mode 100644 web/app/tailwind.config.js create mode 100644 web/app/vue.config.js create mode 100644 web/static.go create mode 100644 web/static/apple-touch-icon.png create mode 100644 web/static/css/app.css create mode 100644 web/static/favicon-16x16.png create mode 100644 web/static/favicon-32x32.png create mode 100644 web/static/favicon.ico create mode 100644 web/static/img/logo.svg create mode 100644 web/static/index.html create mode 100644 web/static/js/app.js create mode 100644 web/static/js/chunk-vendors.js create mode 100644 web/static/logo-192x192.png create mode 100644 web/static/logo-512x512.png create mode 100644 web/static/manifest.json create mode 100644 web/static_test.go diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..706bbc44 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +.examples +Dockerfile +.github +.idea +.git +web/app +*.db +testdata \ No newline at end of file diff --git a/.examples/docker-compose-grafana-prometheus/README.md b/.examples/docker-compose-grafana-prometheus/README.md new file mode 100644 index 00000000..80519718 --- /dev/null +++ b/.examples/docker-compose-grafana-prometheus/README.md @@ -0,0 +1,41 @@ +## Usage +Gatus exposes Prometheus metrics at `/metrics` if the `metrics` configuration option is set to `true`. + +To run this example, all you need to do is execute the following command: +```console +docker-compose up +``` +Once you've done the above, you should be able to access the Grafana dashboard at `http://localhost:3000`. + +![Gatus Grafana dashboard](../../.github/assets/grafana-dashboard.png) + + +## Queries +By default, this example has a Grafana dashboard with some panels, but for the sake of verbosity, you'll find +a list of simple queries below. Those make use of the `key` parameter, which is a concatenation of the endpoint's +group and name. + +### Success rate +``` +sum(rate(gatus_results_total{success="true"}[30s])) by (key) / sum(rate(gatus_results_total[30s])) by (key) +``` + +### Response time +``` +gatus_results_duration_seconds +``` + +### Total results per minute +``` +sum(rate(gatus_results_total[5m])*60) by (key) +``` + +### Total successful results per minute +``` +sum(rate(gatus_results_total{success="true"}[5m])*60) by (key) +``` + +### Total unsuccessful results per minute +``` +sum(rate(gatus_results_total{success="false"}[5m])*60) by (key) +``` diff --git a/.examples/docker-compose-grafana-prometheus/docker-compose.yml b/.examples/docker-compose-grafana-prometheus/docker-compose.yml new file mode 100644 index 00000000..dad3de1a --- /dev/null +++ b/.examples/docker-compose-grafana-prometheus/docker-compose.yml @@ -0,0 +1,42 @@ +version: "3.9" +services: + gatus: + container_name: gatus + image: twinproduction/gatus + restart: always + ports: + - "8080:8080" + volumes: + - ./config:/config + networks: + - metrics + + prometheus: + container_name: prometheus + image: prom/prometheus:v2.14.0 + restart: always + command: --config.file=/etc/prometheus/prometheus.yml + ports: + - "9090:9090" + volumes: + - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml + networks: + - metrics + + grafana: + container_name: grafana + image: grafana/grafana:6.4.4 + restart: always + environment: + GF_SECURITY_ADMIN_PASSWORD: secret + ports: + - "3000:3000" + volumes: + - ./grafana/grafana.ini/:/etc/grafana/grafana.ini:ro + - ./grafana/provisioning/:/etc/grafana/provisioning/:ro + networks: + - metrics + +networks: + metrics: + driver: bridge diff --git a/.examples/docker-compose-grafana-prometheus/grafana/grafana.ini b/.examples/docker-compose-grafana-prometheus/grafana/grafana.ini new file mode 100644 index 00000000..0b97d465 --- /dev/null +++ b/.examples/docker-compose-grafana-prometheus/grafana/grafana.ini @@ -0,0 +1,85 @@ +[paths] + +[server] + +[database] + +[session] + +[dataproxy] + +[analytics] +reporting_enabled = false + +[security] + +[snapshots] + +[dashboards] + +[users] +allow_sign_up = false +default_theme = light + +[auth] + +[auth.anonymous] +enabled = true +org_name = Main Org. +org_role = Admin + +[auth.github] + +[auth.google] + +[auth.generic_oauth] + +[auth.grafana_com] + +[auth.proxy] + +[auth.basic] + +[auth.ldap] + +[smtp] + +[emails] + +[log] +mode = console + +[log.console] + +[log.file] + +[log.syslog] + +[alerting] + +[explore] + +[metrics] +enabled = true + +[metrics.graphite] + +[tracing.jaeger] + +[grafana_com] + +[external_image_storage] + +[external_image_storage.s3] + +[external_image_storage.webdav] + +[external_image_storage.gcs] + +[external_image_storage.azure_blob] + +[external_image_storage.local] + +[rendering] + +[enterprise] diff --git a/.examples/docker-compose-grafana-prometheus/grafana/provisioning/dashboards/dashboard.yml b/.examples/docker-compose-grafana-prometheus/grafana/provisioning/dashboards/dashboard.yml new file mode 100644 index 00000000..9f7232c9 --- /dev/null +++ b/.examples/docker-compose-grafana-prometheus/grafana/provisioning/dashboards/dashboard.yml @@ -0,0 +1,11 @@ +apiVersion: 1 + +providers: + - name: 'Prometheus' + orgId: 1 + folder: '' + type: file + disableDeletion: false + editable: true + options: + path: /etc/grafana/provisioning/dashboards \ No newline at end of file diff --git a/.examples/docker-compose-grafana-prometheus/grafana/provisioning/dashboards/gatus.json b/.examples/docker-compose-grafana-prometheus/grafana/provisioning/dashboards/gatus.json new file mode 100644 index 00000000..9fdf282c --- /dev/null +++ b/.examples/docker-compose-grafana-prometheus/grafana/provisioning/dashboards/gatus.json @@ -0,0 +1,582 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 3, + "links": [], + "panels": [ + { + "cacheTimeout": null, + "datasource": null, + "description": "Number of successful results compared to the total number of results during the current interval", + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 9, + "links": [], + "options": { + "fieldOptions": { + "calcs": [ + "mean" + ], + "defaults": { + "mappings": [ + { + "id": 0, + "op": "=", + "text": "N/A", + "type": 1, + "value": "null" + } + ], + "max": 1, + "min": 0, + "nullValueMode": "connected", + "thresholds": [ + { + "color": "red", + "value": null + }, + { + "color": "semi-dark-orange", + "value": 0.6 + }, + { + "color": "yellow", + "value": 0.8 + }, + { + "color": "dark-green", + "value": 0.95 + } + ], + "unit": "percentunit" + }, + "override": {}, + "values": false + }, + "orientation": "horizontal", + "showThresholdLabels": false, + "showThresholdMarkers": false + }, + "pluginVersion": "6.4.4", + "targets": [ + { + "expr": "sum(rate(gatus_results_total{success=\"true\"}[30s])) by (key) / sum(rate(gatus_results_total[30s])) by (key)", + "hide": false, + "legendFormat": "{{key}}", + "refId": "B" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Success rate", + "type": "gauge" + }, + { + "aliasColors": {}, + "bars": false, + "cacheTimeout": null, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 11, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null as zero", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pluginVersion": "6.4.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "gatus_results_duration_seconds", + "format": "time_series", + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{key}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Response time", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "cacheTimeout": null, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 10, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "connected", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pluginVersion": "6.4.4", + "pointradius": 2, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(gatus_results_total{success=\"true\"}[30s])) by (key) / sum(rate(gatus_results_total[30s])) by (key)", + "format": "time_series", + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{key}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Success rate", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "description": "Number of results per minute", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 2, + "interval": "", + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "hideEmpty": false, + "hideZero": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(gatus_results_total[5m])*60) by (key)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "30s", + "intervalFactor": 1, + "legendFormat": "{{key}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Total results per minute", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(gatus_results_total{success=\"true\"}[5m])*60) by (key)", + "instant": false, + "interval": "30s", + "legendFormat": "{{key}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Successful results per minute", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 3, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(gatus_results_total{success=\"false\"}[5m])*60) by (key)", + "interval": "30s", + "legendFormat": "{{key}} ", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Unsuccessful results per minute", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "1m", + "schemaVersion": 20, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Gatus", + "uid": "KPI7Qj1Wk", + "version": 2 +} \ No newline at end of file diff --git a/.examples/docker-compose-grafana-prometheus/grafana/provisioning/datasources/prometheus.yml b/.examples/docker-compose-grafana-prometheus/grafana/provisioning/datasources/prometheus.yml new file mode 100644 index 00000000..47564239 --- /dev/null +++ b/.examples/docker-compose-grafana-prometheus/grafana/provisioning/datasources/prometheus.yml @@ -0,0 +1,10 @@ +apiVersion: 1 + +datasources: + - name: Prometheus + type: prometheus + access: proxy + url: http://prometheus:9090 + isDefault: true + version: 1 + editable: false diff --git a/.examples/docker-compose-grafana-prometheus/prometheus/prometheus.yml b/.examples/docker-compose-grafana-prometheus/prometheus/prometheus.yml new file mode 100644 index 00000000..ea94b6a3 --- /dev/null +++ b/.examples/docker-compose-grafana-prometheus/prometheus/prometheus.yml @@ -0,0 +1,6 @@ +scrape_configs: + - job_name: gatus + scrape_interval: 10s + static_configs: + - targets: + - gatus:8080 diff --git a/.examples/docker-compose-mattermost/docker-compose.yml b/.examples/docker-compose-mattermost/docker-compose.yml new file mode 100644 index 00000000..10622c1b --- /dev/null +++ b/.examples/docker-compose-mattermost/docker-compose.yml @@ -0,0 +1,23 @@ +version: "3.9" +services: + gatus: + container_name: gatus + image: twinproduction/gatus:latest + ports: + - "8080:8080" + volumes: + - ./config:/config + networks: + - default + + mattermost: + container_name: mattermost + image: mattermost/mattermost-preview:5.26.0 + ports: + - "8065:8065" + networks: + - default + +networks: + default: + driver: bridge diff --git a/.examples/docker-compose-mtls/certs/client/client.crt b/.examples/docker-compose-mtls/certs/client/client.crt new file mode 100644 index 00000000..65b70513 --- /dev/null +++ b/.examples/docker-compose-mtls/certs/client/client.crt @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIFBjCCAu6gAwIBAgIUHJXHAqywj2v25AgX7pDSZ+LX4iAwDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UEAwwHZXhhbXBsZTAeFw0yNDA0MjUwMTQ1MDFaFw0yOTA0MjQw +MTQ1MDFaMBExDzANBgNVBAMMBmNsaWVudDCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBANTmRlS5BNG82mOdrhtRPIBD5U40nEW4CVFm85ZJ4Bge4Ty86juf +aoCnI6AEfwpVnJhXPzjUsMBxJFMbiCB+QTJRpxTphtK7orpbwRHjaDZNaLr1MrUO +ieADGiHw93zVDikD8FP5vG+2XWWA56hY84Ac0TR9GqPjsW0nobMgBNgsRtbYUD0B +T5QOItK180xQRn4jbys5jRnr161S+Sbg6mglz1LBFBCLmZnhZFZ8FAn87gumbnWN +etSnu9kX6iOXBIaB+3nuHOL4xmAan8tAyen6mPfkXrE5ogovjqFFMTUJOKQoJVp3 +zzm/0XYANxoItFGtdjGMTl5IgI220/6kfpn6PYN7y1kYn5EI+UbobD/CuAhd94p6 +aQwOXU53/l+eNH/XnTsL/32QQ6qdq8sYqevlslk1M39kKNewWYCeRzYlCVscQk14 +O3fkyXrtRkz30xrzfjvJQ/VzMi+e5UlemsCuCXTVZ5YyBnuWyY+mI6lZICltZSSX +VinKzpz+t4Jl7glhKiGHaNAkBX2oLddyf280zw4Cx7nDMPs4uOHONYpm90IxEOJe +zgJ9YxPK9aaKv2AoYLbvhYyKrVT+TFqoEsbQk4vK0t0Gc1j5z4dET31CSOuxVnnU +LYwtbILFc0uZrbuOAbEbXtjPpw2OGqWagD0QpkE8TjN0Hd0ibyXyUuz5AgMBAAGj +VTBTMBEGA1UdEQQKMAiCBmNsaWVudDAdBgNVHQ4EFgQUleILTHG5lT2RhSe9H4fV +xUh0bNUwHwYDVR0jBBgwFoAUbh9Tg4oxxnHJTSaa0WLBTesYwxEwDQYJKoZIhvcN +AQELBQADggIBABq8zjRrDaljl867MXAlmbV7eJkSnaWRFct+N//jCVNnKMYaxyQm ++UG12xYP0U9Zr9vhsqwyTZTQFx/ZFiiz2zfXPtUAppV3AjE67IlKRbec3qmUhj0H +Rv20eNNWXTl1XTX5WDV5887TF+HLZm/4W2ZSBbS3V89cFhBLosy7HnBGrP0hACne +ZbdQWnnLHJMDKXkZey1H1ZLQQCQdAKGS147firj29M8uzSRHgrR6pvsNQnRT0zDL +TlTJoxyGTMaoj+1IZvRsAYMZCRb8Yct/v2i/ukIykFWUJZ+1Z3UZhGrX+gdhLfZM +jAP4VQ+vFgwD6NEXAA2DatoRqxbN1ZGJQkvnobWJdZDiYu4hBCs8ugKUTE+0iXWt +hSyrAVUspFCIeDN4xsXT5b0j2Ps4bpSAiGx+aDDTPUnd881I6JGCiIavgvdFMLCW +yOXJOZvXcNQwsndkob5fZAEqetjrARsHhQuygEq/LnPc6lWsO8O6UzYArEiKWTMx +N/5hx12Pb7aaQd1f4P3gmmHMb/YiCQK1Qy5d4v68POeqyrLvAHbvCwEMhBAbnLvw +gne3psql8s5wxhnzwYltcBUmmAw1t33CwzRBGEKifRdLGtA9pbua4G/tomcDDjVS +ChsHGebJvNxOnsQqoGgozqM2x8ScxmJzIflGxrKmEA8ybHpU0d02Xp3b +-----END CERTIFICATE----- diff --git a/.examples/docker-compose-mtls/certs/client/client.key b/.examples/docker-compose-mtls/certs/client/client.key new file mode 100644 index 00000000..c0c38d8b --- /dev/null +++ b/.examples/docker-compose-mtls/certs/client/client.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/.examples/docker-compose-mtls/certs/server/ca.crt b/.examples/docker-compose-mtls/certs/server/ca.crt new file mode 100644 index 00000000..17338ee8 --- /dev/null +++ b/.examples/docker-compose-mtls/certs/server/ca.crt @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE9DCCAtygAwIBAgIUCXgA3IbeA2mn8DQ0E5IxaKBLtf8wDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UEAwwHZXhhbXBsZTAeFw0yNDA0MjUwMTE5MzRaFw0zNDA0MjMw +MTE5MzRaMBIxEDAOBgNVBAMMB2V4YW1wbGUwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDLE4aTrVJrAVYksFJt5fIVhEJT5T0cLqvtDRf9hXA5Gowremsl +VJPBm4qbdImzJZCfCcbVjFEBw8h9xID1JUqRWjJ8BfTnpa4qc1e+xRtnvC+OsUeT +CCgZvK3TZ5vFsaEbRoNGuiaNq9WSTfjLwTxkK6C3Xogm9uDx73PdRob1TNK5A9mE +Ws3ZyV91+g1phKdlNMRaK+wUrjUjEMLgr0t5A5t6WKefsGrFUDaT3sye3ZxDYuEa +ljt+F8hLVyvkDBAhh6B4S5dQILjp7L3VgOsG7Hx9py1TwCbpWXZEuee/1/2OD8tA +ALsxkvRE1w4AZzLPYRL/dOMllLjROQ4VugU8GVpNU7saK5SeWBw3XHyJ9m8vne3R +cPWaZTfkwfj8NjCgi9BzBPW8/uw7XZMmQFyTj494OKM3T5JQ5jZ5XD97ONm9h+C/ +oOmkcWHz6IwEUu7XV5IESxiFlrq8ByAYF98XPhn2wMMrm2OvHMOwrfw2+5U8je5C +z70p9kpiGK8qCyjbOl9im975jwFCbl7LSj3Y+0+vRlTG/JA4jNZhXsMJcAxeJpvr +pmm/IzN+uXNQzmKzBHVDw+mTUMPziRsUq4q6WrcuQFZa6kQFGNYWI/eWV8o4AAvp +HtrOGdSyU19w0QqPW0wHmhsV2XFcn6H/E1Qg6sxWpl45YWJFhNaITxm1EQIDAQAB +o0IwQDAOBgNVHQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +bh9Tg4oxxnHJTSaa0WLBTesYwxEwDQYJKoZIhvcNAQELBQADggIBAKvOh81Gag0r +0ipYS9aK6rp58b6jPpF6shr3xFiJQVovgSvxNS3aWolh+ZupTCC3H2Q1ZUgatak0 +VyEJVO4a7Tz+1XlA6KErhnORC6HB/fgr5KEGraO3Q1uWonPal5QU8xHFStbRaXfx +hl/k4LLhIdJqcJE+XX/AL8ekZ3NPDtf9+k4V+RBuarLGuKgOtBB8+1qjSpClmW2B +DaWPlrLPOr2Sd29WOeWHifwVc6kBGpwM3g5VGdDsNX4Ba5eIG3lX2kUzJ8wNGEf0 +bZxcVbTBY+D4JaV4WXoeFmajjK3EdizRpJRZw3fM0ZIeqVYysByNu/TovYLJnBPs +5AybnO4RzYONKJtZ1GtQgJyG+80/VffDJeBmHKEiYvE6mvOFEBAcU4VLU6sfwfT1 +y1dZq5G9Km72Fg5kCuYDXTT+PB5VAV3Z6k819tG3TyI4hPlEphpoidRbZ+QS9tK5 +RgHah9EJoM7tDAN/mUVHJHQhhLJDBn+iCBYgSJVLwoE+F39NO9oFPD/ZxhJkbk9b +LkFnpjrVbwD1CNnawX3I2Eytg1IbbzyviQIbpSAEpotk9pCLMAxTR3a08wrVMwst +2XVSrgK0uUKsZhCIc+q21k98aeNIINor15humizngyBWYOk8SqV84ZNcD6VlM3Qv +ShSKoAkdKxcGG1+MKPt5b7zqvTo8BBPM +-----END CERTIFICATE----- diff --git a/.examples/docker-compose-mtls/certs/server/server.crt b/.examples/docker-compose-mtls/certs/server/server.crt new file mode 100644 index 00000000..f936274e --- /dev/null +++ b/.examples/docker-compose-mtls/certs/server/server.crt @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFDjCCAvagAwIBAgITc5Ejz7RzBJ2/PcUMsVhj41RtQDANBgkqhkiG9w0BAQsF +ADASMRAwDgYDVQQDDAdleGFtcGxlMB4XDTI0MDQyNTAxNDQ1N1oXDTI5MDQyNDAx +NDQ1N1owEDEOMAwGA1UEAwwFbmdpbngwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQCgbLBnVrBdRkBF2XmJgDTiRqWFPQledzCrkHF4eiUvtEytJhkpoRv2 ++SiRPsjCo3XjwcgQIgSy1sHUV8Sazn7V5ux/XBRovhdhUivzI8JSRYj6qwqdUnOy +dG1ZEy/VRLsIVfoFB0jKJrZCXMT256xkYTlsgPePDsduO7IPPrTN0/I/qBvINFet +zgWCl2qlZgF4c/MHljo2TR1KlBv0RJUZbfXPwemUazyMrh/MfQHaHE5pfrmMWFGA +6yLYHEhG+fy5d3F/1+4J24D2j7deIFmmuJMPSlAPt1UjDm7M/bmoTxDG+1MRXSnN +647EzzS0TFZspHe2+yBbw6j0MMiWMzNZX2iXGVcswXwrphe7ro6OITynM76gDTuM +ISYXKYHayqW0rHFRlKxMcnmrpf5tBuK7XKyoQv/LbFKI1e+j1bNVe7OZtC88EWRc +SD8WDLqo/3rsxJkRXRW/49hO1nynHrknXJEpZeRnTyglS+VCzXYD0XzwzPKN7CyN +CHpYpOcWrAMF+EJnE4WRVyJAAt4C1pGhiwn0yCvLEGXXedI/rR5zmUBKitSe7oMT +J82H/VaGtwH0lOD9Jjsv9cb+s1c3tChPDKvgGGDaFnlehKg9TM7p+xc9mnEsitfv +ovSGzYHk29nQu/S4QrPfWuCNwM2vP9OQ+VJyzDzSyH8iuPPmkfmK5wIDAQABo18w +XTAbBgNVHREEFDASggVuZ2lueIIJbG9jYWxob3N0MB0GA1UdDgQWBBT89oboWPBC +oNsSbaNquzrjTza6xDAfBgNVHSMEGDAWgBRuH1ODijHGcclNJprRYsFN6xjDETAN +BgkqhkiG9w0BAQsFAAOCAgEAeg8QwBTne1IGZMDvIGgs95lifzuTXGVQWEid7VVp +MmXGRYsweb0MwTUq3gSUc+3OPibR0i5HCJRR04H4U+cIjR6em1foIV/bW6nTaSls +xQAj92eMmzOo/KtOYqMnk//+Da5NvY0myWa/8FgJ7rK1tOZYiTZqFOlIsaiQMHgp +/PEkZBP5V57h0PY7T7tEj4SCw3DJ6qzzIdpD8T3+9kXd9dcrrjbivBkkJ23agcG5 +wBcI862ELNJOD7p7+OFsv7IRsoXXYrydaDg8OJQovh4RccRqVEQu3hZdi7cPb8xJ +G7Gxn8SfSVcPg/UObiggydMl8E8QwqWAzJHvl1KUECd5QG6eq984JTR7zQB2iGb6 +1qq+/d9uciuB2YY2h/0rl3Fjy6J6k3fpQK577TlJjZc0F4WH8fW5bcsyGTszxQLI +jQ6FuSOr55lZ9O3R3+95tAdJTrWsxX7j7xMIAXSYrfNt5HM91XNhqISF4SIZOBB6 +enVrrJ/oCFqVSbYf6RVQz3XmPEEMh+k9KdwvIvwoS9NivLD3QH0RjhTyzHbf+LlR +rWM46XhmBwajlpnIuuMp6jZcXnbhTO1SheoRVMdijcnW+zrmx5oyn3peCfPqOVLz +95YfJUIFCt+0p/87/0Mm76uVemK6kFKZJQPnfbAdsKF7igPZfUQx6wZZP1qK9ZEU +eOk= +-----END CERTIFICATE----- diff --git a/.examples/docker-compose-mtls/certs/server/server.key b/.examples/docker-compose-mtls/certs/server/server.key new file mode 100644 index 00000000..79bd58c2 --- /dev/null +++ b/.examples/docker-compose-mtls/certs/server/server.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/.examples/docker-compose-mtls/docker-compose.yml b/.examples/docker-compose-mtls/docker-compose.yml new file mode 100644 index 00000000..9ea21f50 --- /dev/null +++ b/.examples/docker-compose-mtls/docker-compose.yml @@ -0,0 +1,27 @@ +version: "3.9" +services: + nginx: + image: nginx:stable + volumes: + - ./certs/server:/etc/nginx/certs + - ./nginx:/etc/nginx/conf.d + ports: + - "8443:443" + networks: + - mtls + + gatus: + image: twinproduction/gatus:latest + restart: always + ports: + - "8080:8080" + volumes: + - ./config:/config + - ./certs/client:/certs + environment: + - GATUS_CONFIG_PATH=/config + networks: + - mtls + +networks: + mtls: diff --git a/.examples/docker-compose-mtls/nginx/default.conf b/.examples/docker-compose-mtls/nginx/default.conf new file mode 100644 index 00000000..67bcbac7 --- /dev/null +++ b/.examples/docker-compose-mtls/nginx/default.conf @@ -0,0 +1,16 @@ +server { + listen 443 ssl; + + ssl_certificate /etc/nginx/certs/server.crt; + ssl_certificate_key /etc/nginx/certs/server.key; + ssl_client_certificate /etc/nginx/certs/ca.crt; + ssl_verify_client on; + + location / { + if ($ssl_client_verify != SUCCESS) { + return 403; + } + root /usr/share/nginx/html; + index index.html index.htm; + } +} \ No newline at end of file diff --git a/.examples/docker-compose-multiple-config-files/config/backend.yaml b/.examples/docker-compose-multiple-config-files/config/backend.yaml new file mode 100644 index 00000000..b17d3e1b --- /dev/null +++ b/.examples/docker-compose-multiple-config-files/config/backend.yaml @@ -0,0 +1,21 @@ +endpoints: + - name: check-if-api-is-healthy + group: backend + url: "https://twin.sh/health" + interval: 5m + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 1000" + + - name: check-if-website-is-pingable + url: "icmp://example.org" + interval: 1m + conditions: + - "[CONNECTED] == true" + + - name: check-domain-expiration + url: "https://example.org" + interval: 6h + conditions: + - "[DOMAIN_EXPIRATION] > 720h" diff --git a/.examples/docker-compose-multiple-config-files/config/frontend.yaml b/.examples/docker-compose-multiple-config-files/config/frontend.yaml new file mode 100644 index 00000000..700d1fe8 --- /dev/null +++ b/.examples/docker-compose-multiple-config-files/config/frontend.yaml @@ -0,0 +1,8 @@ +endpoints: + - name: make-sure-html-rendering-works + group: frontend + url: "https://example.org" + interval: 5m + conditions: + - "[STATUS] == 200" + - "[BODY] == pat(*

Example Domain

*)" # Check for header in HTML page diff --git a/.examples/docker-compose-multiple-config-files/config/global.yaml b/.examples/docker-compose-multiple-config-files/config/global.yaml new file mode 100644 index 00000000..c6c73995 --- /dev/null +++ b/.examples/docker-compose-multiple-config-files/config/global.yaml @@ -0,0 +1,7 @@ +metrics: true +ui: + header: Example Company + link: https://example.org + buttons: + - name: "Home" + link: "https://example.org" diff --git a/.examples/docker-compose-multiple-config-files/docker-compose.yml b/.examples/docker-compose-multiple-config-files/docker-compose.yml new file mode 100644 index 00000000..514cd558 --- /dev/null +++ b/.examples/docker-compose-multiple-config-files/docker-compose.yml @@ -0,0 +1,10 @@ +version: "3.8" +services: + gatus: + image: twinproduction/gatus:latest + ports: + - "8080:8080" + environment: + - GATUS_CONFIG_PATH=/config + volumes: + - ./config:/config \ No newline at end of file diff --git a/.examples/docker-compose-postgres-storage/docker-compose.yml b/.examples/docker-compose-postgres-storage/docker-compose.yml new file mode 100644 index 00000000..a1d76cbc --- /dev/null +++ b/.examples/docker-compose-postgres-storage/docker-compose.yml @@ -0,0 +1,33 @@ +version: "3.9" +services: + postgres: + image: postgres + volumes: + - ./data/db:/var/lib/postgresql/data + ports: + - "5432:5432" + environment: + - POSTGRES_DB=gatus + - POSTGRES_USER=username + - POSTGRES_PASSWORD=password + networks: + - web + + gatus: + image: twinproduction/gatus:latest + restart: always + ports: + - "8080:8080" + environment: + - POSTGRES_USER=username + - POSTGRES_PASSWORD=password + - POSTGRES_DB=gatus + volumes: + - ./config:/config + networks: + - web + depends_on: + - postgres + +networks: + web: diff --git a/.examples/docker-compose-sqlite-storage/data/.gitkeep b/.examples/docker-compose-sqlite-storage/data/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/.examples/docker-compose-sqlite-storage/docker-compose.yml b/.examples/docker-compose-sqlite-storage/docker-compose.yml new file mode 100644 index 00000000..35a4a9a2 --- /dev/null +++ b/.examples/docker-compose-sqlite-storage/docker-compose.yml @@ -0,0 +1,9 @@ +version: "3.9" +services: + gatus: + image: twinproduction/gatus:latest + ports: + - "8080:8080" + volumes: + - ./config:/config + - ./data:/data/ diff --git a/.examples/docker-compose/docker-compose.yml b/.examples/docker-compose/docker-compose.yml new file mode 100644 index 00000000..185ba525 --- /dev/null +++ b/.examples/docker-compose/docker-compose.yml @@ -0,0 +1,8 @@ +version: "3.8" +services: + gatus: + image: twinproduction/gatus:latest + ports: + - 8080:8080 + volumes: + - ./config:/config diff --git a/.examples/docker-minimal/Dockerfile b/.examples/docker-minimal/Dockerfile new file mode 100644 index 00000000..9dbd3f93 --- /dev/null +++ b/.examples/docker-minimal/Dockerfile @@ -0,0 +1,2 @@ +FROM twinproduction/gatus +ADD config.yaml ./config/config.yaml \ No newline at end of file diff --git a/.examples/kubernetes/gatus.yaml b/.examples/kubernetes/gatus.yaml new file mode 100644 index 00000000..f5c22c14 --- /dev/null +++ b/.examples/kubernetes/gatus.yaml @@ -0,0 +1,115 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: gatus + namespace: kube-system +data: + config.yaml: | + metrics: true + endpoints: + - name: website + url: https://twin.sh/health + interval: 5m + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + + - name: github + url: https://api.github.com/healthz + interval: 5m + conditions: + - "[STATUS] == 200" + + - name: cat-fact + url: "https://cat-fact.herokuapp.com/facts/random" + interval: 5m + conditions: + - "[STATUS] == 200" + - "[BODY].deleted == false" + - "len([BODY].text) > 0" + - "[BODY].text == pat(*cat*)" + - "[STATUS] == pat(2*)" + - "[CONNECTED] == true" + + - name: example + url: https://example.com/ + conditions: + - "[STATUS] == 200" +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: gatus + namespace: kube-system +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: gatus + namespace: kube-system +spec: + replicas: 1 + selector: + matchLabels: + app: gatus + template: + metadata: + name: gatus + namespace: kube-system + labels: + app: gatus + spec: + serviceAccountName: gatus + terminationGracePeriodSeconds: 5 + containers: + - image: twinproduction/gatus + imagePullPolicy: IfNotPresent + name: gatus + ports: + - containerPort: 8080 + name: http + protocol: TCP + resources: + limits: + cpu: 250m + memory: 100M + requests: + cpu: 50m + memory: 30M + readinessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 3 + livenessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 5 + volumeMounts: + - mountPath: /config + name: gatus-config + volumes: + - configMap: + name: gatus + name: gatus-config +--- +apiVersion: v1 +kind: Service +metadata: + name: gatus + namespace: kube-system +spec: + ports: + - name: http + port: 8080 + protocol: TCP + targetPort: 8080 + selector: + app: gatus \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..94f480de --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..468866bc --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [TwiN] diff --git a/.github/assets/dashboard-conditions.png b/.github/assets/dashboard-conditions.png new file mode 100644 index 0000000000000000000000000000000000000000..cffafbcc00f5e8e5812cfa769e55ca7c8284b131 GIT binary patch literal 43761 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 JS?83{1OSnir0@U$ literal 0 HcmV?d00001 diff --git a/.github/assets/dashboard-dark.png b/.github/assets/dashboard-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..22c303d99a30485c2428922d082adfe379930f10 GIT binary patch literal 92251 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%0DfxF6zeA^UnbkojvMn6ntS{VHFOipa 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%0D72M@dDkJbD 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∓BO{z8Iuqr(~w?c_UPAj>FQyk zo*MmR|Nc5|wa1gDB#xGf_2SGOW0pGlNqW)P%Q;FCacAdc(OA_P%(W}qZIL%lEyYYIN2x|bFLqU1iu zVM>)Oud-kCX2O7BWh@1nsw^fx#l?*#)I#R{E=m2$Sj&XQxjO;CDNy-5fv!-8WDINB z=P0QMV(QQvBXL_UJ=zd(Ji*AN?R_?KE+nLf#%vB*#%#ZVqm>*n)s~mQP>CWe zpNA1+G^15gZ8Gf=c`Gh-%t(WnNz^AP4|E>|S2+DL<%(4Jo8Clni@n$wS~)j##peZ ziPK3BobwYtm z9%7@f;y-WDCkOC zk!-4YrAF8Uks>1(tFR1}W|mo#tLP|`^tQQCqoVn35HAep!7FW}-9@3TnR(#Fg~5u; zi;uoTQoCFc@@)w!ptdDOA-A%R`GD&j#a>tUAac1fd@gnOM6TDBuTkRUcxqSEV&|@1 zGP8i6&)ao7jkAb9SSR?A6#;5JY$aCGd_mC%{EES2$JrN!QLc_;ehfS9sUE;!jIU@N zbX2PcJmSe7VbJb`Km+?)NT(%d^!$z*PUeyXS;0_9|BmOLJ{FdEP4fnCbXtX)QlX9s zew*>CeboREGl=j?a2S=%kj#qJE8J}$Ary+Uda{>7h#; zm2y|=*`8w1(Xd>gl1VSZq@ z^e>hwcs-q_z9Xfm=3{yK-T~Q!u_j2jU94Sx%NXSi?hSsjkTMyd;q3}N&}v$=TVK@N zpUhHo$Qck|kh?*x;go?3qc0@RK$lPHEm=Fj{^aXJ5~FWnI$EnT2-oFwQKoeSjvV{L z260N#E`+t+a!OK5jce*yi*Id#_s;E>rCEVgqP{|##)Mk#ELrN3A+EK)>Fluv>@FxAGtNaS zhq)kdM+W5@F82;*!N4Pr9yo2?aPg7WGzd5nIL-G|ImEco_iTH?v9BS-wkjE@hWBP> z_dcq$u<73tw(6$?rhH6mgXgXf58?AnIvZeuZdJk~b>QsP!fo;pq)1)**-bDT)l6=Q z4Z)t3-~&w95)jygM}mtE zmnl?8G)w`hB-c)sj*e&>L^OI14i$+h<=P7yeyp-nF{# zh0eBXOYTq7cP92YL8i(NIz`VA8L{YGaa^oPIeiZFReP=(#aucBrD-}dESME=*eejB zVLQ;z9tH4Uww{oPd%*&Wx2xtv~R6_2fBLnnezbN+o}3TCY+P{ zQ4##v!5TkH&oo&Nosat6=`kiQEN{pRp1f#zK%Y%5i#Y9(UAx<$EEUXH^cpo!vyQ)n zPFC$AEeu&>uD2ThHVf;U9I45Rn0_UR@+=+oVpL!y(Lwm~@r?^MZuVtjqQ8UD> z8s)*a;TI*``MCEIF-4r8LfElNfhmZn{yHp}V%Jugr(9pD6SMv2pPdwDFa$Zb;-!55nt<)}!iMU|G{e zjBDuHV#U?LnVCEa-2Jgw4j#A1u>IW{(rB%JurPkKD%{VT4s;=NTeiudD<}x(I%{JTJ7qByEdI$_;0dMo-0WKQKZ~ z$sZ*k*Q>z_;;AM|>gd}(Dj5;zmnZQiJGYBud5mG&?DnV;N~0Sd8K!ocj?4BIC+(bU z7v<(o{1HffJ)CZ+B)5eRA@eRKAhn3#nPg_u?&(W2PL3p1!D5mHKtzJP=jz(?3U-i! z{8KqXXYGXSEq5Sx1PLUxT|_hpmR{qMDO2gFy$jZ)G)Gj(4Q5@pz>u`^=K>O5)kou= zFOBC=4%?@b%2RX#4haWJ$4lv@M=S;s2;|a0f)KEcnf%WW-+1ei>uDe{xku_DaRbAo z*M2>k=1C%s^4!|7(ywB5S?b($c>uILynCZLi^{gMHdP*)%%NP_Q+HZe>b177#<_u8 z&o4g2H8zAV0)7}8YBv(!`6V)@Em!#Yx$l3tDc;$Zj@n3UuU=y-B|tq6bv~$$(F4^n zVnqJs(l`4>k=b4U4zm4oT$fsv!v|k8p5@yW8mM?5ke838`z_)S-R@UsyJeP&75&2> z3w{d!_#=+1wqWqu*_Pl>*eOV^CZ+LI%u=O+>GX0xXMCXEsL#nnqErIV`Z+k!Q zvq`YHq>`^%ACz$G-Iy>Ge{T8D@qpLC-*Qo~e+$O1%L2`A-@7V+=#=4SRpjm2kFHm= zy|^p{%?=9ag#U~e{LUII``lJnUYa|dwYx?y(#u0|9Nd4KWiW=4nfuH43Y%UJLH#*2 za6Nsn;!e8M=1``xnWT68} zSzLYFXQ#^Tk`2Fddv;B!?AEd;lJ)@Zo%&w^R;+W0C=cG28=C=kBxPQg0pETD8kX;w z@SQGHhv}(4_qg#`UCl1N$lq*~a-Uc7M;m z{XH@xv64gdRhsAdw&|1$@i3+}a<>yXBB;K8Kq!UB9@xO;wYOM_RB1h4_BFck7Z&XOKG zl(4!FeD%_Q@tDrz9R#I1oMnSm-$I1fjx9eEvgY@T#$0-Mq^Bk0?c2D3_I;-TR)%x= zZDqAaQwTy1B<{oXv-x||p^QEHE!BnrjtNr*h(E`&-o&oH{N^RARIuBp($y{_FaV_s zFW{H_I)5*2{hgzijiA~SV+^*X3baj)ZW8QJIh5^jYMZ;=`l~_%Az#or|PWupRkEkv=W3OF;+UG}3uFFi8y47k4Z*jBx?TV5bxyKlQi zyXTQEWiX5c7^`E_y03gHe~_y0-5Zu?=le0^tcHzX8r(a zqGiz#xw3KvNLnXY-?$RU^oy|#1~0PxRF*%4NDwkr$J!O^imqw@%1?jv=UNf6mRskJ zo~W1FN9F@+U0&M*I41Vss} z7iI|KC;XVW5?nav7J=WXK3`NbBWXN)07-gV6>jAQJuOa#qz{uD)-%A&i!9$DYfCHr zS!?FT50v=@1$^z!o8Mrhl0Sez-9HJ?wAaCHVzJ@`$d>{ZbYY-?*|Z_k^TIZKdAJ+hhO7X+C#|263~GRTyNpS65!BXHsh+S2GK;F&fY+Q%gT+eSnJDQISKaR zp^%`ja=ft6bjlBXj=nAv!-Z4BnO>2yuA7N}D4oBpgs-3y&hWaOwq|7|z!Zne9C_jn z-82mE{9#|cHlY5bcuiZUh7(&5(qJzInnO?Fcc;ioubo7)Bp`xNCy2Gv&j}6osQII& z{s#=zO~iT^wae8a$xGAvA%qiC6C@yIg|!(#u+6Ez{{+^|YK$nKGcOR*T3)9AV2OCq z)9+rdeo$dfW@R%(0ASsHRiD`}Gd?97d+VLY?f(I_=C&1fr6nhA`uzxMJ?T)wbg>Y3 z6T2p^{hw*<&uI3$NX^EaLk$lHB7X=W>~t3DmwOM`S_`7X*dp!aLj|_t70W7=61>u#atI zDM^H#bXNG-0Q03(l8xL2lvAmcf%rti@*kjP>6QXw2|BtX0T{wqqPvgZdnw3C)e={V)_d$ppNfrBS&yjuKJ9ut^HJ7 z#F4h&gqBn2YkPY~#UwS*Pr$3GDzwfzt4ME~@o8-;`lwz(`Qz-vs`UvUt`@z@(+LRg zT95Twr4|urhw6RSmhQl}hOM#GvfhL#+Kj`f8enQXFVCi+B;M^vjsFxWVbfqJ_BF7h zO3CLkbBmgL!7f@YKqtK?(@mP$557dZh!VQF{ctYG-*H zH*RTOe|w~3kKG8wbi-CNDBY#PYVMvJ_KOfJPha$&Nzg%;QMI1I(Kiy7w?1yB!HVwp z3INKsU%GWf+H=BI5WHWD3q_35%`OWM_rd2+v&L6~kIW?XuK5*lTpa}JOj{>ix5Vb1 zE!J==$o9G(P;YD+P&C@avZxhcXTD9o*iR>&verr93{6j)vGzR(A#btPHG}Vvg|=>x z1F+pbH=h!&E#x%Ymxnpe$bwunD^57I^4dVa?f5s~y`%>y-PVK0Y{P!j5Ku_q3b&)F z-8c-@3B@lMM{1;OdOwcgwtTS<5!ne;F46H^<_1A7(E6=^G|=@~oZKuyoJbwvg66Z5 zvhRQZj*g`&NrA!3&G6BYShhy5faB6x{oz^KfzuPvUORhnn8n-x3Vz9%AK~ z*Za05A`Ckb``Yxm2pLNl{xmjA%!q~n#N0irMc{Ansb4)WkEIk zP`T7w5)VYT)d=3C>39A+T2>WhWM}FZry@ow@Nr-8b&5Z{*Ud0WnZh*?l%8Urbk;sZF=|d{?8~Y0yO8vr@x*UAtDec5;*9Ge2 z`}XegTdOi3J6jy~@-&kalD?okj)**9+$DfE2SYeldE#UG#%rm6IsXx}xYBpf#euHx zNnJXnaDW@e0_{z;20$;^`$HqV0=q^Pld_9978g_$co}am=7zN&HH*1-wOhciJP|;T z7FT)q@1AKuw)mg>DelaH1DR9x{$Y;7k)_-qpj_{t<;QeS=U;GkI3;<;v(>*t|4=;= zNSF9|Ry$`v<)RcqiQj)pWVW?aLz$uExaL|}z3y0tk$&~`>DmPUvf-7^?bTbd zlqb*HhOM8LRkCzG;qI<~iYnEJ^aP##s;phjb1%M{AX~_SOpZ_TS`#@wb}V>+=BbUy z-Dp^2BoIMmNLOkiflVItzO$;rZnIy@5`x zoa1hXf}ENV##sl31jBTrBVKo;tiL{rQhq%Cd|2o314FSlW7gQr$yqr5f6JV;CQPmi z+{34eL*#`{wvVC53jFTYw-78U9%mo8l&?&?STr`Et(R;bmD74sfR`eG0ZCS<6a&HJ4J1CwH@Qf$ZT22{SSh50$xd^za{eAUmjW^-EIV5-p(}5FG}+wr z&XkdI@Uq;%SWKMtEjJE0QfJP=cUYk(K3ZyOw)#;Jj*VKS$Xoqy1#~HF$Y&+DbA~%F zn3&hPBTfxKyVWgg*sCZKu@$(30bTkl?(E-9^pZIl162r|sTj`97@mbzUe|+mV6;GY?J( zaUs^@V7PHr-f$tg<+PaJeUWLf`m72r04XPcR};RI`48@k@UJi(KCd}G-yLqbC-PT> z-g_bEQgBGHz4~4ed>8ksYvWMUP6?3vLI|<_LU{ROip~oWk}iGL+0pm<@(079-*Nc= zpWKz!Qs@d*fSECtQq-*??X!A6Q}d5;wY5g5h>2S>lv}9X+&p{Lp~=yFT&-H2NTTN+ zEKrxs0$c)$oSGOfS6DCijt2z=fwvzdIW;Xg)ro+Ga}RDbEln8hynEZ&)Fd;?OEZ{O znj9U??v^llB6!Vyj>z6@j(Z?46z#5m9m3@|JWIG`Z}07cEzBzGNv)`;0D#$Vu>GyA ztwRF?PnwoTeb=Dx7nf9`vxLF=U1t5a4!o?YQ~`1-fYADSBh+?YL`0OLv9TCP$=kc4 zqSk8e-8cHYVSP5%lbJLwyk*zPg_mjHxeu zJCEJW$;pw+e|4gDXb9MkRuFA#NbJ1WBC@^y5!6^Az=oOpGal%$>Hr8xQB-Zy*-Y zAxma;N4NR3=~uxn_D985`N*dEs>$7rm5-gJzp^i0BpiNFD&FsS^B2nD8smW*jutUbOtP8DI*L zH~qTk)RDd-;YGUw3(v!`J`F?L>wS1+B-6npIs}Rc8q%uMxd_Ko7F*P~V#yQn^#v2u zVKq@h0-v5I3eG?sGKyOdkTubW6O}R=@JRGK@!0Q7=!?JZ9aj0*yrB)S!HA@pXx8?j z8umM(YNqcH{Z0ddyFP7+@dgZw^OuBB^;Y%uN zQOhOc0YFq#e^pJ7LhR6@S&+cjHUTayQve~FHD%M?lte?cW7*cMYc6ahFr-|$?@64si%kgh4y?WDleLZD}s?0=JjHS{Gy^{%VRlO0LG`= z8^kZErl)rp;GMa8gkJN0p&&cn+d`QkTR$y-OPcm?*hKLg^P!AwQ&$7glS9|4z25QX vVwQ4<(%Y?Y^-+@@Taj*d-GFdnP^P;kOA>CioU?r*EDCVR>gU=EH-G(qB(M`W literal 0 HcmV?d00001 diff --git a/.github/assets/endpoint-groups.png b/.github/assets/endpoint-groups.png new file mode 100644 index 0000000000000000000000000000000000000000..25d34ce0dbbc18d5b01139d6cc24e5d383d87e71 GIT binary patch literal 39559 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 literal 0 HcmV?d00001 diff --git a/.github/assets/example.png b/.github/assets/example.png new file mode 100644 index 0000000000000000000000000000000000000000..8196dfc0523ce6c851a03afa69326b233763ef60 GIT binary patch literal 43633 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 \ No newline at end of file diff --git a/.github/assets/gatus-diagram.jpg b/.github/assets/gatus-diagram.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3f90eaf87cddd95eaa6bc0cfde1e9a82e0bee129 GIT binary patch literal 77798 zcmeFZ2V4{Dx-LE;^kQhzB}x&cDphF#5djgw0tgYXfHVP>79sge0Rc{yY4|%h zOaNdJfWZY|n=JqmdQLXjF9-0a1I7YpWn<^y{)~g;MOU{=?jD|A{sDnO!I+Tiw{Ay9MaRU(rQE%jnwEb5 zLB`{}{DLQiPoF(6E3c@ms(xKl+e~a}ZEJtq(K#^q{=?Al$VU=|I`wIK=JV{AIr{Rq z?>|;n!Jlhe^MV2JU#A8A{B>gg%e7iyP)w2j`LTA zWYlkR3Li>-RNBNPt8tRH{lc|=ZV@?6iadR5YQN0vZ<|=ee`#ianb^P0iv;k(VbIHi z3jio!<5Vt7I5)5d_}8z0Ey2H};BVRp@HqNv8j?b}S{! z+A-T)C4(*7E~$CqQlh}i;`il|;;Ba6fd3{y8z65Z%$@~{Hi4*|Ru{(0TsTt#O&Q+c z$=Qyh4Q`F6z6WqbORVlD@W`K9!j!;m$1T@}BS25TXqWG*Sldm2Xbp|q+rTyfJ{8(L zGqN|10D3Z@6P^fM~ynsf=VC%TzO@| z>Te-Lc5F_$99ndx$>RLXv$lh|2%~>cZG{lsc!cv(;|zOepDpp8x!0pM-*6j!M@vR1 znAC}?Fni)0Igvcp%<-Viyd}~RwZB{_;#P#wUQZN17D3lpi3Gi=z0nQI0}Ru=F$Ri$ zC+XYq8i%|wEhOKx*t4H!-BT~`)92>{LW==mzy@X{MaGB8V9Y3K@gi@gb{z5cQ1zw0 zs)!ou44tp*dS>yF?M-p5h5jRx&Kcl`-&@A3|ZoCm9S8bP*df&+mYN7X9@%u?%60NxG!-^`WJy; zEgskOsbEi6OZX<@o;GF}5Idyo2Q0l=V};o$S6qW^O{%Xt&EMIUNoj8*M3F`O0&nOw zW=wZ44&6>Gn;`lZjE|HNue}@^%&mz-X^Y$G?C+AE9owteV#DEY#J07L9H|&Q^LP&B z^TypxKpgj_op=v6SFyFeznuQZ_ywc5h!`Ry6(%7Yg(`)vJuke*Y^UOCQF3v0P9*MhQqNpCJ_tq zcz%ZAu)+tG?~50{qAgVpNyhVJ?xlvtJbpmAl5Tk6K%8-{LBW;Ld+=7nz04k51wQdb zy^sIJKw6s>nu;ert(T{=4BM~R)jBQslaH2oM2Kh1sHeB>4w2ZchRk}!zAOwcAb=e7 zV5SJxS(f&x0ZCh|$e2le>g>Iy5FePi_vM053S0KXlF7*0jUO$^t?~rYr)2ZQ$9~G1 ziurKKW@LFuyIp8dev`}y4P9S5?f#8BZz0o$l~TGGm}&AZaJ zmAMqTKH?$>+Xso_0{gxwm^O^IPGEWjd0;@-P{W!vbx6A*${QR*6ZZ?5r zf9V0SyHTAl*^LaY{pu+q6zNDt7K+%ruE2z(Vh|s_2mZn1AIyD%ch;M20t(i&Z_JKO zAc63GiqZ-EaQxK)(BA~8$Yu+yKed>nt=6oiWIL$gNMq(a{c>e&_QaR6kd^8(M+?ns z1wRTpZ>;lND>(Fu$b09ik+=)(2~!5zE+bnOxydgQ_tOe8!FvfYgzrHp$jGB9F3eL~ z8;IMFs%W*4`NG+V=V$S7+;M){xNerzRNSh0paeVjX8 zvV|a~?>%K@dAxtA!cwgAbwJFys)%K8i$XUvkK`qHm5aWxpmV_GZT?2m>HkOycFP_} z+fq?Kb#c)-E7lCRO`s6D&4%N7-!BFNHj^X3lm*v~=#cYeD8)EPQ;TL8Kf~NI`|zX2 zZHQr}eV465M97m?R%GH9FrZiU_%_RPAhG5kOE~17HJh!Uf}~0kgrq83?F1wm+ab|N zX1?D9z8_(3$5Fc8<9?b#k|u|%L~RHVB3;01Kak85Taw0wT!eJ-VLysJQ|b*Q^7l?d zqV`X6zeuJ$16r8K!UBc{_<;7gIYVKE9R0&8sB`rY-K__5sHJtZ`8$M5y9*r`#y1`( zym&#L8{t>y1FrwcMf%4v;TZeY9I06xViWLGi(2a;_`J0C)49nw?^TpHt3#@2>V`z) zz7XF%^|#M%AH4R+n8ek!XQw0-bqpf;aI-vg9VjO7i`@i%x@-jn1ft&>i~$J-(SeF* z+V^e66KXUvGaMz1qkX{SL7~WZ*tgzA|Md`WBy6!>g)U23ie{J;d7N~qx?AF5s6aEU zew0t?&yVs(uh*I9C7qZyf05ue_M+h8&mB+Uk3snQ2~c6B2ppY6N@CQE=+Y@^*>L)8 z(JNjg|CWeTPr(2IzD9P*aPF$0{ce)M&wXu-3~PV&>)|~O`xv(GnLBhMHPv`9b_hXC zVcXhBzow7w=M&l@qiGY~4?bs8EEmC$t5 z3L-*lJw|H>(xlHUqCRw6&Bfk*CADs*INV-|l5q^MzF&pm zp4{QmY#oP_s6VxCJU_t$=1{zmTq7iq$8=aFz*5CZ>%hPab@_+<{jp3Bd2-brsz=P8 zVlG8)A%ast&iC*3!jT~xFob{nvTJt<`8?5l|uoyIL zi43;Zg!5b(J-x>vSSDiRY^fYv$F8h=5a&sVB86pn@)Z|X6A5Cby!>r~2a*F3e37;% zn#0)vDQ@6L=dYf@ft)xTcYsS=ZBR1bO8E;9$CBv4Gl9iI=>F+gwSf7zAMVtL1zbBc zN4HS6mMVRlkWyv8%fU#0)N~`k5b5FP6YM=jjzN*^swQM^d&R$KXZvRLV$AevaDaQ1 z+|_+)+g;su9hu>Mxor>Jacaep)==t0Rl>?oM>{R_=Y*V{7~;a~XX zX+=d8tB0>#h_Q!Dh) zyHY;BpjK~us4<_c!gqNG1Sl>lnL{}Np0Exid%c1ub%#W|CE;}+!-~R#U?qPM)j=1J zT2-PWonGV~lfN3cH-z{0-6*Gg*KiMp;zss&Lga+f8+tuifZ;vG=eufBN}G4Wq|6Q2 zCAIZ`@{m2@=g0Y0{jDXxL%hg^Gif`ei~pIzs%dB0P2g@J7_tetZUSvCYgcL0e=#mn zg-H!U3}x`q3KDe4;nUwsB`@^qEVzu;YE^CmAF+Ccvy-h$oD zMPrRJ?N>-}zz@D0Z+~hXRm(G4MPzxO)>a9KVcOuAls6 z)Cn3@vVkI$9RM~M3S_y#*!AO}5_3BtPG3HEmMH+@uNL(d&L(3tT32g7tdx3PAnOZ$ zS@sN~j~`Cq36lJ{qWSO$w#^OrQSx!H6<0xyhnzv(_c%}J#w5|mN-}8AR9%<(vg%e? z8#5=pqiYP!*Wn9}T#>(EseLitgBP~)7pETUHbushkt$*Pz*@g;rcXybvN`BU@sy|E zV_#wqx;-Y7&YJqj^@jN4{G}hK`>Tv@+#fBxdw-`>d0ILkJV+H?gco~X*UsZ;&q7<5 z(G}Xrf><40ti-mI{*Rt^171F?ujS>7lD^(fLcA^v{(qsIEx!O}8@o}T@z#o|iI0{#+$t=og8!o#xv ztnPl_Ir__@{E`~}zxsc$Kd`E78hKw7>?G)eD#!Dyp!E@r+WzgfUUkTM`klI(wyPPy zG9oTpCccr33`2s$_7JuZw-j&&i=-B-dS~$vCDJNCIa!B3vf#Z|kRYTVIxsZ(?40n= zV;b3e;OPH$G)8Kun69vLb-3QCkYO}z$cG`GpdYy_K`fjP#<5~ewKnp-dOAzbYTtKF zU{5*3@(QSvo(WerqkB&ZquiGpgh7g7N0C(tdJ$Bi8c%y&`9cY*1fJvA$EIqMa~6G&5N5sbI&nEvMeIz~GUTjP4iFZis6 z&N~bJKKLVg&O)7^Z(711p$RKBZwHHk)Lr%GC`7 z{r6)Y6Y%OVQ z-)ZHmLPBxC>Yp-r|2UBLa*4nu&@sq>a*k!VDYcCQ;I(iAyMHtr^Ea%weo+f<88Si$ z)Itu}wFw+Il*&ce{u1%e1QKGM;k4d(O&lD=x9Wza7K2d-g<$Ks*Qpl2-?vN1pLo<$n0tfPzGTW7#XH4-LAy%zz|oigSO`%&5E4z(oGy@>|9a* zsZHPOej~ZDXT;|Cb_;`>xqM29kNzviKPanI>LEYYQBGG4U&*Ht7`v`t|9Zrx^U8Tv zSAnLPa1E^cI*QpwKw#xxVCLJ9<-^TIdcn70SC)~QV?Ya%Sb`4E*Wj~D^qkoX%q_PDg~^2*>EI}L32yi`?Hnlgc;pM2 zaS1}LU6(#yWF{6unMZ+I$QKZY@NRLvl{1?_p66C3^R{mm6f$|h?+qAkMM*B}r?&ir z9HKQh#&X@7vI)e8jc!Edq0ihX>UxhgnEu&tRCS}_;^O#2zsrG|e@SZmzA5AAA+YN2 zqI*;An{WW*$S}?;C)UQxS&`O|T0CLuSpYhnOA5_2pGm%GHy|FtehpA=7wa(afJ4Dq zqz+w_nbZzp?L1f|2weAqz;!EzDfH;2TR43LnR^ph&Vc543MYV@T?m5$v&rga0^_Y&dxhJzp*ZLqCjZ^OE8B^ALjOL~&$KP?w_ zDL1rK z(66G|QCO4Rvt$;Le}}kP#!+^#PqV1&&O$(@CETo=PX(-I2|3B^NbN=w;um0yNiEEE{os)V|z&EHF%1S zKOXtDUT56~Q?|AteE%GM_pL``BXt*V$~ZVU9f>p-7JWaae7Kb)yrd)<`k zt9+NuPxVA%@4Pck2)Cq5QhMY4^l?<}*PuH!%Z!?}8mF%iOj~LlS~G2_x~_eTHz;C8 zDD3%#(j;xX?YG7JWQmK{I1?Ekynvug+EhD{nwGYP4A(a)#CqPMqGGhv_%SQ3n7?$X6^?u6|m zuvs#TdrmLQ-6?o*D$RE60U^!u=Kl55VHY~fnxB8Z)B=3J;ZLz{^)yt)dCW@$m2$*> z8miX_r0@lGQCh#2hCcbiKRQ~3ZY|nqm}s}EaK) z1N7czrKM?H}6o&qaBsgRav`U zr?h>nB_FO0~3C`?kNIccnDx+148U$Hi1uHn9{h9D0({cA@-n=bkXnWu0Kb* zuxXdFU`9rJ69Hl@NJET;0O8+f@NlGtb&!|6k?flQMz*u zzyH+VeYv61iNN*a46X8qzEPu16RPj?PWAf=&YRqHnUk%}@}K?uzK^5mm&N(>eE;eT zrB4+1xfJfH1@) z^aM?)?nDEQ<_z+RLiCSC?v*a5;jaNDAhTv*Ch1ImROfscdEv929pvVn$rp;)1F4bQ z_?5y<;IYnDW_z16q&PYiAw?p)@oW>AP29?C6VsnUiX;wlsvzCsg+ZY3{6JIXHtN()NsoP1GCS30rl<{T%p>5~n|-bp_ehBbIz{u7xG=i2*m zwfUr%VmyX~Oz>6aYdp3fD2COqYyNuK{lM7Op>SRDe|%OrQxHYUVaAZ5H2lHq_)TEX zmIL^|GyA{&j?X9rK8D9IQ_}+{07XUiP0q+cEQ)f&?{J)oP^;+mid7Br_!oaAbqElD z;SpbWKu$x@)tQ|u7jf=a!|?|!p5>B{dQ7W}ljJQP90K>H7DB~-FWQE85?q&lYT<9; zAS#5xCh%nTKcNWyeNpglU9G8)qn9^BUgY^gFtoL{D*4ICn1@7l%uDD{we#?~tbbQI zRsvmdEZizhP2bnhCU!U4gFPoaOcf(bb((qh1aslW1r-xhbcou5RwCS{|0^X4r_phz z=?1U5*cfq-J@@JRWAN0Sz?Gm)ppwuWDtREEe!Ozpd15YunRDJf_sjLFQkBjxqDp+D zmy=rE5Xc9T=8v^a**Yy0Wi5N`(b?+>SGw~L zb(TA!>l#JA$OfOj3JbE=Gv`Y-5@RZDlrqIXVsz(QrCCL#P46PLz|!?)pETq-#l3EZSvowfF)dyW{q`K=a_U~N+1 z`1^fdPN^%~4_`A8Qb{eZh+|#dy{7~54U4aURYU8?(X>N{%GOTg=@oHSh*8msS3`^+ zsTIjxsUjtk@*GFIf?i2tCQisKjPK;VmYebdfHo#>)PxcoV<`S*6A-{Uco&0Le^|Mr z2YdqV^;PQ4e@VB>c1@$|VT$!vBQGS%r5TH+!#h5`rJBlwYZ>lm4nb6yFhNjpwT15W zxh1&0`4mXBC>>aQ`SSdmGJ&+}G;|ThEop%3)2NhxccobAXq_h%XHfBr$j1zYL8kb; z^sYYNf>JQL$h1JuG4`u_S;*x}HTbUT2yyw^Ak4|_iNgEUSu_&!S*Ft$O=(Ie1{8)> z^!;lGMvpt4yg+iJT1fF-AJRK!#eY?CT=r0(&$knCQKn-xTbt;&!;Kv}(WL;EV2$G47{^3R8G4-E>dQdsY1)@341Kpfcr%n~jE% z)F$wMH-r3th~Um%a;rfXi=y|&-EDF_gn8NJm}&j`!>+1f?2Z>64L4p08cPivULAUs zpD8^zw(sPbkmpyEJm7u*X@!M;(Fz3b-aP~A`~oNw$!oq5Tj@ZQV!hkCIdfz}apO^g z8mQ2eQx)8di(C_pz*?$i6@J9bSSeY1=Bk?u_>e63I-iTsKX~cw!~+E=P2v)P^FjzR zdywTE3>63iDxvm7(<4jjPm|oTt7&5&okuz4Vp#Ee(n-A|fcer!Xpu4ImNzI}_Ac_52dOzgku2+(`+ zF0_*+Y=|g=kXe%fs=X+fa(`w}a|ubLMAp&zKHO^)fnCG4jDs$lFCKFU!cAr-5UYl>ORUlsZME^g0-0GUe|_q!g67i^Is z*8zkTcz)dj%fH3`YI6~E-URGvsnx`1?$NCz_t?vPPYNFEn>INt_Pw>-pV<6Z^g`nk z{YD$W--VYjnVZAjAcyHCX7kq5(pylhIg%WZoi?+j`$Z>4Wm2_IzDDnDNs?2RJ5(2O z>y4=1`1bIFCEUzmJ444Xqz31uk!8+`a*G$wVy)PHTUIrZ+AX8^9oUs zuM}?i5D3LD;^;+G_hxbesyrtW%SClr=vHgb)^U<;;lGhI;a5cx_p$F9SH9d&S7P+1 z`d+8)gI%PGGK7^T(}`rs`D~$dB9gz}_zC97$_~BOFvUK7-Qikfw5qg=%G8_J+r%9; zx?=i!X)iys0Z@D$Pp)=v#@}`ks6a&)Q;^b_US~QgdKcX=8gc7;%n$2DJx|wE+nqe> zbsc3~ZtHdWl|2kb1?&?Ff%8MfG#uEd7NxK)HIZXTYPX)y7Cg7b22;*VeNZlO&mNY@ z&Oh99zC2Ol^hAPiNLP*ctJ5q?%}b+o#4whPj32#8C=rJH6fQ!O;4YeDJ)Y-W7) zAl(+(=jkl%A~~0>l+O54QDfQ=^D3bWQaF3^Q7L zf>t8hD5KZ(gW*n>v+|WK-AZOJ=d`P}Z^94f-m?rA{1h(sa$rgh2*I_WK3Y@LBR7Fd z8wwT)o@+CClQ|L6yJ@!FN%)oc%=nEXi=H25ZK8%e{Xa%DAwG~tm57~a^+~j(ikh9(p*gC1sH?}!y@+QldCH-HdKI-1j&4Gh zDa0}q=t}0HsRMy1P7JPSM7_=$%NkM=j+FpS-fs&6F)5}*#-+c&l#MvlkPA2I=pztoI-o02WZjN74ehgv^)o7rz?YanwTdjXtkXI6TBEXMsZP)}i8vZXdGkhqbJ?&rfL4D|_x1 z_U^{>y%-)^yC>oqA|q37hCBZx|9p7rJ!^%uxHC2!Wnq1UO3~Z6OG>?bUO3?jr{hQt zOcLe$Dt*U2oCp5c8=g0^HMhgQxhBVQSA<=>q`=0(8h&yhY$u4M`YrO%hFfzGScd{I z-mC`1P3_t-lXb1!?6sGnWas`<%ddMCN6uk(O8f~bMZ|^i8L!)5O*b<2QS>Q?jViSO zy<&S~-FN+ShWvd|4=y+EZ|PAqO zZ`$7Y#6|plqr=8FZNf7N~^;11@7G43x<6dN5Pu-rZ9eFLNCKtsGBa*}Vue--%eJ00snkCv}zI(&z z3a4nMAALjysH4sqz1B4;t`^xow9yx(50A&4?eINysZ9;y-nI9Rst%fiCUhMa5d9d_ ztVR2flz0C8)YI=Cm-=Og&e<6YJ`m5gD!(au|J=TioW0jDxrk%Xq&yPUh`QOClUW)~ zVQTcx@o3Ykp}-w_L=n5?H>njhr!%M)Gf`rzWfo-y>!aS=PqKT!QT7`p4Wc6$RMUj| zpk|Knd@?xY@R~N0S@>n`+&b3r#r~dN_p7YEk|t^oF4MBclv0)L^xpyoe$Axu-dKIq z#g|TCux;O^`WW*y->0Q2?wLaMKvSSo^c%nnGLwJ6_5GtX4@W%n|3Bf|Dm|xX zE{$@mgbn_vsW(8ySGvE6wtqvTozYh*vXUYd=i{YT_`7zLCWYSVuiBZIVJ7LY{np-{ zqG~9woSQ>o+xzrg^-nyT@NC$Px5>0}LJ_ZLEsiCR5Yn$Nd9&4x9eQObcqB!OvxU8B zrwzw0#9{}{3nhZVTYY$0vWV5ln`KIV*fJN!1ztLKYxjqaMR4|CsN8k@@UE~+s?Ihn znIRG30lK(FG_7PE-6A3_KfG9RCLs7u+ptK+y;I63JonGtEglONxe|}QUFn|wmMnWO z&V-ns+yP@Z#o({lT>xz)Rl_njU5%t$*O7|VYn-8iI>~JrvC*2r0vOCMZEA~Owp{R2fJGWK9mYEN{*NqrT8wp{%!idmb zDobzi@o&{mA-B4he37tt-B)tiElZWJ(+U1S9+v64zo{x0@Cy=AZ|bjhTi=qxRXM9t@nF0Hb1tgy=<3mD%%swL=i00u1wDk|P;_Iq z82xiNq+gLFC>~u}_)J|A^TdgzNM?!5SlU(778y1lojX*DhYBOo+WaawZ6dG99q`Gy z9)18BE@9n9hGWq*g(#f6S|qN*K3sEeV3nZ`t-1wBH{==k{^X%Wph#+-JTsP>y{!=F!NrpAi)L+1uJZbdnpn3eDwn?X6S-vb z1Ws4C3kBvoBaCx-P_zz643;(|AtC@n{xS~yutjYCbts*v#*kP3x1rA)l6WGLA-90~ z^(t~1G!jijFyvnRWrVyTiG#A%47tmH85-V@^lM&X$U!a@Q|nifGz9q{`ADmVe5?)B zRypgI8YI(><8N;Qk}nxL6f!Sp-Gt(+S1CMez^n|WZOS@9-*^&ndqRbdzg@5O3={1%@I5oOqFC*CS;|-H*VXg< zM@o;pSm2G+;ol~DwT%r3yXb+U9bLjV?A^$E@}Uw((rdXM^oXh_u8$6(Eq}IrEi&k> zhPa;a9CYZ9n&Gey&xrORq6y`5KJpR)IFWEt)OdD;jfOi6Dh`^Ho3;f4crr-_2x zCfaAmq~2!T4~dLXvy~T1tfix!qB15juVAP5on|=#teG^SSn3@ntw#$diS3gF0SihF z8v{{I`{pgAoIaEmTxuDV;}-fOYEQPU8GubrvpXqZ%o46y>7>&}=0!~w+9QR0!-O#N z&a@JIy?`4f^oIALe$sN6bzNxJIoJAo{8hBaqZ$V38h75 z)oJRLXsEkNulZp+fQWwCJ(kA)QFKuTp%xN-Q2LVgN;hyLn*N!xn!vDtc&jWR8#Pg* zne>Ud1InON(N@;z%2#QY1_m>OUsLvL&NZAp_HIsCgT(>}%thRDUY|lX;zlk~a~2Y( zAiho;lH>@yce~q4Cq2ga-q5y~x;Gv_EZd#=x!FXbj$bsW`Q=mCn#TH`y!Le!#*U=J ziu02Qgpv|3|B)LZ~^7awD4H{uAd;jwTUWQ6C3QrPY0Q@6~c04oF9r zRD8W0OG(cQ=qzJGakuj>T3R7u@hYwN%m7v+5E)&8gMsnZ-bLQKMeME_^kjZ&bh3B7 z+#$JeEaH$hJ1lpbWFu4tWW6v*#l=gD42SVEjOZ40mqaZ!ag3i0?Q8t*N8V4{vB?77 z%JgX;xwyj~9k2IfvhYK_41|iXGGHA|XyrV3a0;sR0cCuGp;U-nZQ49U<{-^j6l!v)??4!*HFFFZv}s5^veIUgGIK(;p1 z{Jq57*I=VV%IW^q@7g8vPo$W?gQJzmcDj-xgya|uBG{1qAY6&OPcC&Bg39w-rdh{Q_;=VQ3P>< zM^03OC|C%E4=p6yk_d(ij<0|bI%OHzKwDD|cf~fj-MJyw$A$;b^^VLl6F)a2K~5a_ z90D_Mp)QuJ>Ev+YCU-#<j!qJ_C`h_Q65H*Y$O-Sn7+eaTE6@otxVsLJhQo?>stH zlZ${dol(a((onOcFsS}lvJgklfF9s);NWfbAR$a~m?MwNb zp;}%2;G+Kd7ajunr0nOzcA|Lg9KqhuPbSKpE57t2&L*+AxbTCE94mXl-aHQ`lD0nA zz@0WRXbA@LZM=Q|mDui2d2`6q?M&Xfs$7zXg7V95aR>fghYGua9qxF7Yfh}Q4&AIp zU8|Q1?O~`&_aP)K6lCwNtBG16T2|dUo@6c`-a37wOjfROzY6b#{mB{&z*>pL4Ld8v zPOG4a0X~c3DV=1;V*^c6>t^>$>l8I|FNe3DPL4PokjoSBD>DCop2Pr~k-#tfob0w< zi5-9ngfkPZ9bJSyr6v4$j6LoiR+i|0l6G<-N@LgEfF5-@o^V6w{P62Her*!-r`Pk_ zk;Gr#oiN0qsHkS#EtX|=a@<7yP#l>3Uwc0M5iIprJ;r{r%rm(nX{r{LK5ABAJl)%z zX7{E@wd5HnbewK~moi_Y)AlX}{nPX#x+6i*b%N#Puv1Y5eBMdPaWD}Tz{ff( z(Joa|Hc%%9C^da~gRb8e{ah#lNzp0_!r^yHwm^p&Vu0n z8@U)}FI6WtY#(O0Eh8-}kL;1Q-}}lJ{w5!`PeXT)cUFmLzw2>q8)I2$1V*H0!VOga zGfMs6#aZ}I0g~ks5}Ii-lnj-qz#DVcQauCySIW^J$kTtM4gGx=TgSeQU&#agm7~2Z zCSAnP&kR24Fp*aXPNg+#dvg(XzZksVKyvg8TUagDPh+@jzG_3BCBxCJofeXTk6v+~ zpSHlSS^?K9e^nZPlwA5(r;GoFP8|Q8oh~@KUCM{s$ULCrq$tsv4O7@e*nYXP1OcLs z4E&Jv18u_;?!K*!=-e}nzkWttd-#a>$ZI|3lzA+d-Tc`SzW;sx{srq+s6r zC6$@F>N3B`1_hyRRx61vnIi!k?_vfIiHA2o;Ft0h2WtK=>XxSx_AcXiQEpf_fm>50 z_*mUwrHWqT+V+C!tT7edmBIG&+#SNnuXvMQaHsC%H)j9$iVM1)8r9@i^(WL5x1qC!I+2PZaua&roEbp)C!Hy+6hP}h+yxJaY`<6u$C zQ7iTwih)^+$cx70TFGK{uH>l$o#_G%oMSQ9wm9|wtQ{kNOXaXA;Ylds@VQ&~Lnd`B9qbv0YuM+}rw&I~W6GC`7vtdT#q9_vdWl|P+;YQKdruq`>VAi9vFW3VT9J&BTtqg`J7=`(42bZi_{Aa-Pzs6Kb0r)b*n3w3`z0?V`~R(MBW*--N1kX(I#RXw!9$ znD6&0Xvl<}b+_yWv*iS{%!s)$zQDdQz_z?Je$VKeQ`aJ3KR*1@>#*BJkl>Ay#7MSG z7?k%jc^thnIEZBaS;HOg1L4$U@4&0t#e8>-`p}YgCq>Y+Q#ZctW*<8F(angu# zRc}L|kL3-Q?xyC95{O|?l{qQ`YxM*?GO&ZgcAy?bJ%sfXoVzop6DyGXeM8+`By?RV z=ykY3)3Q_a!;>P*_xZWLmyo_uMd~OUk}d-coU>oE_kxjuv~}Y+KQUeLC)E<^nZegg zW0z(43ZHT}cqc1IiznLI@JMrRlwfU(G10^!=Yychc}k+B1KqH0vM%e0n(&QUO9Pjq zadS`t@tTT_LHUg&TZ`(VuSm_KuP*^n#Fg&hsCYMOV(?_-MnfufiDRxogQn=Y_Nsv} z+DfSOYefoTbdG&siV~&{ZG?4bfG%h_f5lEOM_nN}q1_01?!V5O|0@Poz3H)0e`}Yq1uHy}xAqW%Pb0W!-`o@$+Z|cja3AqYd zO`4OQS^d2%&9mnA0pE=FRr6IIx~clf2F;Tu7k+7O$nLt|q7H4;da!s`Wn~o@JjMAa zLaf<5e!yS}rp<1gKHIhae`o`-@7}OcM)#s!Ni1JZlM3;wJ1XI**oq822 z?%YZHV);bZbo+-FefKlQj~q^}-*_0tQJNCWd0bk*4F170HK zRw16nJ?T0;%4O*Cw75CV?7c8V{RO);b(`>@TPs&f<6CgFJ0lDSFuG9wM|V^~$%rQ@ zvN*LmMoaQcidxmUn99JJ{0B0Tmn3kk z-k7QrNuvrbyNutcewTDo253fA>?S?m9yPN3nd1t40e2|}cE;IbHTIeJwJg!LwO;)B zB!@??DVxA4bmi+f<7t0DbYS5`l;kP@s%?|JeDD>0nnHAV)Mz`I-?eJsyCoR`H43YZ{o$Hj16tRp|xMRk>>q6Zv!1@`pQ(BtN>Ik7hu}lRF*`9vnS;!;j zc4}-}s`R4^B!f|>k^KEc*R)7GPkCjz>N~IRL@9V(2394uxJSprvL7?N79}pUvyuP zE7ETUjs$d74_=0pO_ybSYh(g{uNE`B|M2ofueujP>dy|I7ryQ8*~n4w3;pspB_zat z5h54M%|oR-Wj80SH;z`+{(gDL?`ZkI$`bxYa^m+|4E#YSSJt;Z8c?$Uj#dtO1A(5` zgxrSJf|9$^JcEHq9`G)@A^mX0;O^|1P`x+OV%syA1JkxF546ghFsL#x> zux@5y*EifSmf%k=)P&{Gc~~qO6B2R)|Z_N}(FA4=hp$=*HNY4*v?#+ZZEhW)Rbv2yI6 zREuel0FxndCFX)4V#s<#Xx0|Yxz(> zm*qO<>&`m!m**W$xJR7M5I^*4r~b8eshDxw{xj#oJy^#77J2ZvFL^!?Agi5s?d@2)tom7QyEi$F$TS^_r$`70i-`&W0NiV8T3@X(Y#F65e zG1U+y=AbB6%2sZCcjJLp#vy`}k7jTwzS z9K~Th*-9Hv7*6zP%23|AHN&v4Ugaeeju7a)xQ%#INzkEO z3T<=_@#@bSoh7aEs@4XZ`6!Zuz^iRydv{LKhr&@>h6)UGaGZ+#7_tkzNsm88 zmqQ9QuzO{z)kjv`Q18F&=TkU%UzuIf?V{A3aSP#_T~aCZ3NsIzZFgOU>qFO33}3K= zE@@3KCda<)so}Ua@y)JZ^DfR{njER}GN5=^WaxystZ2Wn@2-=(-o2espRo{we@7m! zc(1u3(M+`ekaN1BsxnTp=yT*Q?`O6^NP=hNp?5q!&g@qXp$0H_A&tq_wme@u99gL+$(dr6s62qKeQ~}{q^Ey$=F`n?0DE)tS;IDl z(JRno@|htDdxdTt6R>#ka9w<(-Ae7Dv!K^szazxqQ8` z6GB6~HXdnNf7Bhct~C?_`>EYZRF#(J`AyO}mrGP@e9<6wG?PalDSF*_ZCMN++K!Xb z;zLd?wfJ!nTKssi(%z!4(3nZu)%Og`DVOh>=L3D;9U5chSfIBhAMd=d^Ul5sSPQeW z8{$s-jgWT75S97CWv*Ps=Y2;bY%vc`123TbC2%fc-~KpYy55)$N!E^yn`#X#gW5}V(>B)LTn#7168Mx0+gA@bFfLgf-dNCZLmM@ocvEq z-tEAe&Wg!!y(!ftTw3x&y){KegE*{ifqm8V#8ah!G>%poYnFVwFv4Cf%2nl)*yF0g zI1O94_m}XM)xU}FojUsLkup=odjaR~bROYEMFL?y$lGK@J!)MKYm9FW6DyTTV6tk!k-1`M48Cj)Jut`5flu&DQNvM^=vFeMS&u@sO;oC`7 zsRdtXtVx#BH*^m-e;->`{QN!x_(qVx3WH_^%y(oqL-jAd%iv&(v^-6_;29=nPEd;s zRogE4xV}bLD~I*LHQqk`xcttmJ;m-dZ{ivVD#X z(;skgm{Chgd25NiAdnB;PqxTPrb{huvx+(fG3+SIZn_8}`T%J!c|Gw{Qmdx_2$U@=dX+#5&EB8GG!gis;JM%LF9 zBe<5Dsu$0ih*nKQpKBF3PJ9IJB<=m6H{yIz9%P;rn%DBHqL`3!auQi3p0pz^a(TnGK& z=-VeFEy|^Su14dZoAzGsJLu4%+SjTOz>>*ed9zE46seB;Uoawi1qV6kW5y#x-~<9v zxYW)LFehaQh~=lPtQkQr`%_@l3*p++rkH4bB|lb${oyM~GQRkeOqaB_czWyM+&CYlJ$_&yPDnX37HG)`D_+ifNlz+h^va*2U5RJBq^f(tE0Kt}^`p2G0BEWAm= z#w_Oa)L2ha_`ObrfQ+~E-cA=9ujA{UrN_M8tx=fL^5p3mb}ZhoJ*%ID;|1bCe%h`a zk_m-<4SVms>;5s_6ZqgesHu!2Ud3HiFT?ZI$4o-C!C+?}QMdktBttxeTE#{m0E5%% z`G&5v7!21&?Nn8S*!#(PjVrAcLp>=Itpf(>Lkbl`&3(EoZ?4Ei*?XTSMQXEaQjx%c z0^krDK89xBDI`Okz{M9OyUYl#1s#?#TWHFg?|)}0o+;b!Big{k^2$|q$9gyRDx5V! z!B;4tW&y)7tWj3bC#9jChkfX>m^EUwU$eJC<5G`eZ~H(1K3tYDq&)&cMs~s2oB+*s zR~V?D3R79-trgS?IXf&_!#%2_EdRW_hTBYPgxT=NzOf%}B0nG2{M%N*aQ-(U199akUmr|Stko^Gbra;F+l4a}%`Y@Iyt2+d5(fb7H0gCz6 z(+M6sN4n(Oyk5~B*PF-N`<&^EFc9a4{0?5>wyN`o%F8!u+miyU`uy~9Itxh5@*3*2_Kc zZRI-L0En(c>674~aNV=*#p;S@_A!3C*sgsI^&fjVGuccHUKw>UG;*?kg8()JZruCj zw*4SYu?!sSDBY$hf?MA?ZIZ6w(h$QOGmdFy6` zHtoV_izvCcLtmegmAWo9POxnrZ?Qn$d*$OK-DLbCAarE|knxf7L8WTkc$1K19AOL* zySk|j&h^BRp$pKis@*grfLKX0ZbgEUZ|`XPyV)QgZQAXN^AY z_$y|b8BBr5Rs#M3a4-9Y)-hxdYbe^07?}OM`_Yp|!*cwtRE0*Qa_rIhcT#5~b>*DN zdNUXNE&%THr>F$xpyBw;hASHB6=?Xx`bup~oRQl_^qgSbhk(~nem%;C)(4MPAIdFw z-j~l2)*i95mHh51npb9(fsE+ES4GlvycU+j{8IsNb#%ITiEReB2<_9>$0?nqubClRSXxs|CH@zvHT#(w^3N5ep)5cc#fzE& z+?t27qm4(}_Rv63F~u&;qvBOh2xZ@vS1RjmYwt-T7xtTn?IDrRRPXYD(5N2)T>W~) z)2~Hl{m2y=Z;scd;jvagF>yTsOD?1HnruZAx4e#S^3&ukj(s5@pNx5aL=UgPv%IJQ zv&|>gb+lY=pW&$b6Ny$o`gXOeuF*jaeat;_O$Uw#UJYi4aH0a4o?E$QzvV^N*3~e_;f`0z!ZiBEYc&@EEb|gz@MLzSrM7`3>?|7hdIU zU+=VryIh3n`}90Z?NX1FhP^WtL<(zHS66-Q5x>jz`<%b5COx+9x0rZmib(*BWaO#3 zM3QiA>}L}12Bn<*wAx74e#Dd`<6u*Rt;f#os_6DG-4A@mn57r6+t@U$^j&ZV-^Ql( zRQn7pd*a_E2Ph3SqLEy8T6Cuw{ZB6l;#Ef@wHrTy<29)QlL8T}G+EOZM;}&oR~%zv z%xBzWzG}b$*2jM>D^B$jb?d&aeSr%Ao;Ee0=U#=o87{di#H|Egvd+`vj30)K?s3t9g?Ah-W(uxtOV9b@~nwnKm< zv9hf2y{ojoIM#Y{b!~0s{Dvjl(v|TKN6wGPf2a-S`>5uf5)@E$SN7RMaE2ZW#$o>@ zHpV|DO4|+-|BRP6K@RD&jH7E&tWk2Nmb|b0Y8wuqw7-mhwGwDC9_TQzE1AF z;Xdme%VG{yKcNgNd4DnK`sGRAf6M}Z|1`<(&$fQ_;Qr|pOcz0o*(zy~*i?2!`Pl$P z_m{2%I^7}MKImv&C;N=Gi`KCmX0StkwRlk?_tHzLnp`i&mak zQ-cfmUAf86$EWOjCF#|BZBQR}$4k!i@o<=4Ew_=Euz+XR^QX!@cZp^Yk!OfHrRYEc zH}LTYB{Mg)$b<;xbwr)$Dfh3e^Q-V&TR4&W$xhhZLCl{hWSPDbD=+{0>UD@(0M57S zs>a;zQGM|=0hd`Hq2;-vL`QF@l!=d9@r^BBk7IW=C5X!&tE3$4JBG! zuq3o&C1AaHzdm!Sr{aX6$eWo7d*xaZ1+19s{#MQW zxtW-MTq^&(@_u~bz;pn9YWo=4h*Xv4%11$f{49|h^EPG|gEXmFyuJ0L6CQfYNXhxIhxl9o8!Qv;K^q-KD&LL}RM3XbV zlTe9@)sPa0G>Uu@7X%xRh}B!j)%VJ$igKZA&bAHH z{gN@ag8PL%_6P*&^*}<``>OD%!}ARZ5ghNcLt^ex^oo0i_d211SpoHvY{3_2%vdT$ zNe7rZNoP6?#l^$dE*A|FX-AGoTT0t*ekzhEnx;{C3m;1gU>!YJb z>lu14fQ6%8P=^eg_9LdX6JjHzDGk@uk*qYs$(93QNRLwBvIRx2$;GKQYsapSEp>N@ zvah3r;4K%(FEAL&Nd`lTT%E>zrp244cAi?0B^~>ATpD0uTHN-SM zGJkg!=)=?&L*ePT0!!HVOUb4}Jw;#csr9yLMZ%745ANp3m(K;kiVaBi_!;`a4_F8P z3^ou`+BC#;XoKbo0nKDWoUSqvzLTawuFt*j`RfEBwnfzkS-xcLe$MX2cj+Zj(st>$ z8*U3GqOsy?46Wn_>c#Cz&xpTHdNMn|r~Gx&GxJwWdM;8H=Hq%WqNrgj3bZSoxFz;s z)1-c^yyc`HY1Zq=gxVAL3_HSQKhJ7sKV{?5J^NzkyB~h{^ink+M1+tC_e@K1@1-kK z5HaYJ#pcBTws#T_hQ7>+bx@9(C8zUOq=F;9!{172wmjdtCi~z3OSNeUqsj(E5cCZ9 zgs0({6l!6-DC_n1?9WLXW#H&2&$KUovPksWWOjWqUjfq;-f7R8lXub8lcXD=>8G)} zhi2M^VNjgMZ~_6Uueyv7Ij;`J;TBcPbeUw|1jf_Tjs#SIv+&`7`+O_aTX}DF%8VK# znXmgJ4!rG79$@%n^f#fvZ!6IJ?Iie7J~hJ^IdK2t0ISmM7+>68lvg|F;T-A>vfVA> zv$DNupo#>qL0xfXygNPp#YB+wK7$?NPM_9ioP>K3l7z7B$XE>pv6M{%z_7DTxX#-o z^ResVR510SM(VwLF@_BNno?h1Gn!Cu-XNBPkc&12Gziu>g@r26g#nn^=sgNFdE?#W zr=gSn8^9c^%f5j%8!zOO&uTImkzN*!W3R^-S*sSpvyq2t{wk1fZ%Ay^FHm342-7iD9YGH3MuA*s30P zVZ|tIVpb#0;ruiYZ-EyXK+3(1Sl+Um3$|h&&srtB;O6hSYQgGF$vCPCAZG$DC89bf zyYNcHcb@MCH!at`!+NVOgy^)aww5ry6Fue2OjV%heiim3kcUUT^Bif9eTM5`RXEWz7SqBvUMB8ZgXr3EoPTY;E3E@tYEq9Ow*yY5AeVM zjZARVKtcJv~8jN`nZG1@6{tL7ADv$`g-af~h>Uz16ktFS4) zllxKc=wh1Wz8!n{gU>={o?S9!E(C=fSpb+unS@fn#kVsL zWVp_Mwx#jc=;dc_b!IHh5HM3pMcd{BQ>MTz5^k3tNaFq^stD5k^)xK?C#mLU`%_P~ z)jqMrxdogmu@i9106kyj{-3SMc5(M#ym#tA{W?FMIcfQeq1$ zan;M7ywMf`@-KwW@$+j3=Gf-`bT+iH2GWE$r z;1Z_J`!hZ>i)pAgN8>d{WzKAGty0tB=}VlK60ck};9SPAMhE~|J$7O`6j&FQb*W8y zw7}uKdd}>=xv*u2i95IU<&Hc}6fzN)gRnkc^WJe6%cRdfG~d`JJVegwNF&P1XexUv zeRn5aV5K7nHCN3B_=N{Z+d_&$D zU0gd;{Pf(*obSR+@r@vO$iZ?aN)gTv@7_(5ykC_lRYQLse%=REFV@ZoKh+dQcZtn& z3{77GJ%BE2;%bK5MUTI8XdRE(MT0jrQuaOwKkL(?dTzbssmI5p8Raxp<(qrPnl5=b@%)NNzaO%~hwt+X=O@DE#x;^V4=;#w90rL=peE6A zfgps2f+JR-#a){i+4H`F_4DVfTP%~G&9eL5m2>s|eC}ncHJp=(aLDnt+L3-sT#TP= zPCWt)`B8>DaQqSKhUCj#PF`IW+Fz%;Ttxu4qSH>V%r9TbknZW}d>+qL-)7uyr~&P- zU59MygZ=YgLgD`xj;ny7aW)8LNn2QD?4t_+m&xzi+hCMkMI?5Z{p6c0XNm~^JH8PI zi{j3K3iexLrfc{_PL`4#8n0ioK*~ULI&2lxZ5=~`US1-tCZOl%_SSB)X#<`L!rNY6 zaY~>m=o?<}s7R-egR^YLbdX9iIF9-9ADD&ym+B*U^88t^PmCYPqFB=T^0LUopd$vB z#Tz>v0h>^tmYZem$`xVekZU193lHLz~8@P>GrhlFV0TZK9!iN*JX`8ZQJHJ83%gle4===wkUvzCq zRS629s%CRrN2FfC(>>*+<_e|QWhp#QBDNYjd&LXzafTOYhOH7B&!#QSpA1cD;m=2i$}!BL~LO+Ph=t8QI&J zoOJ}MQ<^6038tm3Tca)E(XYXTIwNy!OTLH$!_1FkmAcrKNzFS|GcBqydanE-|ukq1T zmVHfAtV5S*P3R%*LE$(v&YoH~K`{e@EVKXrgh)RV$NY428tkC_k(N4tUQ+(o5P@+h z;y}bOoV85@72SoO>TzLjG4ml_mTwA93q7Fi-FUrn@ol7AfUs}O4wt9-_5AW?kfDk4 z@(ltWNvy{fI~n@<`IFasxeRwIUrUv8G3;`)J#`_UA(9zXvVPxT-2Oj@2?+=`wWrLVhch^t3}^!Qy~%<;C3Rcff6aR0cF$?qtU;gF~09_VqM)*MwGdBG`e<;J{4 zpaZ7UegP%~q!f_p4a5|ePbtCl@kMYcBU^Ir`laO(UNA)2go;ywtE_cAQVU z*3=L!ow4+8!Ll39Jv(z+DofQbE7(Tg58H@8p@41JC7xhEbTXF6ufa5NGE zVfg0}+CQz`^LKF<^MQq`@0BsK;c;|6uWfJ7hE3I>O@8WHia&;mP(|K(6Z&*oGt{R! zK=tuu&}s|JJK1tK_acHBWqlEU5kapB)$*{D0tbfnqFx1Q`J~$P}B&?}*T- z7P(T(fXR$G?`)=`c(evVFXZ>u;@I=oYgB6Eqr1sX(y(2!`!RK&w_%QAp_2p<|xTG>x2GX2aXnGbD6nfm@#+>T=1FD8nxdtJoI_tX+&Lodb^fvGo%hEn>Cpzldl{~R zaVjAOP6VxIqiB5no^gaK8Ca5B_=rwPqn?um<*$~1{QJVg}_xGdi zu>brop~IGJc=X8;%-I|)z7FRNgUz7`UHzC!C!K@U^+>H3WEgH2}%vgIL&8%+X^x(u7y|ePLWS=-VRD7mPqlssD3R(WjN5Y zxgncmJr?<}BcGL%$vhyRwQ=hn&9MY@m1J*I1F$`=Y?DF#y0(6#l8ta*9p_f-(NQkx zoPPCdv4`ITnmXe54`khxu$Y0I1jdTcb|5rgi3gYL34DY{`3Ek`>I#yocKFpG6yrqf z;%h7)zLXzSUx}JGlh63BM(!Tto31mARX6(J+~~^`>GC!%+6keaxhG`EvY1$FR(RW; zkaF{oYIdWFJqLIm%iS%N*r(IyrR#b+BTZ&t;O<}g32Bo;~tKBV9 zuIs?S;JijzxwEq~!Cb_OP&AX6Wt!M7KL7#wsSwDn$iqYM6BN)YVV#BkoWMV|V3Jx_ zf$0+h6O-CuuoU0a-5}($RbS+}dQduD|2uZ!^UD1~abF@Xz0S;s*8PF0$DekmfU}k7 zwypl zA^jB0N*!^X8&%dlcVL7rgEk~zq~xd3xpkmW-$y+@rPW8pKMHzA*+m(0KUAHr{qAVg z2?I6Asyj6R5H301Ac{xC zS?7mAvT2(D#UxJGrix|;E{c>Qvae~Ia4ki#cqjF%jqZNFTYLihe2P9KiDv}giu4qZ zGRtS{-b&SF+7&q`@`yr6ldm}6Vy=`()28AJl?RDsU(V&3wRMu%ZWsEMHuPP5cYr%JiGMQ1}`%9Go=b-H1=A3%4a+U z@DmlBI}y+p;majU%O2$ElcPa;2ajb-$d4{k1c*F01lh`=&p9Pz?z!G%c>jl;!GFw2 z{(G+SrzdFtnM40y&%a;hHvTQ^`7J@z@A>HeBmeJDQ$YUx6W1Lh!MiaqZ!tKzuL~Qe z&jgBQw#}2hzqU2VHxc6Bj-IKH>h#y=v-Lh-nQan0ZWU>3z||llZpDQ7JzBjV6_oy2 zid?XC^{b_hzw$MbaoxB?47qj#7mMN17XfUJ(WlAu(#lt^?_K4eOkfsz-*yV_%hak} zcFDojp1gG{XUloVt|NbBHSm|yPLcm7_}gN|@?ywi0GI~c8VB=Rb2O-EzIT~aMHj;G zVm>r&W8si-hz&(FSYGe41b=V8-C|`|ATQHnK%^7kLj(UXumt&|1%J$0F7#2t?z62= z9*x#nV7k-1RHtXmiI_c0_zkY($+inVwVOT#Y~Xyi!gXT~b_iI5{nr=uCrw5E+RyBF zy*v#|d^C|g`wRZla8iqocT4N%_!joc_tcY6M@6JODF(9U*bZ7a*iC7C&ZM|H&Y0-( z-0$D}xba-Nz&Vxlj-8yK!GpqI1S5aB4}^&iN{hVnWO7d5I5|1r)bzM!R9B~D>0>`r z&C!~t)&qMSxrE-%@Lkw-ZrhRdGfl0p3-NUa%>rB(&LBq6eq?)HD3Pb>kr*COE;NY( zIfAz~w$~I5=rI%M6xY?&mPT+kLc|>f-)1BD;rpyJXf(62= zL{KU1LQcE3X4gwKWOPXR$xmTn{a%w27K*G>bvq8EpSg*|5OSA}rB~h5P<^y3Ef%CR zATiuD@kyEwk-+SX(piwZEgv||bqQaeyk0yQ9xyhe`bAT?;9A*3Be7n$*b6EKD)+Vu z;NFP6H2Eov4+a7_w>Q8!0<7<~q~MX5wEpd~DKy#idfZT@;CZKnb^pRBpEeo;Kqz-6R+MyJqp;5A7lRq))L z2E;4-9j3i!7~fXdq0&I00rB0o4Y(j?v9IgquV`ldy;1cG(abMb zn||A<&ToA5(>4vGpam=+N@w0_Zpg0?s8lde2ywV?j3PPR=Wdpn)!f&mo!=@FJ9qH8>UBsekZA0L}is&%@nm zW=-`^8se;CIWAZ{Hk_#|qY33M^@AU_hs&c`PSjd8(f0X;y}) z(dwbIoxFJNJx9*AN7+L@MsldOBUnMq==AcyY#vsd@XP{4tETx#oCa)Hev9;+oeB)9lmE<=JX{ z#}zRlA7C1M?lqXFN0x6vn5NpR_$?pMhl71e78&R~v(uIpJZg=N4jVozx1A%_-|3$p z=!zJ=&NIbgaM%>?1;1RO+Q0a0t0t}mfKB^e@9hTm4=0L$m6yx^ zbys7|C);;G!5ff0$T5fz2j7k6CAu6B3kIzbkBunEdu8nM@JuL-RX-b(E>L=S;J}J3 zW8J@OoBwWA@0Ze&9p}6RUkB$y#2M zJf*RfPe`HJtt5l$xpP>n>k^mTkL~>8+c+!1u(x_gds7s{Z~~lGE8-j zbxF6ybarlvU#m;!K9Fq}%D^Q7`J@z&5rBIO76SzfphdOa6Y>x)y2W-rMe+oB<%(-o ziDS7_PyKbp48Q0-nc*3}+}gUA@}KHftL%i_I1La$o6PLYGV+z-9(`$w^b*=0vB!wy zrz6Ut;1f7@&+wsii&^9RYR2m#N&t^fh=l8a8``C0kl0)iJleDIq$=jtL|ydhmq)IAY*$$Dif2yro*saH z`4o|?3#hs{M zwUeJ9kkjkRMgAN|lUP7GbsHvW!pl zW#K8GzzVw9Zs0PpDp6mY?3z;oAiA?>H=3j$+)?|i`fBz)xxIXfnsu;*@9X^a#KMdx zx@RL5z&?5okOa!Q-6{6qOhFqjmL|{g5h9o z!kLJm+k7wUMv|1aMALc@G(G@){!Or4@oTRqNEZh#Ef286_s}i9fvSs zaH@SQY#>TXNpjU?Z;jijyAZQJNH*kcZ5KyXsgsWetiG>j|C3g7cZBLSv-_w^(%Pzz zY?~Cn3fKxtzCJ4wbdU)W7$F3Nk#GX2%f~6mSloG7qUKlQ29I3@#j_?4P^aaoW5!a6 z1{{eTf`XtaT{Z-C#`;%jG5im9S?V8AXcI>Xg9foJyk#^m3L<#}!HI%*YZp%9S5wnq z=|LW01$}JkdMPP2TZ5aC9Z3fU?(R@E5S#iRMb4Y})ZoWnXo~KO9yya%gLfs)@N zCUO6Qn8bl>j{iYSeovjm{eU`oW1Bh&rkUSAtYG_E-;*eZ|A$1mUHiU4ma`5*fOR?xFNKGiOFju8Nojk$wc*#=Bxig-E`AkpWu=6#)rFTk-EJ7(*@Vt^ zX$d(gh4pf4#k!kydp+~Cf;w*HqU|?Npc)qVyK+sac|dqRfN%l-6Jrm-!EHR48XX)K zNw3wJIhrM`se9C#e>cNRv>zE86`_iD-b$_`#I`8`DPUWh1vO=iH5>I`sq0I9*F|=)3F$O{|JghDWZW z4f9Xu7)?spU3G6njh(kqT8}(9;O;Is@WBXj<=v{p{bd;ICu|(71{MwXhT+Y7uDpHN zl-HtG1zmdFPf~F560Yo-7~Ar_8Pbcs7ox zk?9&l;^RnZ$)NG+to^BQr{Qkxg=ZgUjCdd22veNQc-0`0x+ghmzvJ=8D(Vmvd+{pI z<_VfjEk(0;c-P>j6#}46ch^8UeDe6nn=Ytba|41_tip|wQbXd&PX!Y*`;#Ae4vsJ{ z*Lo$cXE0&x`Og=a#fOM zoBpG%!fhe`yDoj$l)B_RMnJy)>)aEKTRS>HHUl&uC{x({ffpO+PUT2L^4 z1@}Z;y^``~CHT?oX&2z_nvYj19;yEdL<+xtl!%<`-6H{eds@t!Vd{!z@9I(K#r1Cx z&$O!zD!y6uO}9P{EtEC*l{LEw)HmMDWqg+@!KUwA@2FdgH+~Vx0Vh^?h)1IwlCQJpniFx|3Z8=o zV?mf#jMR{}TJmkVeCHXUKGc2mh^%Ry9ht&;S;XseSOn=S3uLP0MVqEC9FM`nYE0Uv zt5(a(oL4QMQzoXj>fcTeZt9MC8EmbjhjOk2_gfy+EA$tDL_3giy-?1@?8&4m`XGj( zP7mJQz%{#UmzAf{lr4G!)qPeg;3Q4o&g&$bgky)~z<1#~DPbX1Gp+12>TK33S0}`cn{5GVTg(x^51S8|(SZ z`e1-$bY0ac4I27g$bJ3e?=F=tF+p5w$nSbBbUH)ycZ`9)ao-?U`Pu|qQynS;9ni?Y zOrF9P)2aYn_?t)PbK9%5K)=*MKisaigTAiJ6K$fX%ivz5H9y4KZEtq5(iuSX**IW) z^CgF37m#rrcDBNzS?6Q+uq;nT>hjZB_$g3o9=OB$lQ7^8t9gwsb^0$mEDI%^`okSI zYBrV{o+87ARlZ}O=SK~s*sp4m2+44cIv5^9q{a}#L+D^gk<|9IDV*rDc3(R#?$T$z zg-gzD8Y1%LW}eS3`ZGl8xBs<9_3j%a4F^@n@zWg1GRYC#XmfIGI>n~_Yeezo;`jRd zENel>7Z<`U_Q4iq?U`fuMy2_=q8M~ILf9ae#DvG|EV=0;T!1Py9?;F>0MbcHOo+uM zc(SfAsky#(eB*X;!dk^b;0Mo!Y%Qr&BhRDDjkkiH?`BSo7}ogu23)(Z!sZ)dTS7=% ztd>BXf9cYk__%-F9sH84+9q6eMMLH3v-8$==cJB2ib=I@Hs>)A#q?shP@m_~Fp&su zAcT~bSf_AM=^9A!HE0?CR9YDn&O(vN#ve7VG>*qj<+fJYHH3&W>5Dho0Ofd$h+&6$*yg_SP<1Ao!+I=`NH4y#hQ6`2Iz6W!=DN*KrWy{rzhYDdxGR23_do{rP zAGy+AbZ&646i1I&0`1qe8nCqQ2HqxqHj?yBsI&yr>G3|7V+-Kpj>nL8uJz&PM{jB~(5F&#(g67;b-7SOL91{qS1R z@Tp_(JFDp(C6L%&3+L`&Jfds4iU)|AGRUENRF9|RVarZC?VFW)d{yPkiRRM}ugPo4 z9_K!lZXgmDrw$HtgH}{4U1W3O67AR|Sj!UzI8imO4I?y1(aI|Ao??exYJd6l(sj;BZ#P_iy#TJyW1wro_2NC z>#6Hrmp?uz+&iXxLCRyMoedJs{FjjU=dz~X87lwF)0tEY7ESDgC}aL73hTi=m_Jtf z<29XzP&dl;<|n-uK>{+8>l>thj0S2rt3jFI)?wgMq@EoZ6eKQ9PJW&AlbiOgR9KVr zeh_<#BSVr+r8;l7`1K?6i4Xt-rcP1X5l`{h#Mczjx~bHS!2^oc3!5Bc4sk073k0vJ zUDsvilQG?gP&r3x#uRPNPo)n?e}f$N z$uPO)((BAte(6G2GQ@(iPBj3+DW7}6^D`Z@02M{a^@<7AXZ8*v$N{x#%j?zs&4+4Z zH9P4aESq?7sP^so)`1AAh(0(wojKwehL(0w*@j%>p;N5dyEe z30@ygp08czo<4am@rcGmsv2@5))T@3(Jutcc|2+J@C04wGCyln5(zZU!*cq`RW0i? zj?s>uAwMq&f8tn_IkJDHX5VAheGA^Kbp}=ppF~?*_^zAy`LSK-V%fcrs?Ji9e@2!u z5;D(VzztUEewwTNuZzmC(8t79XmNW{iEqlb9ICo2G21){O_>h4+;DhUjp1#ZdrtISV_3gbfAgRlBKl$_BCd&j}^^zRX6GPGv zbMF4S^aZry``0EmsW%zSiCHUeh-q=I3XMQaDKOZpuR}RsZ(u0YQrz5iQqT?@i=hNW5H6y#D^GeTZh==yQj1>J1$nBOzDP) z_R&3Yeou>JcmnJX_JHFCF0g7uWI)RSaHz1`^#CO#ULBd>WJ%rMO7geGH%MN*XoI7)2z*u$%4ooDkiirVUBn4#eM^te)U6F!_Khsd#Hq~MhD)QNQlD${X zdsKiFD{w1!`uxR8$At5F;Qa6Ro#*Xo&7Zhs!Is=l^MDSDN*>oPnL3f<8zk{hbnNNo z+$X)E`B2z@4IUe+2^5noqX|5pW*K`HgD9Jw;0hTy-BIiZ&a!%u90IsmBl7|kZ}?bu z$)9&mUd_!;qB>YT?a*depJpt)^{fC ztyCZ1xr@E5a!ehEtwQrTTnU{xh(9=d4cn#oc+(_jIIgBP4@waLs{&FJwX)x_GQT== zOD!m6{=;>NRPWbp5JEE}M6|dQyBEz1ScX(BXFfs)Qeg2k8#PrwCpwNi*!`J*D)0E- zSk*U+7kfl5mK}0fNZA{p3(|DK()1s!VY&QOCguD#p4XLo3aw7W33WVppT|tmEBf@d zVSW~26s5I>+}Ix_ok7R*rqB8Z2pr@)oRH813HwJclwYd!{L4=Bb5Hn9$LOE);GeF6 z{-7=LbMNyH{M&CFZ2PSbU-$;$|Fj#;M1d36d)d!?aTOa|{e)og(=6{eJ9c*b_2UfT z(%aYhBrX}500H7!;=+B zHIo}pk@>0Hn4|JCk%0jbxu#gg!@;6MkU$u@pHSVmC2ZMQMUxC%r>lasXqAwa$KN2v zW8^ZBl|Qj9yK>7E$AbaPGU)9 zmdXH2dI%00Y%5yVtrCIq>O`=%F)Ux{%~TC9Uc~QDxqR7IHEpg(i0_0c?2xE8Ll$8V z=1jP1&j<*`Z4;zHYAHtTHXI51N%#9O;sH7S1A92a+`JjkZsauTZXhhgqZrr(L0K?% z7vEzFcZGVB?!W54WY*4AUQ+aBA^D?$2nGy7Uc%a8{4`!N(iI%wI!dsm303 z>9_7X&vcwGd-5Pd#%fT&)tnOoTHDJ^MeL=wNAS0%Sx_{)kgqmPo9|RuA6D83=bJO} z!>X>d%jHWAz8-w@4T2dAe}caNSv4Ultw0M=)r5AZhAwy{pX>gX_J$V$YA$>_UuwoY zn#I#=rdbVG7ogWLk4(Czn-$=F1hxnW;Ja)saejUheB5*Mj1!O)4;=Ku>#L9I zOQWhfvZvxUwoJ@p-yKb}xG`MUnlWlta7(E|(PsUn^R0utu>$9!6Ct22z<<8k@T1L( z!`PHyS4lvaT-;6IrP*vcp-<6=$+OY3x7#|1r!;vkJZdj||B_v*JUt3Em`pJcL9B$6 z(u5<%F+0?h5|!x_Sni6tU_$Zoq#ZsahSqFhEFbzXf04~rK;?L34QEEa)ZG#0x?kM^ z`lCBad@a)CvzrE#Eo?WEn|6j$m7=boW776Q{_G@iR3Zw`+F!9h_ttrN|2ucwH5ej`jd-f$Sx8BfjduK<%yqx#}C_KndheTO_~E>hIUTzI}}V7fIBW{}BbT z3ZvLNk9k_(cqLeyqIUe+nNsYDvDM1GT(i

jq*;L<}qHa)%RdajdTNeMivLBPukQ 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 literal 0 HcmV?d00001 diff --git a/.github/assets/gitea-alerts.png b/.github/assets/gitea-alerts.png new file mode 100644 index 0000000000000000000000000000000000000000..67491f0e4bfe9eb54f222264b8450966e18d235a GIT binary patch literal 653495 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

Up^l}8wY7_< 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 z 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%0DLd#8Lex1XEOaa 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�C+CCJBD+Qa!>m= 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

fTgo!*#>U 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Ι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 literal 0 HcmV?d00001 diff --git a/.github/assets/github-alerts.png b/.github/assets/github-alerts.png new file mode 100644 index 0000000000000000000000000000000000000000..912569b30924498209087d7d471a50cc37422813 GIT binary patch literal 19907 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%0AP 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

7M0iyn$9KLoOu_SyJU&rr~L@qFK|-T3yUcW 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 literal 0 HcmV?d00001 diff --git a/.github/assets/gitlab-alerts.png b/.github/assets/gitlab-alerts.png new file mode 100644 index 0000000000000000000000000000000000000000..a8c54732a71c4780749311a866fe743d356ca9e2 GIT binary patch literal 258145 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

LF@PM7@ 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 VF|5xTPWwh=BoxI9pBlXTe*izBm;nF) literal 0 HcmV?d00001 diff --git a/.github/assets/logo-with-dark-text.png b/.github/assets/logo-with-dark-text.png new file mode 100644 index 0000000000000000000000000000000000000000..bc06bcdc37de691e556470d5ddadd5216de4abde GIT binary patch literal 41374 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

2QMD;E@#!th0A;aBp@k92l#BzW` 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 YtO(~0O}wbF-K}VHrFl_HT>=jN9{_7~Z~y=R literal 0 HcmV?d00001 diff --git a/.github/assets/logo.png b/.github/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c1f4826ba757b75d8b27d059bf54539252b332f1 GIT binary patch literal 17608 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 X9Z$IUM9+ON3c)j3Rhd$$7jOR;%iy?8 literal 0 HcmV?d00001 diff --git a/.github/assets/pagerduty-integration-key.png b/.github/assets/pagerduty-integration-key.png new file mode 100644 index 0000000000000000000000000000000000000000..41d1522aba0be97ada58f5071cb862b075a5fa79 GIT binary patch literal 88555 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 Dm-2&Z literal 0 HcmV?d00001 diff --git a/.github/assets/slack-alerts.png b/.github/assets/slack-alerts.png new file mode 100644 index 0000000000000000000000000000000000000000..3f9e3cad1bb26472e9f49ff9c5e44e62936f2b43 GIT binary patch literal 34441 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 P0ADX5N>arVZ$ADnbP~~a literal 0 HcmV?d00001 diff --git a/.github/assets/teams-alerts.png b/.github/assets/teams-alerts.png new file mode 100644 index 0000000000000000000000000000000000000000..44b6e898a57a7f9825db2f201aa328d080687f30 GIT binary patch literal 26659 zcmcG$Wl)?!(>6+kB!mFLf(1y>;K3aNEbi{^y0}Y#;1*mL2muzi#TE_jZo%DUvBgoVQL<6jeL3-PhfB&rHiTgel00qrD}3i-d%PCMf|>LPC16frRudVI#ETd?}cd{>{fV?+v!^6WjRfQBW^Q6R!K5^jSDx{5j!B8mPk9(EbrC!D;#Qppx z5dgUNB_YkS=WXQGwu3b~L7eD$>Ea(%BW2^elcePr77@P3S8?yFw%w5XL^fVkvoRzy zFzmCqYa&KYO(qwhVE%dHHaVAYHWkpx*CfSKzO_~f`QjtJ8t=nXHL57{6$hwUm3Bqm zSe5b8cFYLu7Ikq{ViNCl+0pzwEKJ2CVPz&P#oauDOwS$*`^ZMI2&A9ZF;V+LV5+Wz zf#d6i5gTTZ8XDbn_>qF6QqGs~fKL^CcD#5Z8m}KAe#u`8fd{ZQV5go{ZAlqXIK{o} z6V@A&{JLm~jAI_nOR4JgH&o)Q<~hPQZp^bMU8oa+agrd*w~sX87#fPIwl(|X+Rcn4 z>d0MlSXyh3!;!cJEB;FueTAc`nUzEfv9u#;Tw}pOV6GW9=I&jp`Fb3~-rOq^6DxE~ zhp~P>qGbCQ18>@S9+CW6R9&k;SE#CByhCGu@npX5Sl|9(MEU~G_0sik3~-WlZG^cn zJPxfa=YQkGmz_9riCQ5Qacxu)`idV_k|N)aHP*^f;#7voEDu~>bAIvU2wwz#xO`z| zA{FdT8Q5LXq4U*ysB;Ni%^0w&>$4G3dbGm##?>1qf9Qj{DqL^o4dR*O2)59B8M6qo zfv&8R!W{aOCbyk*bzb7pl~Mb(AtqX+CH9Hu{oMs3rM>Y9+EM;Or0+ej;lsd7 zq3k>UxwGLQ@6&SiPp)`x_Bk<1GQGg+8I%vj?L12;kF$?Cdx;>96B>3RG-*@*;TAgP z*NT;AH?7nq>ru}h$Nd7EqV}B`Qa=}p=y7htg`GScPM=1fjCp7KmaPnL?%Au@d%=P~ zVX>Tr%Vagn&;Mh25ES9OJH&*=!t-7+v)iSAc~s7g>(jgc3=%hr>a|YfD__+$_);x= zT!_FhBz_c_O<}j#g8txueABUTXT-diSTT%`o$@Dadr7cfm7$BqydJ9jS#}-J`O++| zSA66ylBfC0hOd4n{s3|Ixt+?>^JEX=@|3(jz46oe71q?bT$kXWf%kIEfXy=Co6ZDH zxHc05H*wvDy;zQ}BT;ioG=-ydQ8tU@v`SkdvB6mI^d*VX@3^+o-ndR~JY+NJCNd4a zfm)+Sxkzr3OKlL;rsoe1d4kyeTqQ(9RNZ0qMVQ zj4VeA0IEHP>33z#t#srZOBb$g5)2-5lSDgcDoa_-eHHD z^R@W78__Gz_n5I#fEpj4h96h&A_p^%)%%a=!Cf=Jp|Hs*U>wVh>E2{`Iw4MgJ*V3) zmEUq#V0)2f#5S4n{;SU!r+M|f-1zvqUq-XXxH!?Whu^$g&&O-?(P(}h17QAsT3GKcP$ zwUku*kKQesoV@1^v7P!P3GStko@<&w8M*V_n_ZS4;st$@P9sy!&h>)GvNKH;ChI72 z%zaE}c)#6dn4jFu>x7eEWUAE6lB>5+VR40&6@?3fWzWw7X?xbyqiz}n}KH!dVFj@`i5RPq)YCw~#J=w1L|u%y-A zRXvjkcYF8Dixuta=4rL9Fw&$~*s-NKhFIc+T4w-bAiGd*Stqx*n1nYPs+zH)h?^{C z;K4A0Rk4mi@v^(@V5Y9Cmu$u)&p)O{IW(nz@s-_@L)WjbRn-Sr=jZO`R-Su%$dB-i z>@#oegdfnO_BeOBHh!Z7?oAP$6$kcu*v$U*)z#uCT+X(Iskd-%$6YqLJ^V}zJ9-fh zE1x)w_W`#aOk~f4Lrlq(M)Ysz9WHJk(2nMc44bJg(KHsu?8 zFW;fkLUa5wx$33QZs2Jy1Z*dD8mSx7t$sWu>igaB{ko&GE+Orf)4KVWzqHDuVND8k zWivH+_4gyGt;6%%Yn<{*5T;Jez3EB`K#sn|dUk^GSkN*abA&#~o|$2`D@@|tW+|Z- zC>>kQxLkW1bDqXDVjgudS>yy`hG(%K(EUo%K zTTbX&Kk8gM{%*gyH3w#%ATTC@UBiKXzO28DzabWjVK=+cG`jE`bkd`_RvQb|&6P+z zLB9FDc=+w*{c+9%DNGK*Za5NfnMC2128I7wsv#BB=}IWSv#k>b{ycr44TNg8*2b`Z ztw^U1n;w0ud3$Nrzy*zGB#m;}-Y5w2(fk8KNv_RGAR`}wLS9#wka*rLw9%81ar0P( zD3pp6eD^nb+V_v*Sls_i8n9tvVp5A`FsqJLN%SBe{W?J^4(pX&sHbKWXU8|y4c8?L zBnwZ-$3tseKfnkH%W%&QnT^lul^LK?RAp;6SxM`LishJ~ys6zZNp6X&`_Ssu!|jy3 zOFkDuMB{DouyuxxjTy_`6g<;FYIgq-+lkhHo}rL~%3~KmMzxE^Q#cxF6>gz1@YNbF zuG~d^D>&CeraUr2aC}t~|2DQ|@pZbHfXz5Fnb0`Vd5)v=KoV-I18U}e?axp)QMBmj z@`Z|)?YXRcxk`cAhsv@VWdC=Z{mBN6GS4#eg{i+frTa%ssKFlW8_h~J3biPGf+Yu+ zzm&Te@H<1OG;lVJD@Rb(Zn?c%@C-QC1_*7S?7Bnt@|HBPpq>W(P1uO1DB)TKCA zQ8q(VhH%BhBsa>CSc!~yuC9uOQ?`lDZ8f?pDqH&{O@y@pV3zx_SOf=vYM#LbWj|W; z3@gldiABIH2cY=6mccb6%~MB~?5hlHJ9LaKnAofhq_azvkk9R=C%x}{-jSXBif^e| zfY?K@bVWzTAeR=pD6Ya@XR1|nG8mUdgs3u`s^TQQZ1v-du3Rpk+P>+@3s0-i=)`7y1a@x_QEtg*T^a&kHWVSrWlNEU@U3gaIJLHo}3)6zr-1R!Uj5dT0qwI6#Z@1#$r)O3uvilmz3=$gQ-tHtY?+nA9L81S) zAwyo*Zp`9{&uXEwPq<~N_%U@s9EUq?1v` zN%2Y6Ze(}kJ@$(>G@_R#h?A-D=H#^z+ssC*tn^Tc2!5Yo)x7(4jG72%5O2JLOB0Sz zocnlCLFXiWonP16hFlS^-x*rNVas*;lcAvpinq}X*5`zXo8F5h^=Oa;w2-=Io z4kXNSRtx@N$eZz=?Q);IulC@_@BJClK_w&fBYBJ?w(}`wemXEI?+5S(gMPqa6}4%8 z!`rOf{vt6zZs)zrn!-1Cr-!v8!!1>)bm`<&1k<(KyJ7{iK$NG&^6j%!V(qynIe9m} zafU-T_d4M=79QcvoA7o(v#*m)Z3Jr7niZxA$;_a>vT;>L--Z&wI(NX&pSI=!ZH#oS znR4Rs8m!D%L*7-Q|S%)2W;oVhpE5Zb6Q8yMQi_%V69>c}jFeNTwqRUhUCs1fY!;>kBBp~rjUk2%1oaYTB(=A#*LTeHh&;-J=L;2&D=XnF5- z53^pVzRf+0())O6kr*I5(a?=?SqiGK7!52(WSBI-Hb?ZVa0JaILd*_tnl6SsG)I+O zWqcZ>C&Y+*4h!zie+a>yJqDIf)EsURQ@ky_d$iT8o;OQAq;hx0$mRQ6;cL;04R7^J>0wlkc{ifCt4m`GdWY;iCP$IddnO*BiP>4+IA z{k5PcKu9>Wto(^6kGTu9bi9sH=QPQsxlJM?6x%YgIFiku%QBKKZJOopTt&`a74w*d zr@7y0i%R2?31ye!$cV8)^9l;X3sJ@t=Q-#{%f3pJVxb3zbgQ!l74OR0$*-d#tB!W8 z{0?h`>(F~cu3a=Yh{K|f`{DQycx__b=#u{4`C4)r9kl;12|)V8uhw3{C>i%99MFj$ zoBi}-A4kwvB*s$~ZT-kIhILlos zyn#%-zn>CFp&OvW&qpQ$9gM zRjNy=|69)fjoIs&*V@hfX);-EOrouL>wR|*S-Pa zo%`vhT*wm5lcHmN#kgq7916!_{23J^GVdk zV-dd^OIVMyzI`JR}N>+vsuy@Y4|;VW8Msr}>9<0*rE^ znd|ah(a?8PHLIYG@8X15G*eAhSSGl2a~n)z!$=wccK6<`)_;N9>yX0$Ii>od0{pqh z(gzX}f=+6@s;u8GLXOnjjSq6Yc4C8}Ps%V*rlK%e`?*QVc9|oQK60!b+rgaJ3`K1^jPK>Y+%e;dq#74FuWp(fZ&xz-yC&$?tyrP0_)`yYCOFSd( z4-{N?WghI&yM>C{81#A{&M~Vv?e(M`DLaF9)gaWhZrzV}9b=X+KsLc@#FJw3&gfnW zbIFV>N3c1#4io4nK zVc)ybdg+L12@p_eY`=bYd;a@Gu+t0~pC4mOV|m_TH-aOv-+BIN=SS+V!3uUMD@aBE zU@*-B(P5fA^@VzvD!TNinT&!cyX$ zdGM0m+RT1=ryQ=!dYZbLYVx1R)S@O{T0{L;%ke8;)4F8R@1Yz*h*uz8rybR9wL!|!qn+Fx8TFs4Hu~&;)Y+_xtaZ*t;|a#nCqTd&blQ%g zf6>jC2Pu;24!$40ZNZtCEs;*Kh18G(piKnN&&{~{E|ZI1XF6M2O;WVWX_GFAaD%Uy zmBk~FaLx~8Sg{h%3s2BDH-{7(<3xw+$^H^%Nu(}(&Pk$X9?mL)!TcCY^XoJKqW#ae zEty(U_}v&S=X7sQAl^# zi9#API97M*;lL4kb5P{8x`1A8kuDYa;WizE|M;gC>IHIQ(X z49{a?kWG{J>Etksn+btQy3M9R3GNtbR=JdxrE_QAQb-)1dH*DTC9B}Es)Wg{6MdxW z{oE5@EkjnW-$CuP61_;XCKEh+xE2u0z#F@U1sJ1LKVuh7dnvn>Qy>+9W2}zemG4@t zsHnVX`=Yt!=raQ^h$KCA9oF|6@Ph+|W!CY)RO!DZkDMx${Z8n>XU-E&4wRC$@gjnk zWKkw{Ih<0ca#S{v@lk3JI^9F!RnzyBsl{?r$A_u8d0?IG@hX8&2JfVH%NU-r@dI8T1I(po$yrm3jHi4qw~BtgDL@jm&7X2`n0;{K@ipj_iPmBJzM zR$+k-&O+pZV5Ss*a@U-&uPd{I5Ewx3!>UmNDe_%Q=FEGnU<3dI5qR0!Vgr#dwt$;X=W zOnz7@(P!7q%>s8>R1GjHx(0bIXci0pU(pnA+Kd?lGq%eJ`WyPBZ>6=wgn;G0fm9^3 zYa^)&?Lftm(lxoZvNCQN)KqgTV7OpLT2oFuW#_KMM`A*%r6~W6%%@>CB~hU{Kgz>R zTL(PV=8(P(XQ!bT9X?7~(s$HPHPBoPXs)q&pBIxI!;Uki+EtES{`S6XjSQ zXR(>QsG^44-ix;O1-(^SIuGmcc4X$u%b)e1tis2u<1X*?4EgC;l0ao&!mUnzx|orI zu0)lycxCsVa3ehkn9f#ZN>5+y$^x6bod=dljxixo&Nsv?7=g`;1 zZ7De7jEBygPfAlZ1lGGT>{og273_xxaxr=H7@NFhVJl_aldg7u68a?>5u5~PAw;9X z0R7p!-YI)fR-Ur&3B+VMl}H{I}# zX4UOKhrR*|yVFxPTF)Dnev9`sP?iRd-nmKY@YXMHUJ>p8i`E{z-|Xl8&Zj9P#EflI zfBO|v{m@lAt&P?_b=>iQyQn8$ zgF(QXV;eNTq+B>#Deij6Wq$a#4t|LG6tDade9`W?o116#7BhCs)kM_R1z3(X{VR+K zGq%+Y)i2YR%WSdlV{%(j9ezp?WiA#jqT;K(V1>b5J%L~M0Dvc}-;lkCusUO7z}yXy z@L_rzIv?V4y|b|WioWUNiFb!M=JeAlL$JS}mZqkZ9di~=v>%&M8ta+SXd_=%5wTcV z_i0iZN7k;zcTtp-A%DwY^7%+PLJ$Aq8u5`PcOs>(2cx09h>{cNB;G!Dk0P|TT=7(> zMp8ikg41GQNE$p- zKI3N?DYiE|+q^$nr1_ACX2;+=yW+`iRp;Rqayw_m=)+#27d3Z?sN=oF?Fm{ok0x3u z_8AC<6SBjOdkaj#35}4LC%DP63gK)ytd@B z&oSb)8Gf|S&J-NtM#weVvnq;bI;w?D0dl>S(Gyp z&Fx9gK^)WibTTBcK00w*;zr~`>&w8gHBG?+=`Yf}$qXSLFU__yT+*zn-|TuayIL20 z=4_#+E+*W4`@?0k{n{e+M>rZ$(3afTS~A)$k3*KXOT*@`Szf{266&_P`yas9Z0`mR z&yQ??K0Ae`+jVyyhAN<&d%a>@?#*&I^}#dS;1+~1Ls)3_mA0CWz{yOsNtrc^(VTgp zh~w+xF*fgkCvXbGpGuO0^C`jl_sXP7mGY>r2gwD?(Ta_RYOo)ZQ~MvM=7R*zzgdjO z45Olx+-E5ISg)+w~);}^wE***20DWVYh?%yl)xOQnI2|YICyMz_;KE*OpfMWVLqV5SW z&kpbUGll_ko0NehR~8R7j%~1~#}Q@=LPr5nrF)J1bVlt%lN8rUwfX@ShE0RDA;W%X*!^ zasPm13JrMnIdD^h9m(rpve@_|71x#wK6YtBgMGDL@#+X(3Xuu0c+JD+5Cipg({bEt zhg+tVCUc$tJP5Q|pIMfOYW~}lg}uJj`Ue=$$%;W>oXuI9Nl1qP(58)V1U zA0G7K=lQsVNkl9+v+jvGj>wICy}8;MY#SmWQ0{U5b@&&~Og%E|q01!pgi1m0(PvWj z*C@*ew}ThQ_&72d=bPHk9$4ZO3NOW3<9nLfe0q{bG7(O43N^f9wwd2?Vsnl8-G!>P>z3_nJUUgBv5BA`H86gd#eweODnnH7pAo@K zjjPjQt5&nEdfR1$qgZ`r=jLKO$sJn_+y^e=qj~DDUpLn~x7O0FM&a@Z?z^L?_gNI@ zkqdD@)^-LLXWF0}TQdCsAa`aTW+Hom8WQudDFG|Xnff7*i!VSVa0pq6nN`hZ?kqOi z&}=F;KFOYjYRfTP^`{cvq zAv6*KXZ;F^K`b@8PuG)u1!rJ6eI;_Qb0D@+*<8sl@$qZro(r302a2V?o|E+-)P3hc z21S?0E9jpCcsH+(4jXG9pgzleT$B|j9lxoY=Uc_vto}vj3<(q>SkMgg?w3OE@r@%F zsr;Too;dXizSRt;VO7=5w7t!BH}SJM8xFE64f;9;kHDm=`??qAI&Re@WLO>Au6qh7 zF;D${q`URRM_H!%U=|@9(Mm2u;=;<@6qCti4{5duB0IRwm#0a8&~_ktN`s9Kjnj#c z1+5coUh1|T%pHFsil@;pAAZ2&u-{E*fA`XT>_2A3lt%my7vQOPD@@1qH0v98P{n`p zH@VOMTBk?}%KtfOkv9Ju{Y^Ie7ODmij<-meRdbK z-rhD1&q7TQv!3g%TC83c#D#!?B>A#5Xp-RFZog`T7$-+UPMpH9C`qe1Sa7Dsvrzui zbuTBxRTZDv93IZh@YX9UVK%&M49k_#eg8U^v~(6vRYExbIj{1u?des;XJX+_rLrjp z@9T{9*v0f496r)id5GPr_*OZetFn`*W+@@kiXfU{bG7^Q(U5pC-`6JX-dMp469mW- z7-?%g9IN$t$xAO*?#jTUfA6z> zXjivg%3P>a&;AD3o2C3so$qpJ&DV0PpVQ?Jlf*y1Wubq%|6~&t@ zdHBoB-+qdxfOT1R=h)Z1UKmbOUe$y` zwv4*T9(Ta}1h`K3UKcj`b&tw(Ou>-|ns9rCTRIXjQtyrzdF8Wly7Q9yXi; z=R@!%q_0+^#(L7w&lc|CdNmV`S!hKLF3-w*AemWBG-GX1@mT_sM(2@BW96;!e=F;J zI`)i%Lj=+|D#r5|=sx(IZ5IN-zm(ROev4fe+L}_3rgGb~Y^Rtr@cQ!O5aW{15b>;b zUG&d!>TA2wD^K+ZOg!9@z`O-E^1a(2@(&H}yK{?UmR0O)2=gBPd6w_uOn!z&Ti?|? zq7l{9d&16Un?1|krRwa&u}QAy;JkquSnJQDNqicm`MN-0xOji!;h9?59UYJAne_r} z(*Z-36SJ=}eVgIR&ds?0rEa^YYlM5JP4SO7gIQ zZ&0>^LPtK(*6#3Hsf`Q`wE&P8bnUyK>CLy9s2G$l`-D{WzyOoMl_-9wDH=) z+Hg|-HAImZ!)gep7_lV_3x(DE^?AaNjBt!dUnua94{dV~y1Bk|!!d&CO=iBO93d-T z-Oyww;=J>7*2lO#n}K3jfRwEJ`zY-{*o zcLg_OsTJ|Uwo@NbbTwT9Chrp7ryv|#~qpbeMn{@17;oQ-gLDI=+-m$a^x6Yq#NB*d$l|D z6X~7f-`wSR;=NSx4MXV1tLqR9)dW<2W1#Pgd}57@R?2;OYAF9cAKy*lGdMrN2-oRh zMmYybYuuq(3;ZV1vk*vZcsXgPpQfk#waPy;w712n-%0xU@_o(`aKw94@Z{{7VM#B! z>c&yCYb{8N=i;!2+4cI*F@lZ=EsOP+YCF>z-o5izjt)^0%*9MU4|`>OApx$#!eY99 zZMd*J1-IR;IwT3mo!(ZeI(Pn#>3si7rB)AODpbc0m*Zp$G`o5JT7-iw^L{lDad!|m zMoLlR_t*XCc#|#}64(&soj<&*T8sfUp#rCC3-J0%I0^nb4!109tfOZ0tjY`vm5^O% zHae=ZHIH{RkzQ=Wrapg%IfypTe&slnIceAY{IrGqP_vMw&>j6O9{gijMiS|w7ZycdWMvJCoX`RRi!FCmBbO_Az=fRD<}b$r)r$J;X6G2RieCN}Hq_H8eI zc-N^zsSNw8K@Eu2%?i=80B2c!4HjbwTc_gWJTEr>}@z8XObO z$LX~;ZmjM#)8BKUV|P2@gsNX!U72cS!!{;)1-J39_ z@3+({wRRJ;td+KKmzP@HqRmnnaX(K}EY}av5|gpXKOCJ_UX^bVIGMm8nFiikia$rb z%)D|P2^U&i)~@xOicF8^nU$yaSf2p3#Q0QLY!+$yI1{PaG!?|1Im!e$nhlV^mY5!3 z6Y{Urd8%BU5~TR{|1@z5E^WelwVl+<5oSl(ZdRIz`xfB0vp2@(4ELUh6fL|Y z#)Xtz%-A%B*McmD!|KH?sr-CbE6D?w+!Fi`ht;^dhfBKkM)1AV@nfEry&&oY@Q;2pdq+YH2ap+}n)5O^-Qf@t$y_>7t2$sGtDe+}*=;A8lK21Mn6RM~O3L+{)-GuPaKcm8ab)h`JWDax63R+}$39i+~xmucsPqSz17s%GFYxw%oxV z$OSd8;61bln~^N2^vdil2Wi=8o-zOtUs>^QQOgyvyKhzDrKRul zvARvo0NpOg)X|IC?Z-24t&@C~Mk*Yp-9+IC+B+nu9HSTTA~kJfM5o58YtT?t9>(Le z4eEJ&GYdH?b_1_vQduWQyd)E&DlUC_oXbG*eAXYYqz8RV6RRowHSK ztn%F@^?#x7QRNS2*czRFksPU7jQplQ2%^{|+}IYVr8AD)Ubv%t%)HOO>rL^$#A@E>n{KUXa+}c<<-fcQ9*}+Y&Qe!s z1dtVs;acSGeX00*WC?O4Kk_<+U!_CDpnKE~gJXEEkx7h_49j}?!N8V|94j!5t?(l$ z?t07ODhr!;wiml`%aSyo`9m;qCI(-;Hc^&mjNsUk{v&HRt`^9rcUbugBrF^0EHw0* zZ+bxj00^3a{5eJC?^)o!oq-+iaG(F`U2uUn*OoPzt1$O(2o9sn`*IUrTtPs^DKFc! z)>rLf$Gp%#L|SeZR2vD;m?x+;Ol=29hN% ztkWbFJO`R1xU}QlqF77ys>^uS+NzxQoSkt)0vGBPL3PEpJ5^H_lSb&pED3|fli&2UW6-Hp6&K@?Cm_#oVDoH1uatBvXvok*!l=qCgfYTc?hP|Gth8TTW)s>X*&t3)bCBF&9RY@jrELNMhjTFTPe|Eye+fm&j|I$ zy(1Q9b^IN(a|m;C8Xd>9Y>B^FkWjY+$XbqKtL`2ERHrrQ?tvXiG24`rkAgE>$ zVTG0EIp?LB(6qUY2*u>*#p;l+alePV)rv?A4FR1KwK5()o0h4ey_1d`<{j8qY(lSf9#nW~-cht>kUdNqI+fIbeu(WwzwJ>f{^UBS_#VYSqAoWF-6e?Z^QWm>=_Sh%l9bfj23lp4XkA%ZDF;1`v$+-0 z@*|us+ar^06H2VXBx|2U_$y-dg!!6;w@bcD$aUdh3W~G0pt8Eyw^WcY9Y8K6HI=rA zaM|U2GQHUgaxx|5x5eNj;B`=zv6YlCOZi`#tL}ypGby@UfjXM_n549fHJ;kzplABJ z_jQA`lfA1r8ty0B8I{O{0A!{tYoD7aD)Pc2sRh@)+g;WcK@r^IV3n_6=+vjEQ0G|C zoQM2h*S||#Mm@Et@W$vf+%FHmzWm3fw;JjVx2h9g6C+gV)XyvDp#`{0Xsx3!Oa`hb zd1$NATYNQZH3rIl->ME8aOmph%V8l$bYzJyarD%mMY7h?j!^oYF< zL+d;3a{{)FRZl41dr7Ps?aZaTMvAQl2RPjg1v9&aqSy4+Z-Pbs%kHToj#>ikx;+Ug zw}D@DHN4X;sEgarkd8PW6X>7vcK6ap+}GQy@N%j*GrxAp^KAu~3*rWX!5N@?NXGOqbcP)LQ$?+1>1h z)nU;#qe`wCff5u}?+y~qtw;in$kJ=tA#KKSW z#WME}Eej^j1~5D~h-gHlzo@}OPl?;wqeHrkrdgC&+1?H|iRlXW(Z7^)DdevGN4i#r`bf#gxSl;vK=JxgR#AAG+b%+m|IPQ&l} zAyrb$cIJIs^i4*56IN9{vutFsp)boDFk(Qb2CkE&5Nf#$s|>gvhM#{u|GeEh_T;;* zHIC#?>$1}9o%kPNQE96B_Vy2Y$}&T)?1QmK4U(;g%fP5;3xSJiFe%cv>mPPsDYyBm zEWy)Fs-;9(mccxS+7+O5^to`>#)Bl$-;yJt8$S;BFHLZ+EZ8T3%VfqK!UkSo#GRJ20%HDVpr@Q)IK9-Oc_KFh;}EIIQp0I8cX_JN1fow?F2hNCYVJIZ63 zQ{?x^WJ*k+sQlwz>!`m*7Ve3W_u4VJ#S4k`Zo8B0+`6w_3qSQLf{g>nI-IaMMxq6h z@d*ja$d#~|C^dbWdM(`+bbQ*%#n7@QS|2=lwAUS75Bv{%l-QMwhXK1Cg)W8F~3@1RB z1an*~L|ywh-D3D_9S(Ot$^oJavV6HdF68N^e3_~*uQ?hiZ{<&8#I_txP@c!o9}B%^ zv(>*`i-~w>GMNdbo2`6(kwJ5T|D%g5Ai!@u33JRE57>CHK>_}FbJOVK`bZ!R<^b-x zU8Qe&#ha{^(ao#hc|X!6AH(A8Q74&W-~ug+=RF2>6*nf-$XB=VHvf-?GeM=JyTw*p z^EL>u5j|hM`t4{mKzMWdQkAdCvug=}N6lysIGl6)_08{7n*|3Xq266n8P|8)`~c0a zE`3*4ELK8Vu-fnzUUniJ*UZJwtXgVPBIkI3gYoQ5+7l#jWt0pTHM@aW&r9%mf2kjBvQ+h zuyHjl-;!R?H5qd;XWi;~&p_SBCNYay*!y%U_39k3Z^U*&!*4#$U_Bo8;^%#bFXORY z7=KBf?aMp9_16`TYPssF#`JLll#d24_Ove7`3Du^4QI}@Yw7#QC_~*Za^&HNmgljI zC6X|HQ$WGa07^3fgh+2&stpscZp<%ZUt+s`zIBD``kCt)|IIC)F(gg zRaT2Vq`q--RMy{2lGQVT_7m9VN~MGiSyas|>aM(#-#Lu+VZ^lW3HH!4zS{I$?T{c6OO8N{C37T95hTW)t9V~|Dm7UeTW%Z=krbyqnb zxBNEIT#0?T%M!XlN&GObf^K$>v&R zprGo@K=moJm>3%^gVdya4F0S6gp(JX?4D-*+Kb^_#z6UkaUY5Db*JvArS~g``Vh(c zqO}??^R{x^o?g)j)*Fn7FGnurkG}kJYu;f6`?XmUt5I@mxtqqmCwuNrPWT7U?Mw!zV8d2C(0s{F9P+_;OJY@aA6z<{b<-R8Oh$GVvJx1x zw%{3|E^)Rx;)a10TD=kYnq?2pbdkx>dB_-sjO(FEj&+yD2G_6Pv9Q;Dftk9;j&g64 z{M(uwDAoy)L!82A5Q%eB?G|5=j^*2`AS|%Qj?Sz9M~a4R-@~iS=G|s0@QgSN#`4fy=I(pMcm;&%QCe-*GgXf%zgA<~$d?p= zTqQEO80Wvc13!onApHT+Y6cI=$eRXy2`4j-tep2Q_!QALH2F6rR|;;0TenJhBtUub z=R#Q$JxZc4e2&d+^JEWnR7~;SVIJD-H4JWKBjdi`n1e2NvG32_ZGCpkHmUFe{%j}l z$Ukc966{*yql)o90hdR!x#wd%vXuTTeot~Ct1c0Nf-`PU)>8#D>f?TYZ3fnCVeqk6 ztJ^a9hi-q=g!g5jna3Rz8N3a(Sv23tW?ap4@5MN6I~tOgYjkhJ)o39o8*^Wn3 zExs~3a74PoxFOP-#+(j&|BoTCGW?6^=|UFLA3wbRa8CZ;^>GLC`Y|j@A8>n-=kIjNADpXLZ{{q+&s3+IHXiMGJs8j zK6iamAp7e@5$MK4Txv0P;?;%>aSgOBp|0_kqTCBs6E7?N0{{%`@0XNaS8WtH)46;z z2fs!=yD;Uwnl$u6gJ-1+hVtawAHKekWpSA&*d%K2*g^bi&{^aVeqqOOQ)pv ziEl;f{PHV_+FuFljOhg;qy)6jS*% z@7jdV|27-h*4xpZ3B-i1As%}dLl>yCj~6JLMk>I(!Gw}gTGxBvRFEfkT`BM3?EXCp ze<_}w9`BRc1yRNankm#uRD^hA)7sd#v-ExM;t_qEX4R-_zgusePb1M(mtX|;=jjAQ zou5gEEjBwQjIjyG87%kDB?Wg#NqIMizDC4(GAo(@0R3IfiXi>0Qovhoozc1|oy^^2@?kIN~}K#KXXYoN@nr(a+t1AW~r4}+<8CCACZ0l zF}AbI2VIg*bcN<>K3^UB%7>^fi}(1duhutd7WF^~vwCOK^+`#6Z|&=Kf6V8xNj_IU z*EikFTTQ4aZRhbg%bT%Uq#h|A-~F&mlD(v+*oITGDG&eJ=UBno4(ZZgwh@l~b^;6{ z$O{Q~Df&`B;AJteg5(q51BIey&*x{;?n4$y&2YmA?uvpuQ6KN>HA*HwRtKBmBlH#^_pa(VEYFjO3H1rh> zQTtYxM4ayMa3=kEi?WH*<#-JC6Uj*V3sf`?$nybApSvd17BK)IV5(z%?xh(U(Z@b? zITZ`VwTnp=b%m>_GO_@_>{eGUa&dApv#OM`q5O$m+|7$hb-FDv@#w{?brPpW z&OQ7eFM;yX6JT(Zt!2M{=f~;Y-8$Zj#wxGPNlX1V@d&(K(>@tF7iC;ye^cDdR!PXL zO-2&|rbAESz;4TD(U_a|K|MVsN5^of7N~hpQc?o#&5g&K1ckUE?2@keg0N<~<=IQ# z!H$6m1<3p)itg$1uL1yIreO*P>iUT0yWe}~z-upuvZ8Q0YW9mAn7-`Y3f^k0V$9TJ z{QHF_m(fbt%tr}Fhc(Bopyt=c3xLx+;}E9pg29hCn*0)ujSEI5A+Aea>g9hyD;xM& zc#p5-KmQjXcNTjX0A6ee(&ldVg-6rL$sKhz)3xwoF8q>JjSVG`6?0SaPAj6Z%2ZT- z2j{8&RXQ;9Q93bjROhAURJ5xxSh$bRGNhuic$#zAF79hR4SwtU z^}jl^BO!W@ZHdJT`?OEe{G4VZEC%A{}DgG6OC5=mq_x<^!`;u zmt%P`((%K^5g&a~)x-LgkD0N7hRJL71>~2^my0ZP11!p{#o~03s;R2#(?T|XOdqK#)O{R>>a<^5x@e3$wU$1;)ZAF+j2^k0~x68?|xQh@U>v{9h>7gPPO zzDTX%HdHUb=ecm|oM%6}pG%!~1`FyY8qayC$!% ziYO{5DgsJGkrs*w(u;zKfPfT%013TIkrp5jP(Ty}G&JeGNgxnrk@y9f!gGyk`T%1yMFHmt2b9K+@GaLu^dbUgvXdc1)0AKm{hs~1B zT}A0XN5|@Ji{1E{9GyoGhmYr!2EpDDhF%EQ>A5U4e|w%-zC(?$ujj~Vmds*I_r<>51CDG);cw^5an^mR`&sW zU^Z(?w(>l~9@BG*T^!z^4fX1}wF0S5CjuQVlTtW$DBHuCLv?HN2j;Z>i0eV7WtZTD z)phqRG2xvSme!>v7TAqyr(PCa7L)pURnFiN4#WLFYvBOdnatFP1xj!9*`ENhvGY1! z`niRU`5a-u!Mr=|Sc`anzXPzRv$JzCQ=`=b=*BbPu5TF*$2sROd+jQWinvr!9|D*xg_DL-t3-@OGN2l#eE+9!#@EDl86Q!f9?|@mjA`%i&H# z!(;O;!0j)1QVJRJ*Ct{D;trO#K(@c!f3+x$qCw@+Fk{H{SnPEXnQ>UO?2@1+D0LIr`AGuzv-;fU zfK4JF0-fcIK$pP=-I1wPE29@vE`m2_LA8^MB({t%Rkd5=!`~>Pix+#(-9t`xrHvR> zlHsE-NHD~7!S_?;AsDfPMqJL>N)CyUkEzpjWJ&4PNC{Ef{YSYus&G3n7M#RQLK1m&y*AGYNmNQyhl)rM(3{8dg3X zI;Xu=X;0Jks6rvRoOcp+RiXvNi%QoB15_4ErXfCdeGZC;r04*5P3Y+@5SwU8MH1J1 zC=13o!=BjVyJF?$g{^4#ws&WL6GXX@mKJNXvfx8jsydyx{S$zY7vM&m`}`S1RbB+r zZj4+P7o;xkU;9VzS6LBG)(_D#ys?yBnI_7u#}hm=#j@I|6^f&7S}_K3Jweu%{N!oc zSlHXX9j(tNS!+e)Q76D|4Gdx@F)mkvQUi)ym}1qO{ZDXFc_4CVx#g6SYT}lxX8xd}k zt!mpTom3b*flgyt9Axz+>k`?|vcH=4I@k$dfaB}?@KN=E9D?FS*F#dP5F=U`^7hoe z;N|bT@;m5w?u`i<(Mzuuy}ze3^#aS_3qAm}v+doLteywz_u^E%TxOzU61YqcZaUc! z*)gI3WTK3{kGL=X=}ax5kAPi~s+lGhg*^rJ4H9|JE$N#RMtmhH8OaAcjHjz+AUD91 zADu>4Wi~FpW>K{BA5nSM9BI{}DDQKdK+_OBrNwox?aq<+>X1!_6WOyU^REYgP{gaB zS>W<=I41*W+vlswtP-w3w)tVH<(Frb*WpR(fEquNWN2Gm3X=>TsVb^jT$I^2ox2WZ6*UpiPDi4)?_I* zLe;=zzpX?{l10HIeHK)xvH4txRUf;xW2*A%T)(wR=bQeufcF=jr0wjiFgEW3?xIm; z?&?psHK2Y%F8q>g^&^A(wEHSwNMcdvJ55t!UVUcd_HG{SnQ2Uwdp+$$sxE)0eM|Np z=dk#z8ZHr`!kX!fWPJkeNqDs5_%DAs6v|lK$(pIjZRJBr4QA|Na`AsEQ^Zt662F;?kW1yxe~BM<1FIP zWLT{^fWbnFh8RAzA#i2bkcECQhAWtf!~o%25XMi7>R@dBY^Vqu`O?$7ehel*8| zi@2I1eN_5P?j9pZ-bHqrdha=l&ADe}x8Lb}WstqwSxh=L2nN-s1Y=EPoF6S-L@iEg zq|{--dA%n|mpGrU`!BgY_taOj9zH^F}rjUFi4X(kvOI;Nm52XJyK>6bS( z6|s2_S%u%hMy3tBiFFCRQV~zvoFA%%{AOm4)^d_!=~c*WVP+@)m1S(>bimmv!9S>k zN_=wKX8*NV=Z%{*o^zbR6W1?Krj*DFmD<~ZgYK7KI46T}Q>r!C^P;0U0Dfq8hm5ZK z)eJ`pubOn(VqS~@gm>y!kt1`bbR6zt13?-*Qm5d?MHUK1C3&yz6;(R>D}Jg0sX?uOY5~vRzc*xSo?I9;Gz3`D%RHm#J99 z&0z*iC421Gzln(K=Fb^B9qw;U!|50UGE`VNJp>cWRVfPYZZ^fPj-(CTO4Z(6h=qox zNv*4GBHDdxg(ka;)8weUi$8)zW5Ul>I!n(EEEP94u_78tx^czi;p%HC+Ek&ot@fbJ zlb4-QOt_Ly`m8G-{AJEScLlh2p1#`_iejj~+kHFuKHbxIixlH-*~YzDgu7O<3BFS< zKRQ6hvSnH2PCW!W7fYSzIC&^{_Mr>ms8$~w++D#=+M_(&t4|?tE5(hnZNu^Stpz7f z#rN3C(DkKNv#B)jR52{q-DR*&qpLHwzY?q83`Fe? zI_qsrGPBBc;i1pKIN@FTP#Jk2=)%_5saCe2NO`aA>YH&J7t^Z%KPmlc zc>}#R-sx}G1muu$@!jI_S-pRu;d@MuKgZqkq^BWZ*cYlFQ?C$7cwQ`&A*uflL*Bxg zFsrvc1o$i`HqL?PfUo~ANAsAO|8Q+nth*%39Vnt%&a*vy{C9A=U5}4^Yz>NuK}MBz z!{FMr?2Feq(R)-7iEt!ZI=%3d5jD=D(2bv-5!Pza>@p~dDZe(JdB-Ic-!(g{2GE|B zY{colvL)3?y7j52EmbOyt}Op(W#(YJlGF99jIUoZq)9>mXBoQuBAP3BVMe0s5=eFd z9?qml-&$y|*Bq&>LCAJUF2)UYq5U`NjME}I@;e;?C)72fy9O7C`;}U~UXLl319?&3 zcEM(u^NSKx%QajAoEjD)F|iX$_(RQEgna~*dC=w0-hqP+$-z=fp%QVVpz#im10oB7 zxc9LeU(0Sm3Hyxh-Jevu;!u6DXeFvUuHBV7h(-%dwMf^kMD_r4;PgKx*NOp1eA0bW`jd%wdquP?N=8a=EVZ&5yn zwO(r{k0?hOrwXxvvT;;qU}AhjN?@K-<0@CI{Pn6LbFbhhbdL~I83PtXY5Co$#weSp zh-Z+E-7H~@hMT|#@K8NQX80f$V_EN)xiIullxmWcNq^6B{A06e6rp=FLkd*8V~9T} zM5+suvWm~uST-35Q^)Ql>=LRc@R-zDv@n+F=Gt&~e9Bp8S7EC&^9+VcUfv#v1|<;g z?dkAW9UP2vM~Q%nO+>cD$;BbM3PZ+fqBVXNJH12#cm3Kfw1bFDey}*%KXs@p5840y zU4t>;zWH!ef!ZO-r>m~wG0u9|R=pkx|5GG53hqT%&K~_4C|i7$-DA4-gQY$GIcqvq zKjbkWvc=Hy2ECM5W&Da+#Q55GmEXZfRXoCf3MBF<18U$WU6HW7qnld?Z5R21LK!%N zjiW-jFGMb1w&?2?0?qI4J$tLsb5!>yk83H3nj&{-pT@qzwB8Z$s zdy;ke)c3dm4!9V!MkwD~Q+(6khLk1Ydw!25Y;%-InkrRhc5x|WzLkbUbfho@3?(3f zLSM0jReW3IV54x}F`_qDaPJ3aJ9P_Sl|Ma3$+A~Cf`%SF;JFF{T&@$7W&i{|k4W6M z7f@kriZ#ryS)eYULPLY=fyx(Vwi%(St z(K(|h3!O%BA$TUm7M4!vC#UA?k+WCaq0L`}?#jzrl;!4T<2U@BDMUgCRqIA?z}O6} zw7KJ2aWVbY>9L(?IgHYS-3onRgv;M`$;@;-%7iM7)w)FY=o0A-EKXvnj9^$}0z`>{yIYmW( z{*_G!vB*DSNMs!wbbByLzX@5-zq$9)ap0iVnNsp1LOj3Oa;a$7|N9@7%r3!%)wY3i zUO|s}V*YZkyA#Z`nLM9-g)x3Vo?MFgaa*hiSOQ{#fLK&D{MOO1zf78m?9w5A^Fj^5 z)=`0P?$;Y9sRL?~7qR=YHnr1K%>LcLcvW!gYcLg-aK*Q|@YF%Ok>J4TP1f!nNuEtY z$N}ZX<+##=?;#_ru{x;Ao(Z7@#(k_q8E2+O*Kz;@P5w{ubp^kjP4}f44%)4lgMmLj z?B|UX?thI}xdw}=-1*jUOCIU?vvDvk>Vlp5uf6GRq;XZLM#Kn;yopaTjegIuB`&JATWP7V{cMjUqfS9OKvFr=J*!04 zxaE54-*5?7f6R6gcbFQ@HlXY~?2ifmHO$Bc9|+WNmxz;8qyAo0aUPaG3bpTA-0OP# z@b}3O8Cjexe&$q^1l4qBH!BS?s&r>pzH}_4^cFcJzp<^Na&l&8Dap3>V8M|w;MMCj z#yQt^-Gh6at_4lhypZcJ0wHl^v?^dKK|QHF6yjB zJGU%Di_d8sJ`Rfa{pCw4Y~$Fn{nL-aF)6l6B~{a62;%&J%si6&-sT%Sv|!ES z+1hRXV&HX=s65oe4g>j|(mH!r<$}9k={)|L=7sUU_zP8mYHq2)N=Xz`6#|om|L)$g(JlFnoQ0Fy>+bi>eYB&FY7SEa-(4E zFz|3*INAjjc%ej=Q)LqA&W$vfM$Z2#N|3sFu6G4v#pd-&cvrb}A%}kh-6)uYG)A)W z8;FIl6xJt0feEhoPy!=+Tt1aWx2--ET;1h0-TxHhn?v%yB~s4anZge)9uC3f!g}k3}?jE2Uu#H;ir=s&B>**sxAA z8iqg(l#eShdtNmst9lg{J*ut%4f=<@UUI8haN=ZZS5Q$HO0)*gogVp(fhE_A=w4Qw zX?&*@VE^J-ztQKg+^(?D9tW4e0wY%If)BBt$0c*}@$_?=Sgt0*N{-FyfuQlm!UG7s zdArHs!3SOIr=FZ1pR=gDR^`!U`kaArYu}=W1+x75>#&-Cv%x0uMOt|62i3*Tcj5&Q z&PF<{y?JTtm}3CF7sacs%6d&3-IpmRdk-WHAqfHF9{$nhThSupZn61x{;(+KAi&Dd z!L|*>_*hs^%bEXCEz$b)>JG$V?^x)CH^MUbR8x78vJ?DQEp%rW}2EP&cAgo{x zjQ1oQV)YjD0Lkc6dD;3&(JD7jjjc$(FcyfoXRk8aSQEaAT_zhf?ME}k-ve7KY$>D4 z>&!dOR=O6C-wachO0n3us4JY zo7wO+hz~sfI|XQ~2r4PQLl^YsYCh7JKry>tjRVeVyK0Ql>3WLXxN!Q|AdxKjdlM}G zDtK3s4;zErR$Px28M-oN`R9){aiviFg*N6c>15()!ahhScbfFPY{ zd)kF#Hlj=_(Cm7XZ`J6UUg<>#KF&B=R2xp+-JVZAomAOe9T;jPBiafm-Az-0J?9Y7 z@pPT!+Ic{2!9ErR?CojAu83B`RsB{HvG~f6Wdha#`<#XOFnu9inUna&Ap==Zz>BY zg7!LErYjwEsIxr5VKnKtSjgNJkq{egM)X4P+EGdppUw8?yggak7eYa?5{#z4a)w+Z zbxL~_l=XGjzHIM4MzO1qhOaZolPA7~7(y`D=?<1zg0Qf<j+>kI#E-=Q}+h~Jmh5##!T&`rDd?_D- zsNq~Cj!sDcQ#he++Ogyr`k=iZNdMcyfB-O>p=k**?6&5FIb1jT74i2u4G;U~2 zHu%f}TN+3WM?NT^Vy*)2ND%w;6n8J;&lPuftr0OO$9dn8o;;X$JuL)a+jRf8iAMa3 z0{jIqKG;S+wZG3J^AWi2COc;ihY|sLC!XxL1y*?LX?? z$CIDyeD=RSw+c~p_iE9}pB~*gJYdtS{bumfAv-;P(Mb3*iUirQFgaH~phoM#}kwdI;T%mBes%$xPa!OsoE z-`Uy@V-PWtQ-vPqk?ZSt5o*AGVYbFvrd93I~R zz<~E%UPJzS7zXZn?orN)JHv@b9h~UG_YG#Zx6++^<~Hbp8v0)X@aED(mn8(i+01d! zp7Gj&q1Ft8hzyWeRo6g{YeRmVO#OVIon^! zo)ss#qDn3;$R5^yDWV6p`Xbv|81!$p#W*FV*D=cZ!(*mD2&chf281)mJk&HDbez98 zXvuo{kE?^?(4q3F%LUWnYfm2%>9tFij<|nRrmoCBQYc6?lxPiU`%aW%pD`4tD>sG_iQr+I64D;Ph|MEXyH0U|g zq>9*=x7VT%W!CyRE0?YxB>L4DKtV`<7oUMmq8I0r!v^Y+Gg67s^w>!GRuLKN$tlFM zksxa8#pJld3ymjfH@I;`ROjh+nRlL4e8(a|y0 z;&Ou`J1GZY83gRw&;|>JLJ?Ock`8X(wKHCyD+Cg43h>|GZaK?emLJpB!XEebFsejI z9Bq2E(hSn_-W<|BcTTyAhlj^$u;j7!lLF4e^TQ;#R)FQF#czLD83N?|)UAMW236oX z|E4Zh_QRz!aZwDa&*C3cqin_JE-Ei7^^47qbh&p%{uoJob&xkcW@q`Z?$PmM(+>8$ zjF%=6iRIGbm4lm89Z6CC-X`JK1^Fc-NfJ{E%+4v&2j4Fi9m?F#_OJFo)WR>lA`(p< z)#Z?FB<#yTrb=Cz00qzXmg|dWRuo($*?nx;QzQd+JT%Of4v{YV`%p!PfxVGcv17A^ z2~Zm#B_>hIvg!Lc|L6AhOeepQaiyc=-NRuoU6*>>KMM@K(;a6f{fI34YQmef5qXf# z_#JK$CY@n8=GF7NYyTqB=dxe@(FmT;!;H)|(YLaj-Iu7j^x)8(O@TJ6@ZG7WH{ z$-+*fXe3|Do#fG$E~w*hFODf49cvwy_lU>xN&~18>!>@%U8oX>;zRbc5N_*U8sCIQ zq|D3Zq-TPz#Ds*I|FB;4jYVtiwSLIx()9x36i)1=>O(Bw7Ar^*3kFp;r2(hrHQKGR z{sHUcd{p=cQ%p3tuWA2fqbfU?ggfxBF12R$p1o%67UkNPC?vg+n#vQ|ehUaq?M+3R zqoj4k`Cj3mG|E^!@4uwvUHb>xPX%^d_vjLHGHLoBXIj;_wD}JRKV2M2FphoGV$Uw0dsEsDrzbYhrY~*(kgP8@D!eQ4usWg(oU#C%0yq3ZKw_XP zyKK-1{zff!ct$AMef9n4;!pL1seJR1Q{|QHmlqbFV#IHX_^c)w3>r)ah%78mY9KI< zU3il-e6#!Nic;LZyC$#J2QeZz+2(Z_-*=9Z{o8^YmlGYII-%vLeX(o` zXxZoC1-sXlek&Xq)TV&#LT5_2ImCm;SPvS!E)R>Qoc)wvYZ6vrNQW#)JlmX9oPH?Ev=6N0;ijstDwAAbhDOAa_oO(kSDL>5haM6OZ zo!C$z$RC^DG8^yr=oK>zb&m%gvOYDLch=cI$AY_~E~-;_N?pP2`jE@6%K=$a-Ctg_ zy?SXLyF2$9U+J@|`<;!Y(b72whl{BfQJr1CDUr`-Gz{L~-<)qA`8~E^3Q{^a`nX&j zuQ3jfzo3ko?Q``c(Q}F8{cDk;EGH+|SWGE-HmhSlaL};gt7iK$AoSYAb10cJnO&#; zZ|VI40TC(w&^IgNhaFh)r$Pq>2yT;drm%VIqFoESuD*+hn9W?3cTUtUEkm{V{%H=) zpn(0dD6w(%Y_eE;MfSmBL%QKiJm&NQvYE4BwFz0(TTLbmzcPHK?OqZCzHVaMqvf_H zrsPdr=;j zxB2q9RfZ|$-V4_4uWq&w;Y#D~-()%86B9Sj2%Qf| zbAlIANARprm5g@It(4f*;(5*vmlmgSxG9k%(tc|EFa#CkZA$YxFklG6MMG$1J&e4S zrtqxF^`7;0U%8F{tFtKo>M2Q(%kpY(!k)lHh@gNuYGhd%7B$lcLYyB&&t zr)X#T8#!eCesgd$-}}0zX-!Yddh?q>xwTNSyIjdNO(WJbL{IB|4GJ?kDz_a-O2O;7 ztEA;lUeZ^TMh#jBbB^~y#(wD78lp(d`g$CP689MnKbY@1WXD>;?{t94oSgiI8Rgle zUVG$AHqR_lW_TS?w=8Qb?$C04a#!8@$ShLgk%o!a8az&nohSXrdp&>OT;J?fB9=%) zjWED`Up7!#RRf}}Dm1IRf_=V{^JlaJ&tbFl#`9?D8Y1}b>STjwW8~}P;UGDW@y_#t z?#5D>>4yXDP1Ctp#OB3HR;JfDIR3DKFCh#~pZ1_qTzaseC(AY;Di;_eVJ(1xQ<+}W zsi)KJg{s9z3Tyusk$viYwHCI;FCLSg$p?37*8eCzee-a)Es=hyzmZ!XuAPTQzZC(M z4~m`K8+7|YmjdiS^FWpDpBu(xFZA<@LE7`k;BL*z6I7l-|LnwFOrlxhic4cL%LB@d z*2jn2HR5ON=(E*XRffL8D#4(%1!81(Y5MR3bFYx|0Z5-r!rS()t3U+X(3gW&h_b<> zMWv|3#Ui|eti}pK2;CS<{~J^JppoT$BXR1dc&UJhVjW7c;7BN~EQ#|%;-GJEtK_s@ zkg@meyBqJtHsbYr@J$#bN98VZ78F}kbCp<4lzZe_-m7$^Q z7;+uuWSIU#pkgSD6DNbx2HvPj4gE{#Y=xxv_;3$~JR?-)x%T~t>T4Y= z&(wFjxYGBPb?66r+8?jSIabBTI{JnJxMs;`7A9iGa<@Lve3S{tRyQ4hy{3XICq27_w%~=n&K^2L6@A#RVQ<1+@Q8aOviw2yE936TBx|b>5 z$!%R^9{_Z6+Ix;6+e!aoqk<}1Ps{GsR@%zJ{JGY3uP^jA^XvC4-_O1!Eeh@8ei^*8 zW4@{>PVWH`BAR0`b9gHZ=9Lv;Q9PoZIqiDiA3{)GZpv;(|T?i=C!!|7^asLZyimbI5Y1 zEO2ezoG#5?&l@#{jR!X2Q{C@h$@)MdAh-P#t2A>0AQr2DVd;z4C_a32 zB#}p4a{QUVTJuR>yo}+BOcG#YXM$rz}f9D(jMX}@wr&7cQ;~x31!iv7U(do@k^`yM+?8IMt+7bCxr^+%izu&85=$qG?k9Id4W;} zNLASN9|O?Q6Qekq73><<(6kyIa(K~zL^vVVB;7WpNv1(p`cAAB_U6UlfokEmgmkX= zgLsq-Y84r~?pKQ=H3z93e|=~}%XJzCm)uv_?c1*lbbYTkPuHV32c5^nm~0n*aa-{F z+RWF;-kk3-`CUhKK#2$m-$)r6d3zi%4lLKhpiE8+#3~c#WJ|_q@C~n&6IA=Rh#^W zzvYr>lG2QUc3KCW_b)F8B2>iV_ATUwPUeq5K5rVfy#GL7&bzPpFg-q8c5Jra9>!xk zqlgg&Unp|6F#v!|aAXcZE{XHC7U}5noO;clo86B%0G!MJd$~eSfnjAjbhxRDyPMwAO}imXcBYH8Umy)OKv7DCd#vqit{c+0fPj~Fb5 z5y}Sk+K?|EF$-_IUS-bOK&Fdc1B;oA7!^?==eK5PSaM4*#ync6Q&g6?cOmYdyI-8m9%W)?g{UXw>AyThDeUd9D z?`S~NH4t}P4~F6bl#vJd5!UfA1Ha<8gM)}ifePgN+-(F@Xr~va$dP!~x2>=J4mTc6kfj@Ed;Mxn|!?OUigRgEUjK+5k{=PgX3h4nLF>HcMF7bC; zf*ssO4?V;x3IHb_b^x+S;@RU7*Q5PDqz1AacDmOAj>W1gP4!JrE{px?BR^D!-KTi( zOp)pftdgWcpI;c62dRba^6R5ZO2$-(Uji0*jOURa7Dwa$N2@`f38oBvL1LH54P^fJ z%Fn^oQp|pW_SK&ung--9HuPpwtRQJ1p8`LXbOD-85s39f#TJNx?mtau_5VfF`M>qA zXP6r^r5cJYZU?5jlRuTSdqg~rf1Ck3USS(KM&hB7&vs$@4*Wt-B84Lfc}R_DB!!#) zUvu}dOoayLK#DwFPFbNEw3_qs-;AQ$!NTTQ1jq78d~wk?7R@aVz2=k-+2^;>*&8p~ z@jZuG_D}m3lC&9JK+7*TPqVRrTWi&tzXx`W>B)Mpa`rm92!UI^r3i-GB$bHUpTR^n zv*{vL<(01A-eI^Dj6k(|>vHj>6)&;sloQ-FQHsb61{pX?^3Ko&u3 zf3XLvl=wLo>U*_@4vX6RMR~g@7#qKnX*Q1d^Q3$uOb=_ z>1r#n)lzevsi~=gx_Z)7;n{mnQhM)dlOI;=8P20|FWJ;V(!O?RX}lKSii@wU+Q$o& z4|4g7=QbAjCB4xQRH7bLWEWfM_JcsJd~4uyp6GJ4EC#nRk!Cj^PT@X1Y&qIW=Y04l zIzB%ll|rK|#i)|yJ;~K+!7VWz+41<`)wGoc83t+kmZ2wHUvFoR2})d^E}GRzgqb%m|t}Tt*Ax^n8;+2&XRzi|PdD$bw!!DsR7Aj*L2ER_{5K#+LR8@tDh9F@CM5bla!V+ZN{b1!%b&jXr@SdEJyiw z>ORY~!@24_=+_QFOpvhZ?l|eA;PJU}neq~=C-xLyUZ4>D6c!JHGb4zu9E#PGI>oyl2V9zR#p-K>An*)5oT9 zId=aO``VmTo3P$)$vO!e(JjvFdA4(0wNM91AM=V2?ji6nImik$@@T{3rGWt}{-7itQlW#{``r=D&dTx}9s1wazRm=}F0$605OuBrTtu;be*%qKe09-O%&MQ0rH^V#f zx4H+RcU;R}oypf&?K>``pZ117+7*2o1^2<&#Av(9DCi%V+E|v@2uOc~1QfpH_jted zBYJu|fQnQ^6%cN^H`W#XIV*g39WLezz!Zq)9}c+z5SnK16p}3~KTNhSC8kLOQPf-| z8kN*`1#aj9&oD(qwyLdXO1$s6#Kpr-x+)DY;*hF^O(Rjx$p)t=ftnp|J{ungcQE=~|STApb6jOh0UkqfDYDsf=9E z&gQ|miSU$}ey}9FpTwYJSlCW7N7v0ceb#~d9+Q|cB?$cn5}$9tEHeA1fX22%a&Dui z@Jp%s+L|R!rB}`^C`q-yzb20#2@&~)y1}F0y<7BP15@}d z_F#BT-+GaYTg~?V`>Vb=WJiZ-?F_e~)SUY0%(wb;ClfgN{On3YdrCMWcJYkhVF#~eH)lg5 z*;}HGir-znP&h#ah8AjWR_`830khSJMhgHv!^T$cJH`EdBrn4PyS68E!+2fUqx7cw zF5XRX-bjlEMEel#5lw~HudMbAwTX}SdR^ym!G8zUEHdTK(d-jTQiAX)L5BMMw{7D- z@Ovt`Fm1e9`Lffwb*dFGigL%}!gbp&VRnp5?!I{O1^w&^2ESQwm9N2=_^k~>P=BJn znC6OORYns=l2&aJQCIoGO-GHWTQgk)Zs4>ZJ7<)~k1e|{PSLGZ2S^Ut8D1Rw{hAsS`$Oy1navkS>2oWm{_SI^u836Uzhp4J4Jb+;tO4m;SVQ`Y zK8tG!CZf_Od4VT4H|t5~&)F`pxN#1M>o)q*W#k|`z6smVvGp`&sdn>`G}TLr{h^e? z71vlxP@@TjmF+V#8B*r^ig_5y1Xf~JmYqJyU$20J@bjSazoE8-<)U`geey`Fb3aBN znr&Zvqng7vAImy8i-@82+K2p|OPVXtXQHp}5vI1fGA6_JR07i7Q|ua$YQYL*ov^Fz4ZNGl->bAK$GdqNQle^T{rJ-JWuD-g0Gs#P=sZ?viY)+!rWSChcy3?pgqi2Vps6=DM|PLpeS zy7&{AmIQV7tDbto)EF6*;o{RUil|S+qtQ<@tzST#0k7s$!1!gxuY>goF z45BZ+0?q@^O$2fGE~PIC^=J-H8+*xVZn-K%tgoxT9zd%5Lg=kdcy0 zq}0`ddpm@Cawp>5$z%Td zqsALLx|$RBz7YS|UT1X^G2R!CA(RhnLOwh=z+G$Jut%S`;EVQX)BFy59H$PL2c&fe zVlL|%Qhchu-+t#|j^hT8w}S5RHDXHr=t{&YIMvx?B~!A6Drv}~uavWE_gQuLzM-xCM^CPyqD+$FhG<*~Ugr{>=dC#y! zg12K|OAmuWph(`WTr|TfLk&=bxrKtw;ewc<9_=r}#%6skd>7F5&O%-3Tm4p$u1J0Q zZbW#I2kWd6C2k;wq?pBMh66E2GvKQ->zedrP79#&^rZ^BmRRvLppYRK^8c0jeReW_ z1VwD6UgtJ71HE^2e2!ee#?tzxXmg=zAEYX&kVi(ocKc*#YI7y&r8l#wm;GV1rpx`* z$wJYGk@|{VI|Uum(v1a|3>}|6mfcaW(4B8ZJ2?7+!ih7jPk8EvE0C4O9r&{D5zs$G ze0o~tO8sX&2s;4>HXcBW>Ao0N)3J_XR3mr(*QX7Wz>;XFb(02ESz?X5l$=L!8kT0JX$iSXtIjkuDr4EsHEDiuQB-WS>3HuOn?i#fU zd1doyJtkzt7S)OkWbhs(RlH0WIRY5-cX~3Tj5G6pGhq$AR6WP%Ipw3JJ(|rf#Qm2Xs~+>r0?M zbT9n;`vZ?Pe_6)yL1u@8`-W3_%{B)UYs^Q;r}*uc;lcgDUL5wq>9zpIEsU4PvLy@v zsA^VlK5vno+9Be$kAd!TdvVY`MK0`81P2Jn+9l~0$unO7@~w^KQd(JAeNRhc1w-g$MyE??;>NtDKU9O-@(4j6dzxU(1lxZ7SdBkR3UKi!X(8694| z)m%Me7Y%;(tlgwPyD~LTiCJgdfsV(p6KOkBAzi>WU%$lh8gjo_Z#G%y1p5>x?8*)&VdxO*C!ww;z zD1AVEQ`)Jso*CrR>PJ}%9HY}D_=CYPeMe;06HdsukL{^9({2ZGzu?ymF7&ydJ-izw zW`BmE6U;$KcnyfBVNNZ|FGu4;&JW`~SrWk=IWxk8kDogv88^{R!o?8a4%4nWBi$I; zgW)}M22KoKUC6AtDJqY(cZ7}I45Yj9Lf8)8=q>o|I)E6rtpehnQXtTs2X90>09 zTg33zrSZMlE%XsQ02KMG&aSKeeV<9_BF)basaU6y_YXWJH?-e&EkODQW zj+^Y|B2fXWSRHVny7>nH^%jQuNi<+Ll;YPuqwaWTkE-9>BXZ#g{*# zJn`xFGzM$f#aE>`+7vE)cgu~1J;0mJbZ6>z)#M8?5tWE6NhQyw;Omi5~wum zj=)YT1tg)#DkJHsGrR1MtIeQ_C1S!vfq<3Hx|6wOIQc1?(_=*wkCXlSq0~1RfIKre zK?M02qNwr=7({$li|)cG)^3fwBC}u(Vd}K31E@+G-vFQMMlcd0MHK4PKHi0i6o?nF zEw9S|X>Y8_fuxS2AS$*#*><)}Yc0M0jJMWeOu(=|B(N82?Y6>?e&>`ZBh}_5A`S~nCOE7UxC^>`O+o?4W zatfD`6i6ZHY(k!9lZ`_7K1PLL{h}tIO}DWI;5iTrj7_S%mj_7e2AiFLJu0L|U0nCZSw*&Ya_U*|TT2Zn|J}g4` z$}g_RE=T?Eq*|snJvtoeu*C_ren~0cbtH}w0Bz>`n`AZjJE%X%EY=)j+r56>nlQ^2_p+RI&7f<0vi>TBBavaLphz;l|GfE(Xhmajd|iW zgzdKO+fMq{kruk=MCBH^K^^};%OXO%Syf{i-zD&;hE_H-p+Qj3hJfz5~4b6 zaqnyRhtR)aj+=a#V5(DRS{h+Gp?iZDT9==eZ_nNi43raw`8wgru$VFMZHdRB0{O%R z00;&^SL8jS08D_PB=&fI$ZSeSsd~Ba?mO|P5hY5htgz^@^&j(j^7y+;E$$w30P#Rj zadB5BvT39lJp<22nt02&e5<}i0eHdIRVHpVaLLKr#p8OyRFr>BJPJaTLI8FGtJuKZGB*YZWR}K`-VlM!4u$$pV`K*c#j@J#AAI&Ih4Ky!h`& zeK^Ct8(1!nKyftn+vl9=bUb$c3b#qnqj(H-DM}B=*l07rw^A!s`{`{I3Ii-Mx8=<+ zqf5AR%-wM}{?_Blb(4qw)vu_Jyp7hnb)8r1m*F?pPQJs=i?)!5S zyY%MGz#hAARKi_)Z=(Hd{L3#W^Nps zxB_&?k@liBI8am?hu#7Bi^~YdVkVBqkNg41l0W5yD+1c4_ergnO_G%4kN&|6dI^{9 zq@{W-^_bb3=sM}4qIlDQY$?dkDH5It`~BF3&B^y$*l}G30Ton2uVbR@2~ulvGIQn$ zSgjjsbli~pB;9d$I+Q6M5=JYwo#EVN6w*^g#oN$Q*QN?~-*h<%e zEXx;cfsVO_6ywxfna5gb_MV{}Y<)oVamaomxA#4G%fA4Enw-R<*0khcJk{v+sY47LY^K>$4s^8~<2D$u=1$O5_L#tqv&Ii7h3~iB)K-8BPED zBwFe^kMM8#hifx+&H2RESJo=03$PnFGj*YY^HM^65F~SwQY1*>OTHH|Lu4+qCLzq)%~a9(jOBg^IzXo2xDCNIVrXDZYzs1l;k=rdJtesdW0E|1_27m$otfl zg*M#VkAO!)r{9mZFW74&Mx<(tO44`B^S8%`^X?Aulc%X}4c(5gc%9E+-9iOl33P3t z^4=eX6$r=fd!!sB*w^l1(ve0W#LA=2=ZiC#E2@|DVge%m2fF7OH z7Q$EY);qeV)ARRNToweytiGc2Cji`m9*z$SRbBjgMxiSxW*H=AOL;c&nR@R>lsWj8 zNi>DJYpIZ#YKR2bFv#M~vC#|X9`FE1}*>37*L zv)5ViSY-&nmX*IBW~*!^;=m4c2Ta?wgKq~>Lpa*z$Ik3&;mTAwly~?HvHf1DK{4|0kWZ7Q) zF30g7k_Dlpy9FG!o)=!vK7X_pvK`9%w2)@Rp&&EerVh4&Wo(1Hti( z(@E9kxssp6)6Sww-0)M}%7)NnWS+;}J&oW@u;iQmFQ`KEKZ7d&YXWCjgEUO)_4Z3Z z<=OonjT24Q$1Ib|W5&?e4a}~Df1os_;i)P@nb#O0jrl6}b_%w7)%%lwR0U*=|0aNN4e6sMjZ$GrB6+5cO0;CI|WhS%$8jlDoliqrL z8yg_Iq(n7%;>b;!2Rsi&pdXoBub8581LNhLMIgvKjEy58A;^GP zqtA4-E}zuYlu9|JKxFQ!rHxPia5zy6$y2cZ|8f5RuBZLiF95uYxbs8tx7)z)s+c6C?x z>FSF<_uO;ubIubcCnE+Aivd7ghiP0nPrtc%dP`-*o1VlzXL#bGnU{jSZUxWuB^ z6)d2rIT1A zOkg~oGrK3-D@B|PxCZnkmhRmQnyYcfP}{=|92ff#)u;P?-ICxE0XYr zy-z3$-fN~xc(mxbfp7U7n?{9%bH|xPN$!QtpM=%*^6tsk8xpgFV-hX_{ zvvu;rZ)=56zpE$=?RWU87zufpDqUDkdFTg9O>shg1HJRfYb=2kKC8gajkK#k{#+}B z_WiLTck-0$WeBDy%AFyNN={0lABb7_Xot_!&%=*nC(Asf^lWQ5JVePR`ZY`U zoiQ@Yl-yn6IU_w~%=~F7xiSGgh@;1p_)!p_dtVlX%F~TzVTQ@sj_Yxw)QE*qS|wCK z4iG80H)DS8k4cBvsY4GF#&d>_;htGw4UUXvg;Sv_bH%$HxyRR610Lu*9o@O#`?qbtBL0g?nccWa~n1PrS5tX+FCzV^40 z0#ZduF|p^DvkDTpUVK$Zl22C-Ho)ErO`P#*s4+f^z`%6DQ z5;>fJ3^iT|6f!au@w zomnoEk9l<*cEg?EBLB@ELgX-$z3h`omm1x!V2VHBm&*} z+tTFVC=B;@NwcPj4hEHqjA;x&N}U{h2!6%>Ha1qK4UpK}yKT)5mvVfT>Hf04r7pC6 zg(XHWW>V$L$kTQ!8T@&)?OM5=d z(Dq6mHJIL6P(SD%ocqz0ncqj42!Hp2`MVc$S#bjPfJoZQKI{mTFd#WrC<*Fp6PAJ# zCjdy4D83ywbR0Hx5)~F-RlWNq0Xy6BCqv7Y$k=e1NcR(nkvKVoQ#Cn@Nz~=SZJ7p1@}3Ds7I~+=@Rsp zRh54o57_E0-Y~e`n<9$upUGL@UMuT+oKo>R{jw<(8UC{FGHcd}78Ft-pSbf-urg_= zlg5eX-XZ3~1pb4IR}ot_-Ja#=%j?Lm(?kxBU{Z!pw9XuApu^yBs{Z9d;5V5p9S_dH zKYKhqf?SFmT&QHR|HBGNd>TAJ12 z(6FF~?HSjAmWis;b*=qIga{7YptPJwgX>#+O5e6k7@I9nLn@lE*#q;BU~HM0kyJeZ zHgE#ysvL?S9bggVia~F@p^pKb#NTJV8F63h>dhfrNikHJ5Gn?FY|}1Pz!kY$vv+($ zQJK+@eI@gAZq3!dU}$Zb-pLd#OhrbOCAQTPbjmtQ`e+INTF(aY=+P(2-~va*l7e*6 zCFxEqX0EN;@>o*iTrs8A5r_=QAyfM|38lTOSftVU+u4Q|j*gBFT|%<27`GoesS$Z$ zjQ@T`eznttMvkQvgN6VDk1BiWkQPAddgOWsJBA^35%iB!AhM|md@od?;@o@jq3H_O{RMyyo!JD)oZE`hQt|sYAyXP> z`Us9?2mvHgK|uD}%;^lkWSZ@EZ=b=Nf)d)FU_CkYw*V5jK{-=tfvsx zPy8)QQO3FA{h)9|pzErO_Fsd)b$ikzC^Lko`+9crzD-b7*LF zdEQ@4;TfCCrV)DL{{c2FcfF6@@f>KSzjp=mH?QD)#ix(SXe*h+Ovc!;mzckPUm@P2)#*nflltxG zliE4zLZGcr7?U}`iWW_g*s`#nz( z5MNAV=>2Jh95X9<(G#F`Po995dQC&bfl{;~XefBYu`^zd=daKz+=tajm^GTn)zTDyG{X-{c!)p(1xV zEkq>#cG7vycSITfzj}O9(2z}~tJhj` zmVuGEetjgkT9*?>8BXeqjJ6IKssVa=qB2*zbC|M_6&JgEi}exTHa`Q+=>W>yV^n9e zW~-m9?KOdQDOJV~9fCN{RGcLIPX`9)8>-T>TD_bWRD9`sd#(Xt^=U7Fv;2FOoZ@no ze9sX}SOqvhhbl*t2|C$oUx2n5pIh{NTi?AQZ_hpT&*!z)>jmp*W`s!jGD)>t^jdQ_ z5(qQWc|Y3In9Bn>D>{Q>BWX&e#qR#`Ou)TH(C3)g^KAuW$Ea0w8Al9+MG6zohA;IP z;5~)?enlXCkevmf31gu{PBvvMTyd}9V~QwN{TzH~Uaweh2_GS;FjH>#E-kj=P#fLF z?i+oNNu|!zjZ{&IbjNJ#DvKuV4e@QTnrubW^M(X`W05L0g80_^!EL7#Qf0Q#sMHDtpf6#aiPOQzcsft+EPcF6ho67KGth{ z<^b~%=H|;Y)ep+lVxEa7)ycw}EEHl&GXe(wqaZJ>)eRwW7KVcnRW+U`!)8(%%9Y^M{O_n0Zh+g zmZ}#Od;hFyDEKIIU$bzvBzz6CCS9^=i@qXUo~<;YBRQxyJK`SVo}SNpw_6nLS5c_5 z+!J|hzL+krkM~U}w8t8#9BlDd2G>+~4e2(APo=4DKK0O}pq>;G?x@F9RHxfy^#la4+>Q;eL>(MJ(U%o2xjo+}*tVllvuA(%T$@>$4u9&=^c(k#Zw- zh}O0GY4R9?L%rS+CNf58>UxE$1H!yNp4z)$g@2>c?U$c4RcqX*@w)jMnLa&XC;6yu z>1zC|x!B9|-HmH?H_1}%39jyw&%V^)-n1-zono2yEtT&QAs6FL?O|5*HyUeQ1%q>h z!K%z^C0e38;`L**v%V=kNrwEOIA<=Qg#?z#rcXvE7|i`7gr&DLPf{noeY5Uh-8NUH zc-Oz7Zmt+H>Ec~3x_Uip#4EfrfsF1<={shemuqR-7r^Y1)lMs!$@S*Y(G8gWDzp7m z_k@YE4)XDc<_E0B5$nxj4}vw0Jla=L4STBcztv}%P0VygZ3$WoFNEPqJqWV`{Re$G z^v#_qC(v4{BQCm>>E|A6+8{&5-!%p*_w-jC8-l{kOeSY{g;|~{LaHdocdDDDm%rAM z&d2ed(nAd>)dzy>K|D4;;iU!W532s0{ifNB0Df7fvs;1{UeB)#>O85-GzZCnvaC;z%;6f! zAj#vW}BDG{M|h{D?C(#?<}T}nwaHpN$c~VLmJhcTtrt< z!07z9DKw!~;Zk6A#O|IKR=vr)?&Q=cVrV%6WE)k!k#o5vy7U9#c}O{yD23DFop_Pq z^ZPWWZGw!m99c4e2xZMVaMa}U14VBdvN)MLU?OqZ?=uQZjJLOE#?9nVk~YZ+S)3zl zL2Z=x?DVuLZZYR@V4+u&58*OB`H``~jd9j8q(r!VBBn|oaI1yQi5%GRX?NL-_KH0@ z!yb#Vj=01z>0%_O^+zBcH!=CN#jzes7DwE*tj4U5wJ6pQC^>nL+{-f5dM*iK=Wb3r zo4^s&Vd20!pwX&!Wn?4rs+>0wg+(!$>_^5=P z{W#g-H&!j7*yL6IInnTu@BC02E&F>beh^jQ1k`6R~y zy(OGK;E)wu8E((0Fwt?XDhuZgFq?KC19JH!;(@RTvNLvs<>gBlOd(Nf-lcWs#``OQ zkyFNVfJN2l&D+PXgb&E|mtTE_9xUTVp{_t`lc&lfXA!fDi2t?wB z)b?!-9qqHD+ps>IstY{gm(sw#9lfee_8xw?fD!0;hzgRhjuM@s!r^Tf}yab?-ErDwR+yX(*bf1qs={iT8 zYxN8|eZg{H*H#x$`p+>FMOILaWj@gxK-Wp;k9K@i8g{m^`Pqf*E2!7U#+R|K6PQ^ zzZjxd_i3Kgd)b-i%zwmMImZKnF69jS)0O=F2+TWTYPP=O$NK7hv3^E-h;bhl+jvOr zxn{`L)x0pjHl9?vJ*p=^`wV?9#%adUr%Q>uhBOxp=r8;_fiEZ*_|fBC?(!Z+`82Ev zPXp{BzZ^t-xjsKG8!Otkb2ip8xHzbq^aC~T;slQTq`S2@quWW%In?$rgNFC6dAj#L z)m{Dhu%|BR{(90sBJYk zwPccDNqZ>iUoGN-&grSYRF*pZXT_MotN~*=c&B$hr8p!R(`db@Qv+zJS4!Cxo#AJP z<+zZTkcG^EHP)A)LZ4nf(5!HC2T?{)nuWP!!dRyZ6Eik)Wjre(p#5{S)I`|e=d7An zken!ogCtY!vD2~i2>UG~Jr!kqRVGVCc-Ydp-dc%AY4qIa&_FM9%r*!7n@K4A zo0$?_`!*g&9;4xC1qngrj<+Mkm*aw|W!(!r^9yP2OHExAhSi$JcrD%#?gnsij0Q@I zsScGSGrMt(q17}@109=QwgA%`5OL>GV|G)n7YH^!M3tKb-F_w)NJSd4D^D+Tx&_^( zI=cfzH!omnY(_Gx*&|Bo%@qXCSJ*!&M*rasj z*NiN*OQzvk^xgdyr5`x<^52dA@Fcq10o}*Cj8qr|_O&9?yKu!CN=k zdZ#*2ir=|^FW}y=;?wG!A%bSGc)4Z;*?%Z|R8Y8DbDUAGJr_QLE2k)`H|~PKg z?B2&#VXSXehLn0^c~Y*ZNBg8Q1Cje2%<$g^(@&Kj6g|qbzTJ+VS^Tf6-8}EB6s_xW z{vS(qRybOX@eh1jfg;3&173|d8zZzfsitEB&A7UQqZ54kMe+es;Ie&$ynNd^+jo3P zT@gdA%ueupu3m(lM_t0Q04`HoJ+XHCvExQY7>c;wr3BHc{74c{~4pq7q{BI zhPcT*%8(f6r{vaPcB_0DxkJ_djAeWJyV2J`r$==EQsHK&mkP!uJI#P3meiQ~9Xwl~ zgGA_dND4nHo@=#WLtiIE7B@`@SO4hM|6$;a&AZwUSNJ63*aYKmi;WyS&n)Gq!|nI{ z=P4alqXNR~pX_j=X!&`;Uq0sfUb(pY-{bbapf}UH7y-xTDBwG+O7Be}>^oW`cYLWa z>2dhxpqzjt)Qs*XFy` z=pSx{Rl%;Oy(Ghk22G@{xc(wHd{d^(!uz&kne}cb31c!D14P=GAL6MF#c?J{!Jm`~ zmr{L4_9$MacILywQhjHqUD9xjX%4E410N(dABWPLRi}pU6Ox6KUST=B%8|)&Mz%-y z2xQH8=$!>!#sr;?6d7GlSbT1KbT4B$-YHIVii5yF_sqHQT3sr8YAK&XC)TN2De~mC z*+P1QIZ1Agg>dG<9FjM!X6v*dt4h9TaykK5rSJIml;JER6Lgza;N_kM3#N<$kGA6w zr~#qmVv=4IV$>1kiY+H|OZzJsJ?EoWv|{~^qqyT1EhI-4g$T0r1FlOfK>0mN`JB@^ ziJf?KJ9?y$WA-{aAeyk?PUqe}h;RDM&^PjIuMvB$4pYc12IJ)9l#c?zLo$XBsTh z4jT!dvJHn!$m|9{BMqmt`@+>ZmhUM)nmm@hYtsmj7yW}Ob$G!_2!l&5>Cq=5My#Ki zJT#RQ7;*d87XoLgR%*RriWV^@k974kKJc2gYxf$Twil=L_FTqBfQx$-D+4AWHOP?D zCURYd91anRGh?uycU;fgYTLAX;8k9fKUJs;$MwF~GOg7+TBvp$_KQwt7c%}U!%HS? z3d;|FtD*L4)#v!mz#^*OifQ_Q+C!sw3+IMCOW$#h3Uv-H^oY}fx>M$x)*?rrmMqSq z)F!%#;yqdX*cyp-klEVyvf3TpU{&{28sj3qcp>J|z*UnNlO8 z57ab3fr@^Z_NjW0A+vQDIy@WLIUJ4Sn2ipuww(hb_Wpn!`-8Xw&t3vX(Zo+gfc&RV1JYI6x3m92qJebdNA67yEYwh}IF^yUUb` zeo&5zL^x^A#g2&cm+Pl{vWcUjt4YAsu`(@*rIO|P`1&>>@0%CThaHW3r++?8O9eaK zNw|805yY$F+Vh;rhA^}Hy#m}y9I&lB!5}=LHA9pP7o_P6SvQ9&cYJ<`>?*6bLSos( zN>9;^&tvP0CMwVHko%1>Vj(!hfl)4YD5{3SfMJR^tuN`q$52zu zKXO3RjbW|NPy6qs?7UiiT5CD-Mr$YY#yqpaNe!Qbl^cbK(^cJtK1OWqgkA0#uRu4f zy*Vj@1D@y`tFyO45aWZHciSrm9QtGZQ3ItuM}?cy1M}?U!}cP2{b?)7akO{u)!C2z zN{^%>5(=d!ND6iOtbZ$fbC_V#GlD7y&UGIXPbZOPbpY%z00?TnJVe2Wukd-XA!P{y z6`eF>8Yb&)62RH>21ZODA=&c_v)ClNxWY+T{rFD(W}VXVi0=Rq->HB?qc37>WUy(j zZ+H{P!GaRP5VPM)eD3y^a}wpx>%)?flEMD4a`n=v+FNJ(Q*%*i`sM~=_l6xS3KE7M zbs~>`m#C7B0Q5VtH9oyi-v9(;*>KG)IL8=zkYaMuu|UO4@5}lU5dwep>iv`J_iyE- zJ?9C|U@lflj-6i!=F&4>?On08Mig5UDiuDtyhnO!w+#W6hMa8lZd;eiY~}enmmLDrq2- z>c8l%@EsUSWKiVBGW6}sZQQ|br2z&OW2;!*#J=~KR1v0VCs$VO2QEh`H$sj<{RTqV>l zcn8>0`&1(qe3-Dl(obtwH{hcbPMh_lr&D-OEj-4(eD6-&R($Q0);RN){9~3KiU`U4 zN|5TqwtbAuB54VVX0Bf!l*LKZ4+TcF2#ISP9)4*XxX@XLq9p$G03~%2JlWitQG%5+ zSE4kkqcypE_s00JvWAG8Y_2Jcj9*_v?%&mzz&8oW){|6#g>*rw=J*e)i)S+g zjUMUjCDBl&rn3*3tWE7K7%Qgk?TsE-xTGR4qH=||NAN-bGEktbwRq9?9Wzh7wW=*x z%7WocD6=CL9N`vH-9r-gb>*6zP3hpF+vMudl;Y>P41~H`_ z#2%aBOGp%8*s@#QJ3i!s)EC5d2&1IbK$Q$->le0HWPochGR_^Josv#Y7}-|XQxD(T zAB3BfDkRq~C&;&JM}F}7OXNN}Y`&-X98OrN*3#(!TfOcZP)05RLUuC;xaExp%#3pZ zE%A(bHvhu~&?3YLZ}{7ljR(O`+LtE!RrHz%t4z9(CR4dK0itsIbCvEWdHSABHpc)(frm(j?0Yb2*D2~u#3n5BML4%og!j@%m+%O_}b#S_{ z9d*ly#UM;9tQ^DW{MGFc++W0YN%=Kc(QE;{*Tzq_`njv3QR~{nEiBwLu-;sX?c&>|K{Q8=> ztbXkeyW{oD4i&#)3U*vOPV*Y&X2z4WljrtmWC5Z+LjoMQsFs#1S{XwyQlZE{w*55a znc+;t5__9Z9OfIt5e||ZGb(+hLzniPX(zl|VvrW9E9rqVTcb@I$?;?4Q#W%uXrL5m zCZael6MqH_F?1mJk_#Xj&WFz0;|PjnUAD{Ywst2te8K&iInx6ghJh&@TRwNQuW)1| zJgU|mA12Pb^bfY11zIr;4aUcqv(piH)dfq|L{jXTzuPR@qBE&cuY_w0Nt|k&WN<&4%7NbHQRXbK{t{ z3&n$&cf}E@mt*A0Xl4q55v2+>lir`m3@&ztQYE{^D!yJurpfjoB-ybY?%SkI0FI3k ztRE1S`tP0$q^OAJSzQNdYb$V4h{_QQ2!>-;i2N84zRXV=Z-n1=hLDc~j!6w&*`t60 zB#{rrL$8}8dba;NtOI|F~Nt0{lKFGz6=zefikC3i|)5aq71~8$V$rC zA&|nv!mEvI&~w_VZ7^%&=6>yBYT(Msr7^rJ#aqhYV6j!_wcD~_5a>(v5`noxIjD^H z15^ovXRCK}_4AYpRHf(oiWpoT8`Wlu3gTHly%h96T=O0|W$M25g&6RqHXy%Up> zKisy-GDI;m0}lIE&cucydJU!e_f?Dim{&^`*Ak3}Gyqak5Epc2%g2ZWr=%G6oy49v zyV>CAns5>>ZQpc;VG&`>>epd`U|QVRCrdN!oY==)+B35cH#*I&I21(A|6$u8go1b> z1pwgG@6>sDhDT6n0uIn(el;Z1Ypaf}M6;VyOGxPsN9OZR>Kx-fzyY6~{~M}2hgh?# zyaSCQ;xs$X$d)Mt?LQkO%O1DD#jbnEh%l4~R)r=9KCe$v85}y3y<}DL^Sfzv%|0XD zU)r4kNuhmt1xJQk+nnw-(@3ks#rWv6_WQ(-keP)KE<4fdp*?A{W6Do|SWen&V}vke z>raM~#Ba#K;NM>MQ+F}~=xFuk z)`?_%><)@)*{p(5Jf;@TXhMYFFryzW_4laK?%me4$}0*~vH#&hNvn{W>7ZYd6R6wJ z252VkjqDv*F5mKmb9Xjsw}{XEOT-51VT>eBpipHS2B?D}_jgpyTW$?NY?Y>C>Pmub zB+1^cR%mCgb_G02K{2SpgHf6?G6L@qF1QFvjN`l5CCcH}Us9OzfX-Jz1OQ{+r%KdS7xdQCq zl2RrGj7O)jgN25cc9>_ptRus99u$4fapo2~`(4IfkFB_5og5HOXtzoYt44-9UbL{e zLvg<9)14}GcgQMGcl~50uhx71L+@eW_QsT(7i&-U9T&o!E z6pQ+a$66tz*b$8zp~Zf_qbcQ$T^Sc}3M)donvjhvoIa*Qv{EvU6_sQvsOQph2S*EG zSJVGVZnv!lLJ6ga0Q346NM|}3e=jJ?dVNR47(xKCwaJzGWfwTQIA}mI*Z0uhF}68C zHLC2x0TLO=)2BLvW3f{I>lUPM<0AVHvG;<1gTgCe)r|k0>-GNo?VH5XR*AHpZ)k6dv6|F>CjZp66E*32dEPp0hjd{6-O z!PGUn&kOZ|idiO|Vcq>g&dq84x!Bh~vs<;h!Ks&%({(W)-S6Z*5`*}$G+=xsiCRky zXr}^8Djga7H>BbSq5dl~Vi|0sS)#B#E**UBYD}&wlwB$k`e#Uys zfStW8t}>kQUUh0_*XR9F1}P9*co%a}ttA~jyR3g*bF8eUYY>I$&)6^_1rAQ-^HqBW z)v~J#yjWY!hpdqC0c>(1?$Pc;KNJ60SU@9ptQsEUn9}cR_a9S3M1$-1a2+bVpN8~|I*7>cp$-}##qZ9sJ`-heI z$N;rYXX33E*K146<7(eIk3FWYprrc%r3xK+kJ(&2^viKI-}q#JDzs6}t{9zE!9l`S z?OzhmVZ$odD0HgTl}EaEGpuGK3M0sjY+dO*9m${o z`QY4EnVani4b|Y^beFd8u_0Qy9g&5Yy20YiH=xcPG2Cg8WeRccTJ1`~8yTrorgAk_ z^mp0h0@8tK+?u4!e!P@)9QbDzWvtYCl4wg`ISg7Du9inpkDnZkU&fe11LW%sSvb~KNfHWb`yA?9K6m#lbr)I2opf>oY z{GhQ};+cuHb(e-Px*)Wa~HAq^uTr+uGgbm}P$R-7z;5 zVL5mIg^y{B}^VR;IJ>kO~Q^cTB90NzAz7A@qquptDe^;0gxr=CkUiS+U3RZYojSY zTsSr@$SW&{_*6!L-iU{q3I+)W$`SC>GuAiA5)~CjQcH7@A!g>46(lTvV*nQ6+7#8; zg4Sj&|H-={(_=h>W2>U-GW>YO+8Bp@lGT^jy(}Ca)jl*;j1_fJ^>o3z8)D{`w_`Kz z$ui@h$X%j%6aoKVFq+@Av=Pb=RXY2;GN0Y4oJ8^+aU8TDS9Wm%45ISVKrlj7FU}wl z1h_m}THVqDCdQehpZ?d<#2VZ}^Wv${$f60q!v2+xDza+QOA@|+V#5${((?tF1J20{M&cU6=dXh%okaoSAqVE*l zsW-l`B)`_!KmJQmVtE}3a}~TGc%8I7QcSKjoJ{E8wn5u?Q2wR>;XSF*D10+oF%hY6 z3RfM{R2maI#xd0<%+zSgQsIDsMBb1;@F4v=8=aNsrX9g$ebxeC_oBbSV4?ycE<&srA*(PX*cHn&}dxJ)X}H}7HbybCCTGDj)GK`INx2S1sEn)8Sb?y zSDsiMY$SNa>}Yy@6Oiq6QDUggN~txc-bbqnSMCP22K6O+zE2>-z{^jyrfstOd+me$ zpcr=Dzt|#P;$F{T2$OhLg_a+ltTqg`x1K+}7~n}YDF;8XCUrW+t)8e4=`=))_PICM zQ=4j$%HA*@HbyV-&HGnPJFxN!EnyrK zj`PP*_+g&IN&~{QzlmyVUXtA3f3Du}9A$?xSyfoF-=H@@m&$hM_K5$%qFRmDuB#{Q z8(#B`HDhT0jI1;MuC5W5jf|}ckrXrMKRN7gz7VHEXY!egNG(FjPDVLrxAn(^m|nO3 z5IBAEk~w`{7+cO;5R=anW z1cCo|nbVv@#VJksZfG-dmv?IB)Kf*pJERKD zEA!=ER*Tt)$)ktp{5P(7f5h1?FG6N_go(I6foH*gyQwEfT*>(HbP*f+?%S6)G?bw! zpW)AfZc*hSw_9)Q5!N-Y2$64A9(&D{%Ue*OW?RYc7#)+tR87eB#ITh_b$+Qqmn@T+d`3Mx*7ue(g$p~L)8YG+-J^)XRUOx+fA^xQ>-qy zH>Uq>sV>ofAjHeM0Y#yaq=vK25->}`Bzc#Z%JEQ*DP>k-l*uQ zi9;y7l5dR_SPF)bEo4i&OMC`|A0*Ez-U6j3-E!AV#eq9El9DHL=5isB>2;8h2~&;B zQTt3ZzxPxZng9@x3DL0k7R>Kf>M-yW6o|~C-wG7l2+R(VrP6rK-EG;sz~_0}1lt%a zN<{rsG^6lQRgK|cVDE|`LE2V5vUpw_#q=Gmp;a>YcGO?~U1AkGszpUwZ7GEXbvvIi zveP!vghK)z`N95Hn>}%^mK68aG{rvUzeFI*rXoHX+-J}9g;~c7UMR-?P{)Ip3K>*U zEPK18o;w8a^(s4rn|^M=%5`%J&5#M5Ef`PV#GpM_XF_PyrEN9sq#-TL5@6&U4rt0#xjRun#j z@!EeKDZ?#=Y|J33v!g93BJ=_YJUo|qZ3y6%gID2}Te_gw!%MA(?V z9U5}eB~Pmw3j&21vMm7)nrv+reeffsuRfd^pUqgeF0Wl_Lao^Gc3*ER_ik9JuXI_a zN+4;+=lWV1IDu8j_Aw!eh*;uQc$6(`Nf89xe^4a2-O`vY^-++MxtR_cA`LkjQyr0h zk{sJ0+8^@AcELnaB&V?4`H|saNb!CZRVIEl^-Pm~=!@aCytdczY{lw~SZB{cIzu@MhZ-x++J4KYGKnBdgW1D>d+dZD_hlO8~V{R54l_0=s_aL}E!SyIS^2Na{$4(KYw z*M^fr!HAwuc&d?3y>hh1vSR#Pfs}%()~`3-&alt9+_)06{98nLZ3sGZH?geEwiI5D z{O@tbXqHwiP651A&s^Ca)nCMJG1y*k z(5^t9Wm!WR$eEpixh;`b`Qx>10^T@vTv9&;C10*??4h`wh7k#2W3EsgbCM>8;6ln{ zS?z(}z#&!Ytu8HaJ!GlU0fQ!~%H5Po(rh(TUln{0Uc^o26ZN@gU2LDXgjCl*^e^uQ zW|%%am22?)pdDB=Uvesv~FSPQVUTMRXZpv`c|b{ynirrBSASRI~j`# z&Wwec<&TpD-Bla7h`F)J zQmC)4**c_(05l>@8lRO$(d}NlQrLd z6r~OG@7|)nzebGhNlP-ZvPAD#{!BB`Pq5KZ<-D5ey(Il5&6{`+{HvU2v8xPjpY5xKE|v7?L?K-Vnn< z|9+$h_4oGV8R?m>zrpxvIgywWYA0mbQozRd3|CM!jKsiJZ&-@zn&n0+JZA8{P=` zW`F*y>!$!UZxvY&bhi^+Vs<;bej+!w*3>(>aay&1@^y7!v~cVfa&SMKLNanW-(C)x zKu2lRToXu>-@t)$f3E^{q5hO+3|LTKHTRGlw?J_1;-G_{WS`hyez_*STdkUN`3&Cb z+72HsoFLwAwJq_9J+Nz7aJTs77SC5q#uM0S?=O{)+Zijbd zxO9C%@vP-lsG4X{#l=vGE>F~INqV?R+fRLpIMk0UTZ>LM;L_pQ$Rv@YPw;n~-^nD< zl-jR^$5k%eRA_SA6Q@zsG=qom$V^o6S5u`r06%?1F(lVY-$pWkoIe~CcO2mde9pVF4Urp0 z&EB&UKqE5t{WPoiMYi@ubk6%THY6oVBpMk*`~ZAq6Qi92&L!`^n2k~F;OX-h3XBhC zKI4-LeRza4?uPk9>$1-aOteA4&}j|ek8f(GNLAjx8eFz!>~P%tkGj(i;9((DA#9*~ zkq89BH@V_a%aZ$xJLc!?FN$q!YD}J-U7W8r)+c4AF4xaD*FBy#J3F(|E>+%s8<7i< zDdhixKgQ)p3J(0MT&v9gQa=6$1_Faj05k0??d#8~YCiWY{f&KK*y?lME@YRTfvC_9*u+r@FM zX1(TP_oYj{8s8_Zmm=xJN2#8YR_U~Qbgf6&axIYAL@xh2t0-u4p*gu>Y9yb)r7IS~e&&t~pPt*OI>Tsy00A^fZF+)WaX;tAFUKhJl-_7ZufXH5~9@{se%f7!Y zC*v1M3S)6N=dOB?_n`5ogB^6d;E6I@Tvc~Nw2+qe28?%idtz4jJku(H#)$T9p-<%N z6%zOO&`=)IS(w)j1am2Cdhvb=EuejlWv*z=o#0ve3wp78J4ansK`?*J`}&8kUkx;W zFpgBYxCur{4k{uMe&T!Bl&_eUuZH1M8rU{xKPR|&A12f=$2wO+KgFc%HH!~-;_ZIP zNqTm}u8Xab0`1)xL~+4qNp{l*trdw{*t2rgWuHUFFj_v_{oRHz87D=jvnz;s%Al9J z)dfOWgS(oeA`!*FY*EM1l>uE^;$vH=xr4o_qZL4|UmYwkv6jT9IK7)U#~CZ6MeR|I z7w&DFYJ%E0rdm9Jk9yF%;j%7=E^d%PDFu}Emi4|FquPe--$gkX1elS>Q3I8pOz6NG zl0&nD)3N}iW`w*`M$w$Q!Q4NOJ#&GWoUYQDdC;Fs+S>|ve2H#E752(N*{^?eNl z|Lkf>_0iOf;Ne_9`FH+wb45k<^0wm8QtngRBW8Wo|FXqpw`o^~(VT<3Q+$cODO3Mh z@9>^4j~Ly}<31;=U;*a#W$iyvu1D=bHL~WxXV#9G=e=72X#2eiv_8e3Ymgqee*uQ` zcK5ZJW88H~+&*p`UV+lVbaRYlVaJg8-{A^ZH|gVMy5HjSqFu~HpyGADuxT6ds3$%A zRX?D8vF8I|yIH6jRBTQ##J~!lbT)>Zxl|v)y?J~8BEb=jyI(f0)Z0k5BFFzo9@E3s zAQB$(u0Vr9NxDB~wmplGr_e(PD#nWbD<)7@QtbwR2fJt1vX41Ojb3miL6V1l6z`oM zwHO+9K5OY-jRVMS9l9C~d}A&8H^(+DD(f>gA3PzC)<)Hd(=|0JA?x$gD?5Y>v#}r z;Xc!qOEucDw+bZZY}Bx3t}u5pU4WjklXk$uX*h~aH9c6tr@{IJ&gR~X_vqL~eli?M zJSf>UU1`G_r<$IniU=rxp-m8`-hjktr^Zvr5;`>7LuIIb4fKhR;bxUBGn5?wPBy!5 zJ@|YegKa~TK0fX(8sE1DMCP~jG}6GH{)Y>Ik@=soXxb5eSxD1_)sc&#oWI)-dTC9> zBV4UXVzxOAXAUj55IS5M5%XNIKatJ6YWz;l2p)GEHYCngy$#JSCF(q2Ff;a0Vusnb z@eEtZvswO{Q1(nfm_?4SM2;FHm=*ep#ckJyT(b2ZgBj^}^SIf>a=m9FAH6Dg|M(Kv zFb!cD=~1-l5SM=i$~4EQ3*hFLl_!-PCdT9GhM%mawB$cA{4BQJD#mf8G=FR;JufoL z!pbY@4jNtkNYGHJ=zKa^)~_G`BBvd6xS%A%m8q82KX0;tfqB;*5TSNzfBADLrGLw# zGs!>~-TeVM)LHc*cJ>;}@gySRbB_EngeM+PDhq@BHANPhbGX6l+&F^DduIg;)n%;f zhSHPaADvuSAB9?R#IAl4+)u55%69381glZ$R*{-DD|O&UKu(KwreCQmCwS2{1}6bT zr6C8nTD`SILlmdoi92W?&M)iCnzKEy@MdT61l(Py+Gz03JBBST(AARGrG&@)ZZ=1Jo;G~T#7H16*1?(Xi;IE}lzySux4S*h3i(o-%d>Ielruh75sA}s3PlL zGEbi8Jm;4i6rsHU3YoS<)ddVFxp$@->z9m>xNI0)&KD+B7svf`bCxHzpIXudyDcI^ zN}46SguBr!!~$;d7I&%*oxOgQ-F+FbcJI}VSNj9FYPH3aBvMt!(nZ)qi36P_EpgUy zp(2KXZ|7xeO(7oJ_FuDG4LMFe4`{4C9e7o!!=9Ij+nMLxFXxn$e(#FpS;xG`M|Qa? zkmt1XJ^FZU$ra8~A4-Km?P~-~@l>Txp{(;_GE~2Qb(A#J`m7?9n7p1VrSKljjQIis zNpoxKo8>>fs1+sVw+ElGFY>074Pa(0XLiQpQJ~l)GkB>1hxY*>L07YUE;0k`GtW~{ zQPA>0yyCbiE$=te`#26k^TN`czFSrM!c@uHkkXNer{8eB2LVbpZzUA@31b1d4xCxO z-Zj5Q5~0Zv)N3tqJxdg4C=- zFWP|)S1-8JRd)?3wYH~E*H)Gn*`H|eS?`z+G$mwR(cr1lWX=d4ga7^*1FA^h@l%B6 zG}uiqD8e6Vi&Uk?FcA|vSxS(H`xg6RL#U*tH}Xx-AHf~68Ihs~o|pa9K$-WFo zxIC&z=dn8op7{>uui0z~gDc1Sxg&iBazhq!L)Nt$s4lpdx0{zY^UKOY;1bRgBaUd7 z@%iEol-cHU%c;Wi5yM2ogo#Sasr;wF?q)!zoe{5{frUi){9=Iq=9}3kP)n0*oz;wn z=0sRB!gUkAMh1>}B*yI*-M_BjUr28MBti>P(Ek87s)Tp`Ii`z~p#S5TZuF@1Y?2Aq ztr4r4CmgTGXwyUtgfhI}p99_`cJ4L?(je^Ym0n^AWHbi*QUYiMzyFEZfX~^c>wZY{ zCvPsBv}O#;C5G-7Dw#E}O`VM4#EeNIXS_Li@Mv5kZ)PNi?#IWB5)IIufMT_fGu=T_ zA_3l%Sc5=a=X$Gp6&H&2oqKr z;${vcCB!7fB4VGJ#*q-xRRkRg2G#!YjPIfd?6$_kmOJ3ehB9cL9Te1vSUe}R9Tkhu zufNgzfPme8;ceyzk&$By?&eiBH^^SSFHQIbDcvs~>CIJj0ytCuSPv;4*0nItUca6xT$=^oJlHLq4`Xx}-(yqN;oej@?q zCK-aFf*%2klKGp#g>12z+2NEtC=z!3$$gnwLBzkHS|sEM(DJsK1|#iLS$f>%2)&q5?jU`hKK7lyjHJN{E0C@+pK`m` zk`jw!xqJ6Akck0Jh<#beZeVYYtOBHK+}K{IalBua? zAo)m@qviqMB6kV?er}2j@!o%Y1cn6Hf(r?gV3HyVMpF(#dBF`0#cY=*4t6$0*aus! zeDv{Fzt{#wwU=W@Sl~Ljd&pp>jk!*&L2pH4{YAjpEoTb~qO&v6C`Xey<9atP5?rp+ zYxwX&&hUMwiJ$)R+fM3G0GW(5d)@_DDBFIqjIybXK;9VFYAVXWBr#=~cc*%Tik?Z(H zS-+(jae5)<c$InagVNauqDoXKv1!aHpu4ix;} z;2Uw!j8xtBiJ!HR929ve%;Mss<-+1&47n6;hTlKc!M&A>C~%JEz}FL{O{L?*N4P>k zbH|JZ5SkgsK@bZ7qctx#8zAL1aZV*-%XdyqR-_!P*rt@j`~=d{5TwYx*8l{zrcIlh z%Tjz_abO0MGcnAh%%--FEFcEdz@@>n8BCj6W?5%K)S{5!#h@t|h_G-WtG*Fq85{GH zmQ%oe)9r=I_18;+etf9oPv%JRa3Tj|!5|3?Ts0I0Hkh98+?TAEF(1{Py+9C4S=3Id zPlN{r6_TgU<%$_ff;mLuHWSZ{vT;2kY1f4oX>FSh>BFKU_=>m>r5Rg$CyPiAxh|R1hkZ2*s0t)?s+hLi5a0Y|c{nhzu1j8ivJ` zuvmBjQqF7&Ccyx(W^o=@7+JFfA6dBf*eKATq99HT=h-^ady8~0&-0wzNW?|!KE3n9 zvr%wgPELr53M#|W-v|xrHSIXdcC#&`eRL;bwBTXcYz)SV51rqw@uOy;GJXRW*#uJH z(0Dw+c$iELZEbojnhk%1NGOuTKX@Qpn>N-jj3wAX=xt~1o&rY~c*eaeZ6*BqyaAI{1oMSMhDOnE9Z$+`88jj)%sQgq| z2gc7hWIr>|?gLa079^|(_$d%bjp5=rhA}K<0`usB0zo(9axJUU7($~p++aWX+r`V{ z0eOURw4|GbSL6xKFmv-zzh*1u)&PnCmP~G*fFA(1TMvWStzf21?#4~o+kpvOXzt?@c&RXG{buTL)G}7@pGD(U}*X{3E!IF*fzjx>kTjTm-@8TwNK8| zmS;o>0N-JnizDi%-A8JRYB66utgNJV?=QYjUTib&z|PpL%IB-!8dqL!Vox_UX?!Bc z%_X{rZ`e*(oLkj5`eD6H(UC?kzh82;)y4Z0MUOK@6}j6W$~Ij6?PMTfRZZsVuGQZ6 z(0h*fy`R5bEQo8Kay#x_5h^VRtd&=k+5%+f?RkKUe_LWPcFkA*D_2=Xd48E3+`7Fl zGs$G8#GH;KywYup+wm%q^n3!vE(36uRW+t1PQlHVp())V=ICFCkQF5!I?c}j`)^M6 z>#*M(I*1W$OOn&5HQp9@AhQY6r*U?r=V$K0v*T7ZkBw7p;mWZtcIM(xv~lH&i6Ia| z{ubby$z_anstl2A7lk8QbBy;nRfS5d1%-uP1A|Z|L&n7w?1eSW?#rr1=9MKG=)ff9 zwnmiKs*IN|BXE^zaE(11&~s=u@?V!a&$S-V{5$s_YR8h3*S5hK_dYD|So%QxLj|S7 z3E@%6$NM*U9Y+*Vp{d|&#f8V8JWR_C1mogPFO&xyCL|+btFOb_RxeGeWuUV|wem}IBpEjYXoVTw?j zB62u^!}oSh?ml17oNUq67OU2ky0YRk=;rJV3ivUh&K41!#b=}^ggVw8yS$|vG~k`6 z=WCz*O=UdNtb5w2EXnKU4b8L%K;*v6yZa$z6I&n2rZ-(s)%oM?0-15&MHo`E)yQ0MKD;Opc2??>&ZZ=#CF z)uxN@UYQP>L|d|FviL_UNq9u!>c`!KT!603at3*J@(^L6$i6A6=qoPCj~(Hmo^TFN z4lT9`lG#Ebcvr{UUbW0~xNpcyn7`aSEO=ocX*6XpmWRMD_|^rWX~IaY)O+Z$D?~+! zgHAYGQo2$+FnkO^5dzPk3PZu5-ZSb%y?825M?_`kQNQg&8D?u^Hnb-AB@% zJ6Fpq!!2p&9li2@;gFp*;mE?aq@dX4XD4MIglwn)IXY4XooN?p+r|QwQ7Ab$Vj4~W zmbJ!f@9tT;U9BLO_lpQDqSP_XdcU6#Xv&L%j*>Wzqf*ChIv4DlQ7pcsa&%*YHKQwv z@eC-v^U70Z9aJ~!WWILHm!3H_SS0agH3lE~+LiT!g*DMedMWelr67#W??0{8HH7_f zNSRk8U8bW35)#k>XX7smzVF}6Jn-B@ee;1+Fd0>B_rh3n>Y`-2YEP3r1hd%G5{%e4 zWuC%WcPBuur?14n>_(%z!4&=nerB7lxE?xoe#0Gs+S3!Fyv)SPGub(s_TgIJQbvv@ zDVGzQ9Z`h~dCyyZL@jB~j~edr`t0pHnU3%Aew~L>&^Vu&Pru#6K-2q}>(N_Z-uGcQ zG$G4BSa7y#NO!YvXnkulDi~rb8=jXMMBuz$zV(>H{$U6((Q2usyeHIChL52kCFdCm zPa}VMefkRJa&w9+@i=35rp}m{v{<;@m@2ErhzkYVoO3re&EXJ+n|SA<_p zc7l7(GB(AyVPT!Gkd~K<&zhigY4Dx4Tg8_(>~o^4HQw~X^7X`F1JSHM{yE!q?BsLl zANf;jwbSy4P87aFUxBQ5^HPQ=OMES=o$>h^^F~Sc-h45^QrftBhXZ!|jNOVJJ{AWH zX(B0}@e>r(jfPo+#k%7Xokj7>x#=*=%2?Pzky`!!fZiPTEF#|%gszsB=m3=2iJA(i z_e18YOE|n8xXhfOu7?e#2n$y!^tAr!Uv^8MFDY7tGVylyJ?GC`0jSu@Hsc}QqXfM+ zm~n@Rjc3N`>XZEzD6P=c2=y;(hVBpin+P<%*GQGlMiZKkp3U*58J%i|`-6$b+tWum zb67v)jPYhRRYm15N|kN~m{3;-pD&I2tqg(55U#Q)5cAQr9jrz4ZFQN1ro^Zho{QHV z*bN1msh>4~$afYl*$(;0pi1rQ+|vR+djtNy*p9g6l>6JE>hF-XOm^W2mei51!NdzDN=fU(jTc(MKWA2jd?p76rNUTpN?SX)dMh&Vlr5>1 zR}q;uu(_a?wOI|vT;f?Q3A4#e5?8x-aZ>yK@F0?@`V`^eg3Ko)#|Vhg&T|WB!|`!J zI;`h9=#-p+zy@k)PGBlqaI5RzEfwwXEnp&XxSaI^(tBx_;vD zxI{fxvs#YF*t_>i3s`JL{Wg=NTQsAcKYUkf@gsje^^Ikjpd@UT^gplfmG} zEZh~OiR(-&wCqh}i^$xH$h5`G&SG6q7*f$=eK}2+&^G%1p;j})py;F+(ryn`xl)xt zgLmZ>g@u9=+rf1b1uA@cdN!09ABhxO8MYFTzpotYL04*gh!5l$f_~YlbS+Lwnq1Ih;o)3hy_$=)f^UqO&K4$0)f4HsKlLKI%S#+~6*^ z;`>7M;;u5V!&5lMy|*!Ggn6ItVAF9$)+19yyHOH`wW-zU^&qLFKXmkpZ%Sc9sQg&j z{3^JgGzU+52%47EqHr)0!Ef1BDvU-9TVZlNOu`Ld&sI*%! zu5)z&Sg8E|(%- zdkqx*&o6lpoAjSN^jKPrXupQ}(S06<9e=M+RRMA!wK5#)5+gaa5isnT_GyKhuXhU{>vF+8y@zhQ2uRB=0>ZxfE}<+DSR_h?{b=RWBf6 z)?XMfYfi0EnJW0rKU{JKonX!*8o_l5x!vSTagU_B?#(5{I=m_8K>qEV`^kf9a|=4} zYW{-b5)U3D&m$W5*Itv2UF;^4F!M~`u6N2!DS^z(7BtFDhy$BfKRNn{+_f!C;0(v4 zIp(AwOu#ICQ`unYn{!SB^?WkkLlD{oA>e|%qg(={hlq*{}21o z5HU~IIkfm`wE%xmZ*IVO6xWak)Xvy8KV9Vm1o|B$NY2Po()o>k%18jzQ*A^cNsKsQ zkIZ(etMH$qPnzH0z@haC3-lpRF*Vq!kBLFil%(HlY@>)!^?wEUg(@ z?iiDY`=(YjI=tLk=O>`V4NYrK;iD4M1jJ)*<7n^9s*5P%)J6|;pp1%46xz+{9Jk-j zYM7&bwO*%KGaEo7?7<9TR98)!4x3|_kVPMm%-=*|0A}~G2+9{_y^JiG(s-7`1V9hi z?~Z@w+jNSifPM~JuHnF{$78cOW{qK7!l*MpxkhcO0`va5ghcirGgTePAr4yjcLOdi z*9-lpSl+>kZaS`bSVvky@$@^N+$Jg3l4WQr0{=hjo7lPG4^pXlPG#K!eVpSo$`cv`+DPOj%T(fu$!?BzW+X4%gtn z#i)@Wj@%G(cDc;DQq<*$fEK4u`@_IvrX|T!9faIF3JCY{M$Z}DM1#_>;f6ka7w%`E~$)h@`#Le3X%I)w36)SwtVEXG4^gZUNBKX7CUol z6{Mh0_xgP>YBqTO@IQH%1SO>>^FivaR+0f#MUd=I#VhIv&@d>GI#lig7^5#6M2$6u z(9sYRwP<9&|Ij;BiEjSSiTHmB*@KP1 z?xq#=&I#=C%!J6n?>r?ffZfxr5XOrhV3MC89l1yb6l(v3)~%6=F#{?u*BODJycfh{?JM0BIgwuj<0%ufo-6dHb_9=%qpI<0bULLmJ6T^ zNvi1{f9iRnt)Y=Kn(OG1!rO>WYz$=E(h+KJZHL&6iEm}W2eAoH2n6;B*Fm+&j9m;` zT=XrLfFK`53f5Q7jp~`iXGyr}*!IUzXJIKSCfu~PU&lkHEkIbM?tX^reZ6}(r~Gz& z+ms@qbxXSDNr*|-0_flU-%yWzRi;?&M*rh2w~QyB8)BdPuH{wwU%RY+@-Yqc2yf=e zMg~iOZL@Rh@x(btwRCV(z9%YdGtD@%=LTcFUa9pX!;F)dmV*=mZd#yJX~!s0M)|Bm z3yNT0k&a3p=B?u__RWZPoDJ0|DmwCiKs_Lfflv=H*^+b_RniGfiI;V=@v{s6%hFp4%#?+z;_)8@e;2xFhd6T zr<>f8yKk6-WU(EuHC%li(9}?S#b5m&Dzh{EWNH}g53O8rE&5{uSS>InFdx+>|_>oQ{GUalCja+{`&Z_h|kxunG{V=MIRj>S{JO1e^t zuP{!ssNH;E+p6^H0nW=0pnCP_wT2Ije~}}fmwH6K7JXdx?&COpajlL_3>CVV6LZMh zb3?D>Tonoq0QVJuljlwHMnb*m0Kx~^(}b{bt-tL_r0n-dujjJ!@f7pkTTB81c=-+O zcf((=Z#Qu+QZhpQx{c8?6=n&&Aln{BkTPO?hlTa==*y6+4{xS3hYc?s5Y9Lc@w(}< zivBQ9b<-SG6Q-*d%*ZHJ&$L&j%RzXCe};8O!ylw5#aRiV);XFy>3()GG?qnZq^wWn zA+(2%-lA5n4u<=KH%cbvz7f6s&Kbc4%p6PP&g2F8jZTIXU}xx=n%a4W9h}Vzn@$id z+KwPs8h%YNId9fu0YAI@xl)Z5^Ap~bZW7eNw!N0$M56a07z0BF1Igv?C`|V z{$*XFmkYb+J575kwzQi-B)?-H3ng)xrPy-LvKLFP!*ApjU)Hds7RL@vf_v}BEAiV? zTYMKqN^CJHUzLfGcVt-c$v5vz9L(3BxE*Uo>}s8ZQV_iG_%Yx9L-knf5^m?>@NP<+ zct)}QDi5O|DES$eFEO01GP#`d9`4sF()s#|Q!m?W&th8OJg$oN9CJ5RV~H7RIeY#S z)n8vMm zeU|N3SCNQKR;4=Qw?=lof7=479&>38&2XDVV9Z;}EQ*+=JYz{5nviq^2n}U(z+AOnLM+r8B)FF z=vkeOBD3SE@%n|kyv1v4la+{~Cn| zwU@SWZVVO&`wK^gvF||MD?XznM8YiGmr%Dum% z8P;QGy?NJti#53pNAV`oxfknpfTQNciAbHj2%VH0^ZBvK8%1B6dl;qubbT?0-?KG; zjGgv+qsA-zGb5SY5mT%hNfu-Gqzh|a8F7h8i??=3x|c&Cs$=` zuyI~ub8_OS?%S0fPUQ+|gAdDojLT(?{uVMXDyN3}JGU%4B0fxsOTOZ69lK|U3rhP3&q!HF4f{D|~ zQn{U}2N{B*HS*IEO4PCch(KgLs^V)Yl0G3RA@a>~;AQ~G^*{-IYO{Z_{h_D{+^pEO zFU^!BByC7ZMSO|+U`Ao|6ZTUH6Dt@;LD~#+hX?#rZW|c9hlGP6O)O$|$}^RpSo;-P z-27HRIFuGxa!FC}RJjxS_ZpNqN7L1iA53ee8t!sUNsy3v+~efcZvVT)9vM&-iPwy( zCoM5daCt}=i|kM=?4b4oT{^hV-G1GxQ9l{#*3Aw)RHFgsUZw4N#tEJvxTY$Y>`R+5 z-h>L$R#!7;kEFZgQ}mjz0VSj?B%JrqgOjU5bc-;``QAPY$2*jM*Ix6adoGG-l^*j$ zdleuBzqznMYEU=iR}zhF;vKkk7T~p#Ik1+}Qrmv){kRa+n<`MeIjOQtBSx}1a8w(_j|Cmz>KGIHwI$8rrh&}<+V zy7NH(BdxF!?u1=32Go){oJfNrHAQwk$mn^XNVt)#{s=X{tv(~U%y|GcTRnf+iLa0y z2i{%!nlY`g5!GLZ_MM8tyUQ)Xajfab@0GG1opAeVmTn2?q$$C&32WWrFW zSIns}W?_wU&=V zI8a>emN*6kEVSZj^Zd1c9X{x@0WjbY0EZTNv>(4XQTJxw;k}<-{veTNyz+w>7lwK8 zFi8P%S){{L+ahAsk`N7g!~9IrA%0;y$g|Z_;B4i6xp+tVq{xa{61a=NwINc5JUw5I zi%a2ruS&2cqNIe!jAg+xoQ-pdgkit%AmIlpSU{7C1v`xc#86@5GUHX#=*oM%H9Icj zChc?-;9Qz)2aC6dO#$o~Op{s_jKFA8Z|JWeinq?ytxPl|R9Ukvr>E^f+}U+8*A4;s zdSGB6p$$@n=?jNvu{(QDJ`&LqsOf37eI0Q)77WVlF(X$^PDE1I{uUvOZ@O^QvCj!k z;hLv89*r{zlWYjm(@-3CDA9&oBJgt^p$?6>!Ud@FDNK4*?7oy`w-j0zO)&s2+!8kdsVyx z^S`3mo-J^cSf-^{h&fq|8(N$JK#~U!|5){fZvQ z^_U9aI@n*GC$-*sgmm}rYE)63XJ_6QmB$cbECMW?MOm|2@%x9CB$eyDmEK4HO1ouU zp4DlNYuP^A+o;}b1V+!+atuX*gm}q#vxAjy9F$4MIS~!Segfh6w#5~o_MLYmF zyD#wo{(0(8m^?VXQRR{5>jJ>ysR$p`sS2>um z;}X9VImM>a&kfigymMxfwbRMS&r}2~WVZ|N2CKPuwdQO!f2U;whN-(~*roKug^Ze} z-GtH88rlYf&5X_#Rl>9LLc?oY+9+P_LBr9qFgMprtm;+K0bv{236&iC#TWt*0$?L5 zkAqD0E$e!Ltp6YBwX^Gb-u4i_#NQF*n_A)$Qf@XV7hgN5-Y8ps9&>8lgH(s~Tz|Pn zmPG^kB>zWUJ$)@OL?}g6pjud}z+5*ibWKglhCLt?<{#GstW{F)(=JmI(-mY48(VW8~pdm`TxK4`oGHV z3^br1$d3?ofMLdC65=|+>C;Ouw|2FOVhW&JOvJGAA1M?{IC7DdBfsoF#LoYZvcdn$ z|MuUK{A@9CS)HMEz88k+i5U>&`w9n@pb{VvD-y4+f z7Fxh99~#h5Ck%(|fdb>?fhm=o7?+d$M*;|zJ-5S4^Jd3 z)x@|2Ls9sLdDlbmknx(`Fu%^?v2q@^5BHJ4~;doAXXVckxvd4{)$AHe69@219sn0+6e;F(82 z(i#`-0FoA5TwfM^QAUr{q!UMN=*w{x%kgh(a;Y`&_MbpA!a~AOe2E$ZITsSKftR;U zCSPqzL^TFbf9n?B!6aO*ACnF2o>y@WBv2N6Q9s#r3O5mih=j} zhPn&Hd@Nf{_$hn(r3sc%)!GD5&f?7CGYAnA*WaY29?#Gl^ubjM7*eS9!j%AGJ=Qf+ zr14{5x>pZ$~yHcalgGY?xn}v9kOK!qnOrNUN%@C8u|D(fF4g#9{%I=wm*m}S>^d~cXYC|~*7 zm3^%w=Dd+n80&U1QAu}QR!!^2;UNp0Y$Fo^)_26{ zGDKLUo0ppqTbqjnaS94Dnfa;J0d(_=lftGm=tCF63(_KEbJ3KDSq+Eq_AA2oY*kAv zC<5MZZ#Gk%!a!+mb9_unWue1=bjmP10X1C~zhr7tPHwt%MlN=6KZ;^IK=xxrj1 zoI6y_m(J~vy`JM`Ok~ptOv<%!eSK$QhG0l<{aXE59X>|T?%vVc(m})T1caN#lKF0% z&QK#bEkphyO9q?IJU;LDqjs9O@yme>YyV3}-HweScaaC;2fFJm$G8TsFL4cxw$yJM z4n6$xULJ)gWuQwq7@C_{*$xUf02%N-T%1EK`Yri;@E-PH$&r4Dr>&=FYw1Rlt4aQqMOKY7yDadu%QgJI+qN;hM$x7V$7-5VHi;5 ztt*O?Nmp&un(fK;@(@7$Mh6Lc*&Axp=HE>{OW`$ha~K8r&P1jU@j_+G@9f7E&xpKWm%HEz(WyH?vx4(U;Y}w#A?gx z`$PucD=thpfrQ= z$F*+3(d#Y<%3$9>)ki5(1=>-GIA>&;_bC|-b(|kwM{O`x!t@{l@8r?u(S?v?GjqKX)NF_pneOt}_&&4j6}%ZTJyQUZ-eI#XciG27 zVyii_F-gZ@R+=?_Xjb~0wr%oG0Bb6<&;nqnYBNr{;ueeWxWS#%aaOi7KHXHQ$&q5F ztLaB}k=J!6)y=&n8VGd{N_TX0f6S$@Ol@`wXsA0=_RprZjQK4ef%Z0WSgDQ|A`rcI zZF4x$jlwJ@fZN_bOMGgrUY~lbr}3v{|9i>bnmX6n!48FupP8MRlj-$#w~7{g=)7OJ zi^a}IG`iuN`9eS3?N#s(?1y+dg^^Phc% z$A5R^(KPnzwRTX#$bA~c#oMR$*tZ=Q(;gXr#({glb3OL;#7&Jq> zh`vfHBAL@M{(r-ZO+QUM-qe}zADv-=VM7)eHVSA7{d%aV^7SeNa52q?&#>WngW|CJO6ZYy zOHyhOkFnu7spxZOZ`6h$7D!3B%Mbp&5p58v5QXC8`nbl*vTh`JwQKSdbL-Zx0VTLp;!%Z}>@p&=5;HdvJT1^Jxyr##3I8}ki^6C!$FID!l?W+|pSI`4}f&x6oQJSW|kA z96(Dp{~wDY_U0>-tjN7QB~#eV1hY!+A>rk#Nxzl%B06{`KCLy|0WlJ+Z92k64wo_o zl-Po0XI(qgGc7ArRzPh#+Q^d$?RxZc^7g(9rth7@hst=VrOYx6`@Z*GhXvYRc& zubHa;$>WPXXAbVR_3MHA_3bS61*qZyQ4}|wUfRocB>Pv%bp1Ri z__4ClC))IW6~NY@l0Bl&mUc~KA_bCO)EmHj_&9+?>iaj~={|QKnl!#z+Jh=@V5O&} zNY*`^5|)=MZODyqwloF=9#I!Pu6Yrb&{fn$57Xc2-&3gey19jBQ$VmfxAg?|DZPe;Tj-CP#7l#y~P46d#VvN};g^?`(v zFFZ9FuNG53;bTK+rZCW-Qpey&-~=p{o|+V%ov@~QD4<@L^5sYwEU_2{ltkb%5 z$f77?1dbR2kC7?h$@{HEm*Y$HjByc7nffjCz+p+6`S6TZ?J2vXq;9uVBhViAeTt&` z!4fD^F36Sp)zI@@Tz0Wy2&1Lx-whB9&e^NorzMZ9w7#>r)rXY}e19{elcxJ+w8M97 z;Q0xyVZg64JIN`jjoAI<;c+fX;b{#XCqGLAT2>)0Ber9FZ-tGCZ)tc~fz@A0G`$!x z3(|a|m{0m0ToDlhll-`>CDle;@x7`iGhvh)SD9#?Dh$uYv`j5OtI3}-7R?WNIre;M z<`okPVw*3Llyhxg7~qek4PVSnp$~e->aYuM$MTj;Mh5Z7?Hg|0grQQaNots zyiVPWQY6-Uk;m&Un3Uqr(&Kk7U5gHN&2(7e1oar#h68i9Qqz2FD$MsmEYT1I`30A- z=$Yh*9@(MKIcQTD<{$g5o}_-7DAh)knhtrS#iySZyv;>|$vRbq+4E>A$N_`wav1cwtsohFkV zCK9OPs3B-mF}xjSe(*s1Od5aE=Jaz$Q1lCRwWGZ-GuM4Q>_gz`&q} z#6-#N%q&u->+!26b!o+UY0Y$BsnQTO6d)7Ymku5|dB7`aazfZg@lK(&k1LBNcRO*L zKXu{+5ilultG_RUU;haYXxf)~zi#K0)n)GYBe6Fo|8g{r4rbQ|XIrVez)o9JUq)Vm ze~9(oX9?-);vqJCUKgQ;VQGI$0Xno@f2V|A)rrV86WB(fa9D(SNjG=xp2amL@x z9eUjICw@{~0r@-|<7DmPKhv4I^oByPsM{4A`}~@aujIminf+#`1@pO3F1~-l$KctFP^zekHErm`+T;T z**L`tNFz=UnyG4vJSL&^UJRkki>;(0Q^y!uO-cRGH6U|#!Nal5zgs3Q5fRmDNsoTx zk>(6>kegEdGdOoD);1N;V;s35Ako>KI95sEuJihCEtRAXw`f3K*56haYV3gdkz*hC zuz+;eG4;V1eV!XLyPnLjx)S`$n6R}fa4-yB+kjto$Ci1wYg6d42Y5t)Bj$Q~l+E>6 zS=z32U{Ijc0IIY?9Aov<)FU?jL3-2g2CHrOHP1)7L=6k2v{1mq zzS-2NcyCQPw{Amtv{E#<+`_`0yc5&vTB1_ZFVsD38FDvk8(bSxKtC2Lc8R$dHt}zj z*@x1fxM6#+u^$r--#zFU1ShF_^^$r&66zXESuK_LuEsq4UC=h(OmmVb$T=x|DuQ?E zbf;^TawA_HzC2-VbHI-bw8BI4K$?w#=3D{QUSBj6xMi6p`?it~&uEl$9#h`_~m2IDty{=g&nLqy4`OOlJ zkhEWnT9T3X3h$8+*B==clb|l zg175{ccn0MJ%V=`tWYHke4BkMkeGSwp0#4T@Xk$!2KN{TzUuzv*-{FYW9;D6iX0an zuSRQJVjO2sY|`}k+~tI8nr8&SySu=F_fT?*N1y_;*#l#jpLWRQoznNEhuF1`<&pAO z?-jDler!npYxvvTLh7=oH3Qf4Gj0rR=?RE+c~3~p_ALb(U~iabeQhy{2bGtYvpkHP zmVguFvaQXlrx>z_lavwEK8Sc@r6IO^5Hff7-H0yv^*dQO?SMc!4no1Qz*NOR@Coqo zBbJZsySve{eb2MMehkr2aGeCrU>2$F0GI2Nlx@5u(?OS8$9d9>K0K0V#w?`98Y)SD z7O?qo*$~)wyL!pok*UpcH9+g~8J{s8eP&shDg;%g4r4<()Kdq(y~GGw4RW=#0)hLt zn#9~b8h-9ilOi9i)_t?<9*&theL#SKeC+GJ z5u00~@`!;wiD-9EToc(r7w>Q0y)dZR3?d#F7+AonxRx7gp5wR+f^mOxgd?CSr=;OY ze^{qVXjMUzk8v1x*}7m#DL_MA{E+&$qTCGq9W5QcTU0taz~L30INuNW>PwPX>sQk2 zu#u&m2jmg7WHzy^voF0nik_dgnIww`K{AvO7EtEl z8XsBm*9xX?of3QCswP__c$HmV4Jkp?`^fWFShLU3P*Q zS%hlivNeIzlO-w)%~iHrxIdmiJ??b=6%zPTW3_;h)16ZM?npOn-VGcJ_ixJm&(Fxk zJgs$7Lfxg??Xxd<5tWy|INxH9CI18YR-WN&XCiMZRU`QD4$Ta z`t~f)KW11aK8y}JS46j*t?MgshOacj?3z$$Fb5oJ!KPsc)Z+j)rDX^`Mi%c?-8CNfb*bmVzZ$SH$`JJ#3YuDK?+mm&Pax=2jrwiuA!3tn5^2gZgnojWr3EaWTce_<)n*VP z&8D6pI2r%O(8J0S(YRfU{2r4ifvQ79hv?@Z}PJJ3Njba@|Bq{6!J_G>+|{?KCfSGL0q&P-bwj$2zcC zpg?sceP^O|zyi~5Y?|P7a)PQszTv;fwZzZ!Jq*OD{5pNNhJoqO-&u!%=Et!!7@9AD z29JxeG3B>09hwTGydFFR{tDf9DLjjzJe~gLUyxyptfHS<-^iMekksA*G1WHuu?BM} zcxE2BJ@%_@mJL5Wz&bxe=$QiET#Hra%3jfd-Nw>;iL{?(cO!|Zk}s>h@Y&g3ScuS+ zFV?#yWx!%X^B0ZJ?3Ytux!j~HcuDdaqZN@UcAgA`Ik~5UumJ6Eqy$38E;A}sDF0O4 zw!wPvV=h2OBzZ`cui&!=4;I1eA-^m~M9xDR*JXpN(Z+AQ#K@z|n z>Z1v#NHM})Y?c279?)koNY@X?ZKtKUpNH1r-+d)E^K>S zXQb>lFnI5hmhePkL7eu~!pHzvYzjhZghErjgyM1W!H+p{2|!Qs?z5vD9ZtRea5S^w zD5L~M7wD>F=kRmf>HRJD72nSj)sOSZrcd#e$}dxFDds1=KVkpmJDiqKA*YtIbiwJ~ z{4@)YffR}L^;3I1LM6Gp1n*}yko?z!6h^1gHS06-)kv!eog_*Wu$^C;<}g7dG-E$X z!**x{^9>HNk1V8Dp2YTQBgK(2Chn|f$Rn_v=%?_x9-RIZl*8^K@SzI)6&9o>!EQ&F z1MdynGRbuHGk;3w{;bS}41Q+|gMbK!alYL|$(!%NggTZYDzCYoRn^OITRkoyuDX6R%L;7>H2Qb{jJG>4_BJWx zU;lS}xV(|)pV`Nm$12Fa`qSLArGUOypX874o;}hMSsVDkr?<0Y;ANh9?g){EX$gZF zq$NC)q4BhYClS+;!i4Qq=ckW_+4Fo1^AaapHmAR#JQ*HO`<`@Ak_wtkf)1L{_L8xU2M|e0$^(9$5bOcoSY8Y5DeI}Ug9DVoWwRMId9l4Ohl?(DBV5hjgB+2RG5h1dZQKB8z7x70{7H0 za95bhUyfX2IZqJTwwmGMLCSyBjV%WHx;3fD`&fn%+#|Pv1bSq11tsEbW z5l?804{9h@J8}*VxuFaT52t4=Q@_GK`gy47A%3;t*L-FDSCQy^sp(4$X~R={I!s?W z)r|<9%Ac%Z$0|T|=l>D%B$}s>ySP9%$OZ377OASwBg^BN3M4a)#_$qvm3Sj{K3L|o>(ufN6-AJ z`8yf(eSmV888B0^{Q<6%ukpKATTm)i;~74{U;4vHAkw{;=HTa8S@s5f?HZm9*J5sb zhC`hbDZOUatyZ3#k2u$7qRbPl9Ff?c z#>%XYykk==j*wN6N?uOMWwUW=5g;$@x=&flpjV>)+@o0YCXaJ5H`w$NA?j?4Pct z>&sEh{vhsM9b|oKC4+w(V(BjjvFIQMhsZl#!0fe*!qeKhhnqHiE{!{MhW3oJ%kN^%DmxptxiASyBCIji8DlifCF$1AJ?Y($ zCW;0~iI_2@)`iAbj((Mm`J4Fi(JvDh@u|I>vpBQXCbWg_wvxD?dK33-mxLzB|M>wf zR-N=gF7qF?XkJ88b z&iX9-k)d;L599vTfeW`!V2q+4jZrk?qk=!DW839J85m^A4+mLtf$v0jJ1ge=tS$rU zayH-pAu6++ShPW!-~2tjp(%%vv15N=>aA8Vnayb7#0+7TBohft7BfQFac1S=SrTT~ zOFw5UW+wmYd$@huRs83UTgm!u z_j9ZD*Sywn_L5mLkw7(@&YIPk%x03I1QG{Aj&kxq17lGM#29O6V02?GMdm{U&)Set zDXLpbUY?5&{{6%gDjFe6fGTI~fk?U{9aArZe!hc|6o4)e z0jM}!G3WXbW&)qNmdUe1=3HjP*&%ae`{3oa$l!`aCpLbPrj|{7Z0Qn|h6%J8-v!3T zv98R=%-|`HHwQVnW`cbIqz-|g6}w&GwEL!_ph)ffBN>(z)Krf1_Wyg5)@g?|QN&gE z-%U-)KKAv`*!+}}bT##I)!H?bxYuBBdxfDSmw&nSeNNiK)6o1H8|&{`)YJ&0z8)G| zUQ6j(lW9)5?EuIg&8F@@>gd_jL%1k5wU;B{?A>h6Q67oV1&$#r`z#x6poKgxIV|XHp4yev$)K=%?J2{MwiTwK8sg;M=*Eme9tBJwu zHc?mH#^J#j*0O7<&F$cDZw%xp9nC>Lc z{WHC!R=b&TE{Kf>0$F&ch5jE*5ZnI`RCT9+Ds+WQnAzvGci z*mFU4?c0%Z5;)U5Q%~m%xygBP?Yzn`Fff=x*q5?;>s{R9HX}71negL%_eFLc9Y@X&vpb52MSY(0+e>h)AOPvV{VRicO1mMAOo8)(1WXSn`K5cT!- zb86{ZuKU8Gq`%&OY$v<+J(cqH*;!fhT5dBiFff=a{y=6AxxZhS^Ne!Wz8!S*9$ZLU z&ZDFEAiMYfH0A3p7K(!S5gDEId-`Jpae{oF&-G?aPjI4bHx{#sD2a5x;O^W5JtFfcHfEAd1EO?$s&Bg`yw-s3r!%JR+JeC_Awl^T20*Tb%TJ819O zpYlBdu-omJ&E`4RYhYktFduYXCln4Fvz`=LFyR4Egv#2QZEUQ+J1qek_4RO|;aM76 zUQ5|~4W?YD?KT^VVmLn<7#Lh0bX_MLiJ/." + required: true + default: "TwiN/gatus" + ref: + description: "Branch, tag or SHA to checkout" + required: true + default: "master" +jobs: + build: + name: benchmark + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/setup-go@v5 + with: + go-version: 1.23.3 + repository: "${{ github.event.inputs.repository || 'TwiN/gatus' }}" + ref: "${{ github.event.inputs.ref || 'master' }}" + - uses: actions/checkout@v4 + - name: Benchmark + run: go test -bench=. ./storage/store diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 00000000..dbe4f192 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,42 @@ +name: labeler +on: + pull_request_target: + types: + - opened + issues: + types: + - opened +jobs: + labeler: + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + issues: write + pull-requests: write + steps: + - name: Label + continue-on-error: true + env: + TITLE: ${{ github.event.issue.title }}${{ github.event.pull_request.title }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.issue.number }}${{ github.event.pull_request.number }} + run: | + if [[ $TITLE == *"feat:"* || $TITLE == *"feat("* ]]; then + gh issue edit "$NUMBER" --add-label "feature" + elif [[ $TITLE == *"fix:"* || $TITLE == *"fix("* ]]; then + gh issue edit "$NUMBER" --add-label "bug" + fi + if [[ $TITLE == *"alerting"* || $TITLE == *"provider"* || $TITLE == *"alert"* ]]; then + gh issue edit "$NUMBER" --add-label "area/alerting" + fi + if [[ $TITLE == *"storage"* || $TITLE == *"postgres"* || $TITLE == *"sqlite"* ]]; then + gh issue edit "$NUMBER" --add-label "area/storage" + fi + if [[ $TITLE == *"security"* || $TITLE == *"oidc"* || $TITLE == *"oauth2"* ]]; then + gh issue edit "$NUMBER" --add-label "area/security" + fi + if [[ $TITLE == *"metric"* || $TITLE == *"prometheus"* ]]; then + gh issue edit "$NUMBER" --add-label "area/metrics" + fi + diff --git a/.github/workflows/publish-custom.yml b/.github/workflows/publish-custom.yml new file mode 100644 index 00000000..b42a5f40 --- /dev/null +++ b/.github/workflows/publish-custom.yml @@ -0,0 +1,39 @@ +name: publish-custom +run-name: "${{ inputs.tag }}" +on: + workflow_dispatch: + inputs: + tag: + description: Custom tag to publish +jobs: + publish-custom: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Get image repository + run: echo IMAGE_REPOSITORY=$(echo ${{ secrets.DOCKER_USERNAME }}/${{ github.event.repository.name }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV + - name: Login to Docker Registry + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_REPOSITORY }} + tags: | + type=raw,value=${{ inputs.tag }} + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + platforms: linux/amd64 + pull: true + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/publish-experimental.yml b/.github/workflows/publish-experimental.yml new file mode 100644 index 00000000..d89e5188 --- /dev/null +++ b/.github/workflows/publish-experimental.yml @@ -0,0 +1,34 @@ +name: publish-experimental +on: [workflow_dispatch] +jobs: + publish-experimental: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Get image repository + run: echo IMAGE_REPOSITORY=$(echo ${{ secrets.DOCKER_USERNAME }}/${{ github.event.repository.name }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV + - name: Login to Docker Registry + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_REPOSITORY }} + tags: | + type=raw,value=experimental + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + platforms: linux/amd64 + pull: true + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/publish-latest.yml b/.github/workflows/publish-latest.yml new file mode 100644 index 00000000..1141284f --- /dev/null +++ b/.github/workflows/publish-latest.yml @@ -0,0 +1,52 @@ +name: publish-latest +on: + workflow_run: + workflows: [test] + branches: [master] + types: [completed] +concurrency: + group: ${{ github.event.workflow_run.head_repository.full_name }}::${{ github.event.workflow_run.head_branch }}::${{ github.workflow }} + cancel-in-progress: true +jobs: + publish-latest: + runs-on: ubuntu-latest + if: ${{ (github.event.workflow_run.conclusion == 'success') && (github.event.workflow_run.head_repository.full_name == github.repository) }} + timeout-minutes: 90 + steps: + - uses: actions/checkout@v4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Get image repository + run: | + echo DOCKER_IMAGE_REPOSITORY=$(echo ${{ secrets.DOCKER_USERNAME }}/${{ github.event.repository.name }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV + echo GHCR_IMAGE_REPOSITORY=$(echo ghcr.io/${{ github.actor }}/${{ github.event.repository.name }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV + - name: Login to Docker Registry + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ${{ env.DOCKER_IMAGE_REPOSITORY }} + ${{ env.GHCR_IMAGE_REPOSITORY }} + tags: | + type=raw,value=latest + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + platforms: linux/amd64,linux/arm/v7,linux/arm64 + pull: true + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 00000000..15920441 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,51 @@ +name: publish-release +on: + release: + types: [published] +jobs: + publish-release: + name: publish-release + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Get image repository + run: | + echo DOCKER_IMAGE_REPOSITORY=$(echo ${{ secrets.DOCKER_USERNAME }}/${{ github.event.repository.name }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV + echo GHCR_IMAGE_REPOSITORY=$(echo ghcr.io/${{ github.actor }}/${{ github.event.repository.name }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV + - name: Get the release + run: echo RELEASE=${GITHUB_REF/refs\/tags\//} >> $GITHUB_ENV + - name: Login to Docker Registry + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ${{ env.DOCKER_IMAGE_REPOSITORY }} + ${{ env.GHCR_IMAGE_REPOSITORY }} + tags: | + type=raw,value=${{ env.RELEASE }} + type=raw,value=stable + type=raw,value=latest + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + platforms: linux/amd64,linux/arm/v7,linux/arm64 + pull: true + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/test-ui.yml b/.github/workflows/test-ui.yml new file mode 100644 index 00000000..95c2f0e3 --- /dev/null +++ b/.github/workflows/test-ui.yml @@ -0,0 +1,18 @@ +name: test-ui +on: + pull_request: + paths: + - 'web/**' + push: + branches: + - master + paths: + - 'web/**' +jobs: + test-ui: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - run: make frontend-install-dependencies + - run: make frontend-build \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..1d06dca9 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,34 @@ +name: test +on: + pull_request: + paths-ignore: + - '*.md' + - '.examples/**' + push: + branches: + - master + paths-ignore: + - '*.md' + - '.github/**' + - '.examples/**' +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/setup-go@v5 + with: + go-version: 1.23.3 + - uses: actions/checkout@v4 + - name: Build binary to make sure it works + run: go build + - name: Test + # We're using "sudo" because one of the tests leverages ping, which requires super-user privileges. + # As for the 'env "PATH=$PATH" "GOROOT=$GOROOT"', we need it to use the same "go" executable that + # was configured by the "Set up Go" step (otherwise, it'd use sudo's "go" executable) + run: sudo env "PATH=$PATH" "GOROOT=$GOROOT" go test ./... -race -coverprofile=coverage.txt -covermode=atomic + - name: Codecov + uses: codecov/codecov-action@v5.4.0 + with: + files: ./coverage.txt + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..50891fc8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# IDE +*.iml +.idea +.vscode + +# OS +.DS_Store + +# JS +node_modules + +# Go +/vendor + +# Misc +*.db +*.db-shm +*.db-wal +gatus +config/config.yml +config.yaml +gatus.exe +.IFXApps diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..8e55ef6b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +# Build the go application into a binary +FROM golang:alpine AS builder +RUN apk --update add ca-certificates +WORKDIR /app +COPY . ./ +RUN go mod tidy +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o gatus . + +# Run Tests inside docker image if you don't have a configured go environment +#RUN apk update && apk add --virtual build-dependencies build-base gcc +#RUN go test ./... -mod vendor + +# Run the binary on an empty container +FROM scratch +COPY --from=builder /app/gatus . +COPY --from=builder /app/config.yaml ./config/config.yaml +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt +ENV GATUS_CONFIG_PATH="" +ENV GATUS_LOG_LEVEL="INFO" +ENV PORT="8080" +EXPOSE ${PORT} +ENTRYPOINT ["/gatus"] diff --git a/LICENSE b/LICENSE index 261eeb9e..57bc88a1 100644 --- a/LICENSE +++ b/LICENSE @@ -199,3 +199,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..62f1dad3 --- /dev/null +++ b/Makefile @@ -0,0 +1,48 @@ +BINARY=gatus + +.PHONY: install +install: + go build -v -o $(BINARY) . + +.PHONY: run +run: + ENVIRONMENT=dev GATUS_CONFIG_PATH=./config.yaml go run main.go + +.PHONY: run-binary +run-binary: + ENVIRONMENT=dev GATUS_CONFIG_PATH=./config.yaml ./$(BINARY) + +.PHONY: clean +clean: + rm $(BINARY) + +.PHONY: test +test: + go test ./... -cover + + +########## +# Docker # +########## + +docker-build: + docker build -t twinproduction/gatus:latest . + +docker-run: + docker run -p 8080:8080 --name gatus twinproduction/gatus:latest + +docker-build-and-run: docker-build docker-run + + +############# +# Front end # +############# + +frontend-install-dependencies: + npm --prefix web/app install + +frontend-build: + npm --prefix web/app run build + +frontend-run: + npm --prefix web/app run serve diff --git a/README.md b/README.md index 43318010..7f68545d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2521 @@ -# gatus -A service health dashboard in Go +[![Gatus](.github/assets/logo-with-dark-text.png)](https://gatus.io) + +![test](https://github.com/TwiN/gatus/actions/workflows/test.yml/badge.svg) +[![Go Report Card](https://goreportcard.com/badge/github.com/TwiN/gatus?)](https://goreportcard.com/report/github.com/TwiN/gatus) +[![codecov](https://codecov.io/gh/TwiN/gatus/branch/master/graph/badge.svg)](https://codecov.io/gh/TwiN/gatus) +[![Go version](https://img.shields.io/github/go-mod/go-version/TwiN/gatus.svg)](https://github.com/TwiN/gatus) +[![Docker pulls](https://img.shields.io/docker/pulls/twinproduction/gatus.svg)](https://cloud.docker.com/repository/docker/twinproduction/gatus) +[![Follow TwiN](https://img.shields.io/github/followers/TwiN?label=Follow&style=social)](https://github.com/TwiN) + +Gatus is a developer-oriented health dashboard that gives you the ability to monitor your services using HTTP, ICMP, TCP, and even DNS +queries as well as evaluate the result of said queries by using a list of conditions on values like the status code, +the response time, the certificate expiration, the body and many others. The icing on top is that each of these health +checks can be paired with alerting via Slack, Teams, PagerDuty, Discord, Twilio and many more. + +I personally deploy it in my Kubernetes cluster and let it monitor the status of my +core applications: https://status.twin.sh/ + +_Looking for a managed solution? Check out [Gatus.io](https://gatus.io)._ + +

+ Quick start + +```console +docker run -p 8080:8080 --name gatus twinproduction/gatus:stable +``` +You can also use GitHub Container Registry if you prefer: +```console +docker run -p 8080:8080 --name gatus ghcr.io/twin/gatus:stable +``` +For more details, see [Usage](#usage) +
+ +> ❤ Like this project? Please consider [sponsoring me](https://github.com/sponsors/TwiN). + +![Gatus dashboard](.github/assets/dashboard-dark.png) + +Have any feedback or questions? [Create a discussion](https://github.com/TwiN/gatus/discussions/new). + + +## Table of Contents +- [Table of Contents](#table-of-contents) +- [Why Gatus?](#why-gatus) +- [Features](#features) +- [Usage](#usage) +- [Configuration](#configuration) + - [Endpoints](#endpoints) + - [External Endpoints](#external-endpoints) + - [Conditions](#conditions) + - [Placeholders](#placeholders) + - [Functions](#functions) + - [Storage](#storage) + - [Client configuration](#client-configuration) + - [Alerting](#alerting) + - [Configuring AWS SES alerts](#configuring-aws-ses-alerts) + - [Configuring Discord alerts](#configuring-discord-alerts) + - [Configuring Email alerts](#configuring-email-alerts) + - [Configuring Gitea alerts](#configuring-gitea-alerts) + - [Configuring GitHub alerts](#configuring-github-alerts) + - [Configuring GitLab alerts](#configuring-gitlab-alerts) + - [Configuring Google Chat alerts](#configuring-google-chat-alerts) + - [Configuring Gotify alerts](#configuring-gotify-alerts) + - [Configuring Incident.io alerts](#configuring-incidentio-alerts) + - [Configuring JetBrains Space alerts](#configuring-jetbrains-space-alerts) + - [Configuring Matrix alerts](#configuring-matrix-alerts) + - [Configuring Mattermost alerts](#configuring-mattermost-alerts) + - [Configuring Messagebird alerts](#configuring-messagebird-alerts) + - [Configuring Ntfy alerts](#configuring-ntfy-alerts) + - [Configuring Opsgenie alerts](#configuring-opsgenie-alerts) + - [Configuring PagerDuty alerts](#configuring-pagerduty-alerts) + - [Configuring Pushover alerts](#configuring-pushover-alerts) + - [Configuring Slack alerts](#configuring-slack-alerts) + - [Configuring Teams alerts *(Deprecated)*](#configuring-teams-alerts-deprecated) + - [Configuring Teams Workflow alerts](#configuring-teams-workflow-alerts) + - [Configuring Telegram alerts](#configuring-telegram-alerts) + - [Configuring Twilio alerts](#configuring-twilio-alerts) + - [Configuring Zulip alerts](#configuring-zulip-alerts) + - [Configuring custom alerts](#configuring-custom-alerts) + - [Setting a default alert](#setting-a-default-alert) + - [Maintenance](#maintenance) + - [Security](#security) + - [Basic Authentication](#basic-authentication) + - [OIDC](#oidc) + - [TLS Encryption](#tls-encryption) + - [Metrics](#metrics) + - [Connectivity](#connectivity) + - [Remote instances (EXPERIMENTAL)](#remote-instances-experimental) +- [Deployment](#deployment) + - [Docker](#docker) + - [Helm Chart](#helm-chart) + - [Terraform](#terraform) +- [Running the tests](#running-the-tests) +- [Using in Production](#using-in-production) +- [FAQ](#faq) + - [Sending a GraphQL request](#sending-a-graphql-request) + - [Recommended interval](#recommended-interval) + - [Default timeouts](#default-timeouts) + - [Monitoring a TCP endpoint](#monitoring-a-tcp-endpoint) + - [Monitoring a UDP endpoint](#monitoring-a-udp-endpoint) + - [Monitoring a SCTP endpoint](#monitoring-a-sctp-endpoint) + - [Monitoring a WebSocket endpoint](#monitoring-a-websocket-endpoint) + - [Monitoring an endpoint using ICMP](#monitoring-an-endpoint-using-icmp) + - [Monitoring an endpoint using DNS queries](#monitoring-an-endpoint-using-dns-queries) + - [Monitoring an endpoint using SSH](#monitoring-an-endpoint-using-ssh) + - [Monitoring an endpoint using STARTTLS](#monitoring-an-endpoint-using-starttls) + - [Monitoring an endpoint using TLS](#monitoring-an-endpoint-using-tls) + - [Monitoring domain expiration](#monitoring-domain-expiration) + - [disable-monitoring-lock](#disable-monitoring-lock) + - [Reloading configuration on the fly](#reloading-configuration-on-the-fly) + - [Endpoint groups](#endpoint-groups) + - [Exposing Gatus on a custom path](#exposing-gatus-on-a-custom-path) + - [Exposing Gatus on a custom port](#exposing-gatus-on-a-custom-port) + - [Configuring a startup delay](#configuring-a-startup-delay) + - [Keeping your configuration small](#keeping-your-configuration-small) + - [Proxy client configuration](#proxy-client-configuration) + - [How to fix 431 Request Header Fields Too Large error](#how-to-fix-431-request-header-fields-too-large-error) + - [Badges](#badges) + - [Uptime](#uptime) + - [Health](#health) + - [Health (Shields.io)](#health-shieldsio) + - [Response time](#response-time) + - [How to change the color thresholds of the response time badge](#how-to-change-the-color-thresholds-of-the-response-time-badge) + - [API](#api) + - [Raw Data](#raw-data) + - [Uptime](#uptime-1) + - [Installing as binary](#installing-as-binary) + - [High level design overview](#high-level-design-overview) + + +## Why Gatus? +Before getting into the specifics, I want to address the most common question: +> Why would I use Gatus when I can just use Prometheus’ Alertmanager, Cloudwatch or even Splunk? + +Neither of these can tell you that there’s a problem if there are no clients actively calling the endpoint. +In other words, it's because monitoring metrics mostly rely on existing traffic, which effectively means that unless +your clients are already experiencing a problem, you won't be notified. + +Gatus, on the other hand, allows you to configure health checks for each of your features, which in turn allows it to +monitor these features and potentially alert you before any clients are impacted. + +A sign you may want to look into Gatus is by simply asking yourself whether you'd receive an alert if your load balancer +was to go down right now. Will any of your existing alerts be triggered? Your metrics won’t report an increase in errors +if no traffic makes it to your applications. This puts you in a situation where your clients are the ones +that will notify you about the degradation of your services rather than you reassuring them that you're working on +fixing the issue before they even know about it. + + +## Features +The main features of Gatus are: + +- **Highly flexible health check conditions**: While checking the response status may be enough for some use cases, Gatus goes much further and allows you to add conditions on the response time, the response body and even the IP address. +- **Ability to use Gatus for user acceptance tests**: Thanks to the point above, you can leverage this application to create automated user acceptance tests. +- **Very easy to configure**: Not only is the configuration designed to be as readable as possible, it's also extremely easy to add a new service or a new endpoint to monitor. +- **Alerting**: While having a pretty visual dashboard is useful to keep track of the state of your application(s), you probably don't want to stare at it all day. Thus, notifications via Slack, Mattermost, Messagebird, PagerDuty, Twilio, Google chat and Teams are supported out of the box with the ability to configure a custom alerting provider for any needs you might have, whether it be a different provider or a custom application that manages automated rollbacks. +- **Metrics** +- **Low resource consumption**: As with most Go applications, the resource footprint that this application requires is negligibly small. +- **[Badges](#badges)**: ![Uptime 7d](https://status.twin.sh/api/v1/endpoints/core_blog-external/uptimes/7d/badge.svg) ![Response time 24h](https://status.twin.sh/api/v1/endpoints/core_blog-external/response-times/24h/badge.svg) +- **Dark mode** + +![Gatus dashboard conditions](.github/assets/dashboard-conditions.png) + + +## Usage + +
+ Quick start + +```console +docker run -p 8080:8080 --name gatus twinproduction/gatus +``` +You can also use GitHub Container Registry if you prefer: +```console +docker run -p 8080:8080 --name gatus ghcr.io/twin/gatus +``` +If you want to create your own configuration, see [Docker](#docker) for information on how to mount a configuration file. +
+ +Here's a simple example: +```yaml +endpoints: + - name: website # Name of your endpoint, can be anything + url: "https://twin.sh/health" + interval: 5m # Duration to wait between every status check (default: 60s) + conditions: + - "[STATUS] == 200" # Status must be 200 + - "[BODY].status == UP" # The json path "$.status" must be equal to UP + - "[RESPONSE_TIME] < 300" # Response time must be under 300ms + + - name: make-sure-header-is-rendered + url: "https://example.org/" + interval: 60s + conditions: + - "[STATUS] == 200" # Status must be 200 + - "[BODY] == pat(*

Example Domain

*)" # Body must contain the specified header +``` + +This example would look similar to this: + +![Simple example](.github/assets/example.png) + +By default, the configuration file is expected to be at `config/config.yaml`. + +You can specify a custom path by setting the `GATUS_CONFIG_PATH` environment variable. + +If `GATUS_CONFIG_PATH` points to a directory, all `*.yaml` and `*.yml` files inside said directory and its +subdirectories are merged like so: +- All maps/objects are deep merged (i.e. you could define `alerting.slack` in one file and `alerting.pagerduty` in another file) +- All slices/arrays are appended (i.e. you can define `endpoints` in multiple files and each endpoint will be added to the final list of endpoints) +- Parameters with a primitive value (e.g. `metrics`, `alerting.slack.webhook-url`, etc.) may only be defined once to forcefully avoid any ambiguity + - To clarify, this also means that you could not define `alerting.slack.webhook-url` in two files with different values. All files are merged into one before they are processed. This is by design. + +> 💡 You can also use environment variables in the configuration file (e.g. `$DOMAIN`, `${DOMAIN}`) +> +> See [examples/docker-compose-postgres-storage/config/config.yaml](.examples/docker-compose-postgres-storage/config/config.yaml) for an example. + +If you want to test it locally, see [Docker](#docker). + + +## Configuration +| Parameter | Description | Default | +|:-----------------------------|:-------------------------------------------------------------------------------------------------------------------------------------|:---------------------------| +| `metrics` | Whether to expose metrics at `/metrics`. | `false` | +| `storage` | [Storage configuration](#storage). | `{}` | +| `alerting` | [Alerting configuration](#alerting). | `{}` | +| `endpoints` | [Endpoints configuration](#endpoints). | Required `[]` | +| `external-endpoints` | [External Endpoints configuration](#external-endpoints). | `[]` | +| `security` | [Security configuration](#security). | `{}` | +| `disable-monitoring-lock` | Whether to [disable the monitoring lock](#disable-monitoring-lock). | `false` | +| `skip-invalid-config-update` | Whether to ignore invalid configuration update.
See [Reloading configuration on the fly](#reloading-configuration-on-the-fly). | `false` | +| `web` | Web configuration. | `{}` | +| `web.address` | Address to listen on. | `0.0.0.0` | +| `web.port` | Port to listen on. | `8080` | +| `web.read-buffer-size` | Buffer size for reading requests from a connection. Also limit for the maximum header size. | `8192` | +| `web.tls.certificate-file` | Optional public certificate file for TLS in PEM format. | `` | +| `web.tls.private-key-file` | Optional private key file for TLS in PEM format. | `` | +| `ui` | UI configuration. | `{}` | +| `ui.title` | [Title of the document](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title). | `Health Dashboard ǀ Gatus` | +| `ui.description` | Meta description for the page. | `Gatus is an advanced...`. | +| `ui.header` | Header at the top of the dashboard. | `Health Status` | +| `ui.logo` | URL to the logo to display. | `""` | +| `ui.link` | Link to open when the logo is clicked. | `""` | +| `ui.buttons` | List of buttons to display below the header. | `[]` | +| `ui.buttons[].name` | Text to display on the button. | Required `""` | +| `ui.buttons[].link` | Link to open when the button is clicked. | Required `""` | +| `ui.custom-css` | Custom CSS | `""` | +| `ui.dark-mode` | Whether to enable dark mode by default. Note that this is superseded by the user's operating system theme preferences. | `true` | +| `maintenance` | [Maintenance configuration](#maintenance). | `{}` | + +If you want more verbose logging, you may set the `GATUS_LOG_LEVEL` environment variable to `DEBUG`. +Conversely, if you want less verbose logging, you can set the aforementioned environment variable to `WARN`, `ERROR` or `FATAL`. +The default value for `GATUS_LOG_LEVEL` is `INFO`. + +### Endpoints +Endpoints are URLs, applications, or services that you want to monitor. Each endpoint has a list of conditions that are +evaluated on an interval that you define. If any condition fails, the endpoint is considered as unhealthy. +You can then configure alerts to be triggered when an endpoint is unhealthy once a certain threshold is reached. + +| Parameter | Description | Default | +|:------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------| +| `endpoints` | List of endpoints to monitor. | Required `[]` | +| `endpoints[].enabled` | Whether to monitor the endpoint. | `true` | +| `endpoints[].name` | Name of the endpoint. Can be anything. | Required `""` | +| `endpoints[].group` | Group name. Used to group multiple endpoints together on the dashboard.
See [Endpoint groups](#endpoint-groups). | `""` | +| `endpoints[].url` | URL to send the request to. | Required `""` | +| `endpoints[].method` | Request method. | `GET` | +| `endpoints[].conditions` | Conditions used to determine the health of the endpoint.
See [Conditions](#conditions). | `[]` | +| `endpoints[].interval` | Duration to wait between every status check. | `60s` | +| `endpoints[].graphql` | Whether to wrap the body in a query param (`{"query":"$body"}`). | `false` | +| `endpoints[].body` | Request body. | `""` | +| `endpoints[].headers` | Request headers. | `{}` | +| `endpoints[].dns` | Configuration for an endpoint of type DNS.
See [Monitoring an endpoint using DNS queries](#monitoring-an-endpoint-using-dns-queries). | `""` | +| `endpoints[].dns.query-type` | Query type (e.g. MX). | `""` | +| `endpoints[].dns.query-name` | Query name (e.g. example.com). | `""` | +| `endpoints[].ssh` | Configuration for an endpoint of type SSH.
See [Monitoring an endpoint using SSH](#monitoring-an-endpoint-using-ssh). | `""` | +| `endpoints[].ssh.username` | SSH username (e.g. example). | Required `""` | +| `endpoints[].ssh.password` | SSH password (e.g. password). | Required `""` | +| `endpoints[].alerts` | List of all alerts for a given endpoint.
See [Alerting](#alerting). | `[]` | +| `endpoints[].maintenance-windows` | List of all maintenance windows for a given endpoint.
See [Maintenance](#maintenance). | `[]` | +| `endpoints[].client` | [Client configuration](#client-configuration). | `{}` | +| `endpoints[].ui` | UI configuration at the endpoint level. | `{}` | +| `endpoints[].ui.hide-conditions` | Whether to hide conditions from the results. Note that this only hides conditions from results evaluated from the moment this was enabled. | `false` | +| `endpoints[].ui.hide-hostname` | Whether to hide the hostname from the results. | `false` | +| `endpoints[].ui.hide-port` | Whether to hide the port from the results. | `false` | +| `endpoints[].ui.hide-url` | Whether to hide the URL from the results. Useful if the URL contains a token. | `false` | +| `endpoints[].ui.dont-resolve-failed-conditions` | Whether to resolve failed conditions for the UI. | `false` | +| `endpoints[].ui.badge.response-time` | List of response time thresholds. Each time a threshold is reached, the badge has a different color. | `[50, 200, 300, 500, 750]` | + + +### External Endpoints +Unlike regular endpoints, external endpoints are not monitored by Gatus, but they are instead pushed programmatically. +This allows you to monitor anything you want, even when what you want to check lives in an environment that would not normally be accessible by Gatus. + +For instance: +- You can create your own agent that lives in a private network and pushes the status of your services to a publicly-exposed Gatus instance +- You can monitor services that are not supported by Gatus +- You can implement your own monitoring system while using Gatus as the dashboard + +| Parameter | Description | Default | +|:-------------------------------|:-----------------------------------------------------------------------------------------------------------------------|:--------------| +| `external-endpoints` | List of endpoints to monitor. | `[]` | +| `external-endpoints[].enabled` | Whether to monitor the endpoint. | `true` | +| `external-endpoints[].name` | Name of the endpoint. Can be anything. | Required `""` | +| `external-endpoints[].group` | Group name. Used to group multiple endpoints together on the dashboard.
See [Endpoint groups](#endpoint-groups). | `""` | +| `external-endpoints[].token` | Bearer token required to push status to. | Required `""` | +| `external-endpoints[].alerts` | List of all alerts for a given endpoint.
See [Alerting](#alerting). | `[]` | + +Example: +```yaml +external-endpoints: + - name: ext-ep-test + group: core + token: "potato" + alerts: + - type: discord + description: "healthcheck failed" + send-on-resolved: true +``` + +To push the status of an external endpoint, the request would have to look like this: +``` +POST /api/v1/endpoints/{key}/external?success={success}&error={error} +``` +Where: +- `{key}` has the pattern `_` in which both variables have ` `, `/`, `_`, `,` and `.` replaced by `-`. + - Using the example configuration above, the key would be `core_ext-ep-test`. +- `{success}` is a boolean (`true` or `false`) value indicating whether the health check was successful or not. +- `{error}`: a string describing the reason for a failed health check. If {success} is false, this should contain the error message; if the check is successful, it can be omitted or left empty. + +You must also pass the token as a `Bearer` token in the `Authorization` header. + + +### Conditions +Here are some examples of conditions you can use: + +| Condition | Description | Passing values | Failing values | +|:---------------------------------|:----------------------------------------------------|:---------------------------|------------------| +| `[STATUS] == 200` | Status must be equal to 200 | 200 | 201, 404, ... | +| `[STATUS] < 300` | Status must lower than 300 | 200, 201, 299 | 301, 302, ... | +| `[STATUS] <= 299` | Status must be less than or equal to 299 | 200, 201, 299 | 301, 302, ... | +| `[STATUS] > 400` | Status must be greater than 400 | 401, 402, 403, 404 | 400, 200, ... | +| `[STATUS] == any(200, 429)` | Status must be either 200 or 429 | 200, 429 | 201, 400, ... | +| `[CONNECTED] == true` | Connection to host must've been successful | true | false | +| `[RESPONSE_TIME] < 500` | Response time must be below 500ms | 100ms, 200ms, 300ms | 500ms, 501ms | +| `[IP] == 127.0.0.1` | Target IP must be 127.0.0.1 | 127.0.0.1 | 0.0.0.0 | +| `[BODY] == 1` | The body must be equal to 1 | 1 | `{}`, `2`, ... | +| `[BODY].user.name == john` | JSONPath value of `$.user.name` is equal to `john` | `{"user":{"name":"john"}}` | | +| `[BODY].data[0].id == 1` | JSONPath value of `$.data[0].id` is equal to 1 | `{"data":[{"id":1}]}` | | +| `[BODY].age == [BODY].id` | JSONPath value of `$.age` is equal JSONPath `$.id` | `{"age":1,"id":1}` | | +| `len([BODY].data) < 5` | Array at JSONPath `$.data` has less than 5 elements | `{"data":[{"id":1}]}` | | +| `len([BODY].name) == 8` | String at JSONPath `$.name` has a length of 8 | `{"name":"john.doe"}` | `{"name":"bob"}` | +| `has([BODY].errors) == false` | JSONPath `$.errors` does not exist | `{"name":"john.doe"}` | `{"errors":[]}` | +| `has([BODY].users) == true` | JSONPath `$.users` exists | `{"users":[]}` | `{}` | +| `[BODY].name == pat(john*)` | String at JSONPath `$.name` matches pattern `john*` | `{"name":"john.doe"}` | `{"name":"bob"}` | +| `[BODY].id == any(1, 2)` | Value at JSONPath `$.id` is equal to `1` or `2` | 1, 2 | 3, 4, 5 | +| `[CERTIFICATE_EXPIRATION] > 48h` | Certificate expiration is more than 48h away | 49h, 50h, 123h | 1h, 24h, ... | +| `[DOMAIN_EXPIRATION] > 720h` | The domain must expire in more than 720h | 4000h | 1h, 24h, ... | + + +#### Placeholders +| Placeholder | Description | Example of resolved value | +|:---------------------------|:------------------------------------------------------------------------------------------|:---------------------------------------------| +| `[STATUS]` | Resolves into the HTTP status of the request | `404` | +| `[RESPONSE_TIME]` | Resolves into the response time the request took, in ms | `10` | +| `[IP]` | Resolves into the IP of the target host | `192.168.0.232` | +| `[BODY]` | Resolves into the response body. Supports JSONPath. | `{"name":"john.doe"}` | +| `[CONNECTED]` | Resolves into whether a connection could be established | `true` | +| `[CERTIFICATE_EXPIRATION]` | Resolves into the duration before certificate expiration (valid units are "s", "m", "h".) | `24h`, `48h`, 0 (if not protocol with certs) | +| `[DOMAIN_EXPIRATION]` | Resolves into the duration before the domain expires (valid units are "s", "m", "h".) | `24h`, `48h`, `1234h56m78s` | +| `[DNS_RCODE]` | Resolves into the DNS status of the response | `NOERROR` | + + +#### Functions +| Function | Description | Example | +|:---------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------| +| `len` | If the given path leads to an array, returns its length. Otherwise, the JSON at the given path is minified and converted to a string, and the resulting number of characters is returned. Works only with the `[BODY]` placeholder. | `len([BODY].username) > 8` | +| `has` | Returns `true` or `false` based on whether a given path is valid. Works only with the `[BODY]` placeholder. | `has([BODY].errors) == false` | +| `pat` | Specifies that the string passed as parameter should be evaluated as a pattern. Works only with `==` and `!=`. | `[IP] == pat(192.168.*)` | +| `any` | Specifies that any one of the values passed as parameters is a valid value. Works only with `==` and `!=`. | `[BODY].ip == any(127.0.0.1, ::1)` | + +> 💡 Use `pat` only when you need to. `[STATUS] == pat(2*)` is a lot more expensive than `[STATUS] < 300`. + + +### Storage +| Parameter | Description | Default | +|:------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------|:-----------| +| `storage` | Storage configuration | `{}` | +| `storage.path` | Path to persist the data in. Only supported for types `sqlite` and `postgres`. | `""` | +| `storage.type` | Type of storage. Valid types: `memory`, `sqlite`, `postgres`. | `"memory"` | +| `storage.caching` | Whether to use write-through caching. Improves loading time for large dashboards.
Only supported if `storage.type` is `sqlite` or `postgres` | `false` | + +The results for each endpoint health check as well as the data for uptime and the past events must be persisted +so that they can be displayed on the dashboard. These parameters allow you to configure the storage in question. + +- If `storage.type` is `memory` (default): +```yaml +# Note that this is the default value, and you can omit the storage configuration altogether to achieve the same result. +# Because the data is stored in memory, the data will not survive a restart. +storage: + type: memory +``` +- If `storage.type` is `sqlite`, `storage.path` must not be blank: +```yaml +storage: + type: sqlite + path: data.db +``` +See [examples/docker-compose-sqlite-storage](.examples/docker-compose-sqlite-storage) for an example. + +- If `storage.type` is `postgres`, `storage.path` must be the connection URL: +```yaml +storage: + type: postgres + path: "postgres://user:password@127.0.0.1:5432/gatus?sslmode=disable" +``` +See [examples/docker-compose-postgres-storage](.examples/docker-compose-postgres-storage) for an example. + + +### Client configuration +In order to support a wide range of environments, each monitored endpoint has a unique configuration for +the client used to send the request. + +| Parameter | Description | Default | +|:---------------------------------------|:----------------------------------------------------------------------------|:----------------| +| `client.insecure` | Whether to skip verifying the server's certificate chain and host name. | `false` | +| `client.ignore-redirect` | Whether to ignore redirects (true) or follow them (false, default). | `false` | +| `client.timeout` | Duration before timing out. | `10s` | +| `client.dns-resolver` | Override the DNS resolver using the format `{proto}://{host}:{port}`. | `""` | +| `client.oauth2` | OAuth2 client configuration. | `{}` | +| `client.oauth2.token-url` | The token endpoint URL | required `""` | +| `client.oauth2.client-id` | The client id which should be used for the `Client credentials flow` | required `""` | +| `client.oauth2.client-secret` | The client secret which should be used for the `Client credentials flow` | required `""` | +| `client.oauth2.scopes[]` | A list of `scopes` which should be used for the `Client credentials flow`. | required `[""]` | +| `client.proxy-url` | The URL of the proxy to use for the client | `""` | +| `client.identity-aware-proxy` | Google Identity-Aware-Proxy client configuration. | `{}` | +| `client.identity-aware-proxy.audience` | The Identity-Aware-Proxy audience. (client-id of the IAP oauth2 credential) | required `""` | +| `client.tls.certificate-file` | Path to a client certificate (in PEM format) for mTLS configurations. | `""` | +| `client.tls.private-key-file` | Path to a client private key (in PEM format) for mTLS configurations. | `""` | +| `client.tls.renegotiation` | Type of renegotiation support to provide. (`never`, `freely`, `once`). | `"never"` | +| `client.network` | The network to use for ICMP endpoint client (`ip`, `ip4` or `ip6`). | `"ip"` | + + +> 📝 Some of these parameters are ignored based on the type of endpoint. For instance, there's no certificate involved +> in ICMP requests (ping), therefore, setting `client.insecure` to `true` for an endpoint of that type will not do anything. + +This default configuration is as follows: + +```yaml +client: + insecure: false + ignore-redirect: false + timeout: 10s +``` + +Note that this configuration is only available under `endpoints[]`, `alerting.mattermost` and `alerting.custom`. + +Here's an example with the client configuration under `endpoints[]`: + +```yaml +endpoints: + - name: website + url: "https://twin.sh/health" + client: + insecure: false + ignore-redirect: false + timeout: 10s + conditions: + - "[STATUS] == 200" +``` + +This example shows how you can specify a custom DNS resolver: + +```yaml +endpoints: + - name: with-custom-dns-resolver + url: "https://your.health.api/health" + client: + dns-resolver: "tcp://8.8.8.8:53" + conditions: + - "[STATUS] == 200" +``` + +This example shows how you can use the `client.oauth2` configuration to query a backend API with `Bearer token`: + +```yaml +endpoints: + - name: with-custom-oauth2 + url: "https://your.health.api/health" + client: + oauth2: + token-url: https://your-token-server/token + client-id: 00000000-0000-0000-0000-000000000000 + client-secret: your-client-secret + scopes: ['https://your.health.api/.default'] + conditions: + - "[STATUS] == 200" +``` + +This example shows how you can use the `client.identity-aware-proxy` configuration to query a backend API with `Bearer token` using Google Identity-Aware-Proxy: + +```yaml +endpoints: + - name: with-custom-iap + url: "https://my.iap.protected.app/health" + client: + identity-aware-proxy: + audience: "XXXXXXXX-XXXXXXXXXXXX.apps.googleusercontent.com" + conditions: + - "[STATUS] == 200" +``` + +> 📝 Note that Gatus will use the [gcloud default credentials](https://cloud.google.com/docs/authentication/application-default-credentials) within its environment to generate the token. + +This example shows you how you can use the `client.tls` configuration to perform an mTLS query to a backend API: + +```yaml +endpoints: + - name: website + url: "https://your.mtls.protected.app/health" + client: + tls: + certificate-file: /path/to/user_cert.pem + private-key-file: /path/to/user_key.pem + renegotiation: once + conditions: + - "[STATUS] == 200" +``` + +> 📝 Note that if running in a container, you must volume mount the certificate and key into the container. + +### Alerting +Gatus supports multiple alerting providers, such as Slack and PagerDuty, and supports different alerts for each +individual endpoints with configurable descriptions and thresholds. + +Alerts are configured at the endpoint level like so: + +| Parameter | Description | Default | +|:-----------------------------|:-------------------------------------------------------------------------------|:--------------| +| `alerts` | List of all alerts for a given endpoint. | `[]` | +| `alerts[].type` | Type of alert.
See table below for all valid types. | Required `""` | +| `alerts[].enabled` | Whether to enable the alert. | `true` | +| `alerts[].failure-threshold` | Number of failures in a row needed before triggering the alert. | `3` | +| `alerts[].success-threshold` | Number of successes in a row before an ongoing incident is marked as resolved. | `2` | +| `alerts[].send-on-resolved` | Whether to send a notification once a triggered alert is marked as resolved. | `false` | +| `alerts[].description` | Description of the alert. Will be included in the alert sent. | `""` | +| `alerts[].provider-override` | Alerting provider configuration override for the given alert type | `{}` | + +Here's an example of what an alert configuration might look like at the endpoint level: +```yaml +endpoints: + - name: example + url: "https://example.org" + conditions: + - "[STATUS] == 200" + alerts: + - type: slack + description: "healthcheck failed" + send-on-resolved: true +``` + +You can also override global provider configuration by using `alerts[].provider-override`, like so: +```yaml +endpoints: + - name: example + url: "https://example.org" + conditions: + - "[STATUS] == 200" + alerts: + - type: slack + provider-override: + webhook-url: "https://hooks.slack.com/services/**********/**********/**********" +``` + +> 📝 If an alerting provider is not properly configured, all alerts configured with the provider's type will be +> ignored. + +| Parameter | Description | Default | +|:---------------------------|:----------------------------------------------------------------------------------------------------------------------------------------|:--------| +| `alerting.awsses` | Configuration for alerts of type `awsses`.
See [Configuring AWS SES alerts](#configuring-aws-ses-alerts). | `{}` | +| `alerting.custom` | Configuration for custom actions on failure or alerts.
See [Configuring Custom alerts](#configuring-custom-alerts). | `{}` | +| `alerting.discord` | Configuration for alerts of type `discord`.
See [Configuring Discord alerts](#configuring-discord-alerts). | `{}` | +| `alerting.email` | Configuration for alerts of type `email`.
See [Configuring Email alerts](#configuring-email-alerts). | `{}` | +| `alerting.gitea` | Configuration for alerts of type `gitea`.
See [Configuring Gitea alerts](#configuring-gitea-alerts). | `{}` | +| `alerting.github` | Configuration for alerts of type `github`.
See [Configuring GitHub alerts](#configuring-github-alerts). | `{}` | +| `alerting.gitlab` | Configuration for alerts of type `gitlab`.
See [Configuring GitLab alerts](#configuring-gitlab-alerts). | `{}` | +| `alerting.googlechat` | Configuration for alerts of type `googlechat`.
See [Configuring Google Chat alerts](#configuring-google-chat-alerts). | `{}` | +| `alerting.gotify` | Configuration for alerts of type `gotify`.
See [Configuring Gotify alerts](#configuring-gotify-alerts). | `{}` | +| `alerting.incident-io` | Configuration for alerts of type `incident-io`.
See [Configuring Incident.io alerts](#configuring-incidentio-alerts). | `{}` | +| `alerting.jetbrainsspace` | Configuration for alerts of type `jetbrainsspace`.
See [Configuring JetBrains Space alerts](#configuring-jetbrains-space-alerts). | `{}` | +| `alerting.matrix` | Configuration for alerts of type `matrix`.
See [Configuring Matrix alerts](#configuring-matrix-alerts). | `{}` | +| `alerting.mattermost` | Configuration for alerts of type `mattermost`.
See [Configuring Mattermost alerts](#configuring-mattermost-alerts). | `{}` | +| `alerting.messagebird` | Configuration for alerts of type `messagebird`.
See [Configuring Messagebird alerts](#configuring-messagebird-alerts). | `{}` | +| `alerting.ntfy` | Configuration for alerts of type `ntfy`.
See [Configuring Ntfy alerts](#configuring-ntfy-alerts). | `{}` | +| `alerting.opsgenie` | Configuration for alerts of type `opsgenie`.
See [Configuring Opsgenie alerts](#configuring-opsgenie-alerts). | `{}` | +| `alerting.pagerduty` | Configuration for alerts of type `pagerduty`.
See [Configuring PagerDuty alerts](#configuring-pagerduty-alerts). | `{}` | +| `alerting.pushover` | Configuration for alerts of type `pushover`.
See [Configuring Pushover alerts](#configuring-pushover-alerts). | `{}` | +| `alerting.slack` | Configuration for alerts of type `slack`.
See [Configuring Slack alerts](#configuring-slack-alerts). | `{}` | +| `alerting.teams` | Configuration for alerts of type `teams`. *(Deprecated)*
See [Configuring Teams alerts](#configuring-teams-alerts-deprecated). | `{}` | +| `alerting.teams-workflows` | Configuration for alerts of type `teams-workflows`.
See [Configuring Teams Workflow alerts](#configuring-teams-workflow-alerts). | `{}` | +| `alerting.telegram` | Configuration for alerts of type `telegram`.
See [Configuring Telegram alerts](#configuring-telegram-alerts). | `{}` | +| `alerting.twilio` | Settings for alerts of type `twilio`.
See [Configuring Twilio alerts](#configuring-twilio-alerts). | `{}` | +| `alerting.zulip` | Configuration for alerts of type `zulip`.
See [Configuring Zulip alerts](#configuring-zulip-alerts). | `{}` | + + +#### Configuring AWS SES alerts +| Parameter | Description | Default | +|:-------------------------------------|:-------------------------------------------------------------------------------------------|:--------------| +| `alerting.aws-ses` | Settings for alerts of type `aws-ses` | `{}` | +| `alerting.aws-ses.access-key-id` | AWS Access Key ID | Optional `""` | +| `alerting.aws-ses.secret-access-key` | AWS Secret Access Key | Optional `""` | +| `alerting.aws-ses.region` | AWS Region | Required `""` | +| `alerting.aws-ses.from` | The Email address to send the emails from (should be registered in SES) | Required `""` | +| `alerting.aws-ses.to` | Comma separated list of email address to notify | Required `""` | +| `alerting.aws-ses.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert) | N/A | + +```yaml +alerting: + aws-ses: + access-key-id: "..." + secret-access-key: "..." + region: "us-east-1" + from: "status@example.com" + to: "user@example.com" + +endpoints: + - name: website + interval: 30s + url: "https://twin.sh/health" + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" + alerts: + - type: aws-ses + failure-threshold: 5 + send-on-resolved: true + description: "healthcheck failed" +``` + + +#### Configuring Discord alerts +| Parameter | Description | Default | +|:-------------------------------------|:-------------------------------------------------------------------------------------------|:------------------------------------| +| `alerting.discord` | Configuration for alerts of type `discord` | `{}` | +| `alerting.discord.webhook-url` | Discord Webhook URL | Required `""` | +| `alerting.discord.title` | Title of the notification | `":helmet_with_white_cross: Gatus"` | +| `alerting.discord.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert) | N/A | +| `alerting.discord.overrides` | List of overrides that may be prioritized over the default configuration | `[]` | +| `alerting.discord.overrides[].group` | Endpoint group for which the configuration will be overridden by this configuration | `""` | +| `alerting.discord.overrides[].*` | See `alerting.discord.*` parameters | `{}` | + +```yaml +alerting: + discord: + webhook-url: "https://discord.com/api/webhooks/**********/**********" + +endpoints: + - name: website + url: "https://twin.sh/health" + interval: 5m + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" + alerts: + - type: discord + description: "healthcheck failed" + send-on-resolved: true +``` + + +#### Configuring Email alerts +| Parameter | Description | Default | +|:-----------------------------------|:----------------------------------------------------------------------------------------------|:--------------| +| `alerting.email` | Configuration for alerts of type `email` | `{}` | +| `alerting.email.from` | Email used to send the alert | Required `""` | +| `alerting.email.username` | Username of the SMTP server used to send the alert. If empty, uses `alerting.email.from`. | `""` | +| `alerting.email.password` | Password of the SMTP server used to send the alert. If empty, no authentication is performed. | `""` | +| `alerting.email.host` | Host of the mail server (e.g. `smtp.gmail.com`) | Required `""` | +| `alerting.email.port` | Port the mail server is listening to (e.g. `587`) | Required `0` | +| `alerting.email.to` | Email(s) to send the alerts to | Required `""` | +| `alerting.email.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert) | N/A | +| `alerting.email.client.insecure` | Whether to skip TLS verification | `false` | +| `alerting.email.overrides` | List of overrides that may be prioritized over the default configuration | `[]` | +| `alerting.email.overrides[].group` | Endpoint group for which the configuration will be overridden by this configuration | `""` | +| `alerting.email.overrides[].*` | See `alerting.email.*` parameters | `{}` | + +```yaml +alerting: + email: + from: "from@example.com" + username: "from@example.com" + password: "hunter2" + host: "mail.example.com" + port: 587 + to: "recipient1@example.com,recipient2@example.com" + client: + insecure: false + # You can also add group-specific to keys, which will + # override the to key above for the specified groups + overrides: + - group: "core" + to: "recipient3@example.com,recipient4@example.com" + +endpoints: + - name: website + url: "https://twin.sh/health" + interval: 5m + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" + alerts: + - type: email + description: "healthcheck failed" + send-on-resolved: true + + - name: back-end + group: core + url: "https://example.org/" + interval: 5m + conditions: + - "[STATUS] == 200" + - "[CERTIFICATE_EXPIRATION] > 48h" + alerts: + - type: email + description: "healthcheck failed" + send-on-resolved: true +``` + +> ⚠ Some mail servers are painfully slow. + +#### Configuring Gitea alerts + +| Parameter | Description | Default | +|:--------------------------------|:-----------------------------------------------------------------------------------------------------------|:--------------| +| `alerting.gitea` | Configuration for alerts of type `gitea` | `{}` | +| `alerting.gitea.repository-url` | Gitea repository URL (e.g. `https://gitea.com/TwiN/example`) | Required `""` | +| `alerting.gitea.token` | Personal access token to use for authentication.
Must have at least RW on issues and RO on metadata. | Required `""` | +| `alerting.github.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert). | N/A | + +The Gitea alerting provider creates an issue prefixed with `alert(gatus):` and suffixed with the endpoint's display +name for each alert. If `send-on-resolved` is set to `true` on the endpoint alert, the issue will be automatically +closed when the alert is resolved. + +```yaml +alerting: + gitea: + repository-url: "https://gitea.com/TwiN/test" + token: "349d63f16......" + +endpoints: + - name: example + url: "https://twin.sh/health" + interval: 5m + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 75" + alerts: + - type: gitea + failure-threshold: 2 + success-threshold: 3 + send-on-resolved: true + description: "Everything's burning AAAAAHHHHHHHHHHHHHHH" +``` + +![Gitea alert](.github/assets/gitea-alerts.png) + +#### Configuring GitHub alerts + +| Parameter | Description | Default | +|:---------------------------------|:-----------------------------------------------------------------------------------------------------------|:--------------| +| `alerting.github` | Configuration for alerts of type `github` | `{}` | +| `alerting.github.repository-url` | GitHub repository URL (e.g. `https://github.com/TwiN/example`) | Required `""` | +| `alerting.github.token` | Personal access token to use for authentication.
Must have at least RW on issues and RO on metadata. | Required `""` | +| `alerting.github.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert). | N/A | + +The GitHub alerting provider creates an issue prefixed with `alert(gatus):` and suffixed with the endpoint's display +name for each alert. If `send-on-resolved` is set to `true` on the endpoint alert, the issue will be automatically +closed when the alert is resolved. + +```yaml +alerting: + github: + repository-url: "https://github.com/TwiN/test" + token: "github_pat_12345..." + +endpoints: + - name: example + url: "https://twin.sh/health" + interval: 5m + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 75" + alerts: + - type: github + failure-threshold: 2 + success-threshold: 3 + send-on-resolved: true + description: "Everything's burning AAAAAHHHHHHHHHHHHHHH" +``` + +![GitHub alert](.github/assets/github-alerts.png) + +#### Configuring GitLab alerts +| Parameter | Description | Default | +|:------------------------------------|:--------------------------------------------------------------------------------------------------------------------|:--------------| +| `alerting.gitlab` | Configuration for alerts of type `gitlab` | `{}` | +| `alerting.gitlab.webhook-url` | GitLab alert webhook URL (e.g. `https://gitlab.com/yourusername/example/alerts/notify/gatus/xxxxxxxxxxxxxxxx.json`) | Required `""` | +| `alerting.gitlab.authorization-key` | GitLab alert authorization key. | Required `""` | +| `alerting.gitlab.severity` | Override default severity (critical), can be one of `critical, high, medium, low, info, unknown` | `""` | +| `alerting.gitlab.monitoring-tool` | Override the monitoring tool name (gatus) | `"gatus"` | +| `alerting.gitlab.environment-name` | Set gitlab environment's name. Required to display alerts on a dashboard. | `""` | +| `alerting.gitlab.service` | Override endpoint display name | `""` | +| `alerting.gitlab.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert). | N/A | + +The GitLab alerting provider creates an alert prefixed with `alert(gatus):` and suffixed with the endpoint's display +name for each alert. If `send-on-resolved` is set to `true` on the endpoint alert, the alert will be automatically +closed when the alert is resolved. See +https://docs.gitlab.com/ee/operations/incident_management/integrations.html#configuration to configure the endpoint. + +```yaml +alerting: + gitlab: + webhook-url: "https://gitlab.com/hlidotbe/example/alerts/notify/gatus/xxxxxxxxxxxxxxxx.json" + authorization-key: "12345" + +endpoints: + - name: example + url: "https://twin.sh/health" + interval: 5m + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 75" + alerts: + - type: gitlab + failure-threshold: 2 + success-threshold: 3 + send-on-resolved: true + description: "Everything's burning AAAAAHHHHHHHHHHHHHHH" +``` + +![GitLab alert](.github/assets/gitlab-alerts.png) + + +#### Configuring Google Chat alerts +| Parameter | Description | Default | +|:----------------------------------------|:--------------------------------------------------------------------------------------------|:--------------| +| `alerting.googlechat` | Configuration for alerts of type `googlechat` | `{}` | +| `alerting.googlechat.webhook-url` | Google Chat Webhook URL | Required `""` | +| `alerting.googlechat.client` | Client configuration.
See [Client configuration](#client-configuration). | `{}` | +| `alerting.googlechat.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert). | N/A | +| `alerting.googlechat.overrides` | List of overrides that may be prioritized over the default configuration | `[]` | +| `alerting.googlechat.overrides[].group` | Endpoint group for which the configuration will be overridden by this configuration | `""` | +| `alerting.googlechat.overrides[].*` | See `alerting.googlechat.*` parameters | `{}` | + +```yaml +alerting: + googlechat: + webhook-url: "https://chat.googleapis.com/v1/spaces/*******/messages?key=**********&token=********" + +endpoints: + - name: website + url: "https://twin.sh/health" + interval: 5m + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" + alerts: + - type: googlechat + description: "healthcheck failed" + send-on-resolved: true +``` + + +#### Configuring Gotify alerts +| Parameter | Description | Default | +|:----------------------------------------------|:--------------------------------------------------------------------------------------------|:----------------------| +| `alerting.gotify` | Configuration for alerts of type `gotify` | `{}` | +| `alerting.gotify.server-url` | Gotify server URL | Required `""` | +| `alerting.gotify.token` | Token that is used for authentication. | Required `""` | +| `alerting.gotify.priority` | Priority of the alert according to Gotify standards. | `5` | +| `alerting.gotify.title` | Title of the notification | `"Gatus: "` | +| `alerting.gotify.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert). | N/A | + +```yaml +alerting: + gotify: + server-url: "https://gotify.example" + token: "**************" + +endpoints: + - name: website + url: "https://twin.sh/health" + interval: 5m + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" + alerts: + - type: gotify + description: "healthcheck failed" + send-on-resolved: true +``` + +Here's an example of what the notifications look like: + +![Gotify notifications](.github/assets/gotify-alerts.png) + + +#### Configuring Incident.io alerts +| Parameter | Description | Default | +|:-----------------------------------------|:-------------------------------------------------------------------------------------------|:--------------| +| `alerting.incident-io` | Configuration for alerts of type `incident-io` | `{}` | +| `alerting.incident-io.url` | url to trigger an alert event. | Required `""` | +| `alerting.incident-io.auth-token` | Token that is used for authentication. | Required `""` | +| `alerting.incident-io.source-url` | Source URL | `""` | +| `alerting.incident-io.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert) | N/A | +| `alerting.incident-io.overrides` | List of overrides that may be prioritized over the default configuration | `[]` | +| `alerting.incident-io.overrides[].group` | Endpoint group for which the configuration will be overridden by this configuration | `""` | +| `alerting.incident-io.overrides[].*` | See `alerting.incident-io.*` parameters | `{}` | + +```yaml +alerting: + incident-io: + url: "*****************" + auth-token: "********************************************" + +endpoints: + - name: website + url: "https://twin.sh/health" + interval: 30s + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" + alerts: + - type: incident-io + description: "healthcheck failed" + send-on-resolved: true +``` +In order to get the required alert source config id and authentication token, you must configure an HTTP alert source. + +> **_NOTE:_** the source config id is of the form `https://api.incident.io/v2/alert_events/http/$ID` and the token is expected to be passed as a bearer token like so: `Authorization: Bearer $TOKEN` + + +#### Configuring JetBrains Space alerts +| Parameter | Description | Default | +|:--------------------------------------------|:-------------------------------------------------------------------------------------------|:--------------| +| `alerting.jetbrainsspace` | Configuration for alerts of type `jetbrainsspace` | `{}` | +| `alerting.jetbrainsspace.project` | JetBrains Space project name | Required `""` | +| `alerting.jetbrainsspace.channel-id` | JetBrains Space Chat Channel ID | Required `""` | +| `alerting.jetbrainsspace.token` | Token that is used for authentication. | Required `""` | +| `alerting.jetbrainsspace.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert) | N/A | +| `alerting.jetbrainsspace.overrides` | List of overrides that may be prioritized over the default configuration | `[]` | +| `alerting.jetbrainsspace.overrides[].group` | Endpoint group for which the configuration will be overridden by this configuration | `""` | +| `alerting.jetbrainsspace.overrides[].*` | See `alerting.jetbrainsspace.*` parameters | `{}` | + +```yaml +alerting: + jetbrainsspace: + project: myproject + channel-id: ABCDE12345 + token: "**************" + +endpoints: + - name: website + url: "https://twin.sh/health" + interval: 5m + conditions: + - "[STATUS] == 200" + alerts: + - type: jetbrainsspace + description: "healthcheck failed" + send-on-resolved: true +``` + +Here's an example of what the notifications look like: + +![JetBrains Space notifications](.github/assets/jetbrains-space-alerts.png) + + +#### Configuring Matrix alerts +| Parameter | Description | Default | +|:-----------------------------------------|:-------------------------------------------------------------------------------------------|:-----------------------------------| +| `alerting.matrix` | Configuration for alerts of type `matrix` | `{}` | +| `alerting.matrix.server-url` | Homeserver URL | `https://matrix-client.matrix.org` | +| `alerting.matrix.access-token` | Bot user access token (see https://webapps.stackexchange.com/q/131056) | Required `""` | +| `alerting.matrix.internal-room-id` | Internal room ID of room to send alerts to (can be found in Room Settings > Advanced) | Required `""` | +| `alerting.matrix.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert) | N/A | + +```yaml +alerting: + matrix: + server-url: "https://matrix-client.matrix.org" + access-token: "123456" + internal-room-id: "!example:matrix.org" + +endpoints: + - name: website + interval: 5m + url: "https://twin.sh/health" + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" + alerts: + - type: matrix + send-on-resolved: true + description: "healthcheck failed" +``` + + +#### Configuring Mattermost alerts +| Parameter | Description | Default | +|:----------------------------------------------|:--------------------------------------------------------------------------------------------|:--------------| +| `alerting.mattermost` | Configuration for alerts of type `mattermost` | `{}` | +| `alerting.mattermost.webhook-url` | Mattermost Webhook URL | Required `""` | +| `alerting.mattermost.channel` | Mattermost channel name override (optional) | `""` | +| `alerting.mattermost.client` | Client configuration.
See [Client configuration](#client-configuration). | `{}` | +| `alerting.mattermost.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert). | N/A | +| `alerting.mattermost.overrides` | List of overrides that may be prioritized over the default configuration | `[]` | +| `alerting.mattermost.overrides[].group` | Endpoint group for which the configuration will be overridden by this configuration | `""` | +| `alerting.mattermost.overrides[].*` | See `alerting.mattermost.*` parameters | `{}` | + +```yaml +alerting: + mattermost: + webhook-url: "http://**********/hooks/**********" + client: + insecure: true + +endpoints: + - name: website + url: "https://twin.sh/health" + interval: 5m + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" + alerts: + - type: mattermost + description: "healthcheck failed" + send-on-resolved: true +``` + +Here's an example of what the notifications look like: + +![Mattermost notifications](.github/assets/mattermost-alerts.png) + + +#### Configuring Messagebird alerts +| Parameter | Description | Default | +|:-------------------------------------|:-------------------------------------------------------------------------------------------|:--------------| +| `alerting.messagebird` | Configuration for alerts of type `messagebird` | `{}` | +| `alerting.messagebird.access-key` | Messagebird access key | Required `""` | +| `alerting.messagebird.originator` | The sender of the message | Required `""` | +| `alerting.messagebird.recipients` | The recipients of the message | Required `""` | +| `alerting.messagebird.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert) | N/A | + +Example of sending **SMS** text message alert using Messagebird: +```yaml +alerting: + messagebird: + access-key: "..." + originator: "31619191918" + recipients: "31619191919,31619191920" + +endpoints: + - name: website + interval: 5m + url: "https://twin.sh/health" + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" + alerts: + - type: messagebird + failure-threshold: 3 + send-on-resolved: true + description: "healthcheck failed" +``` + + +#### Configuring Ntfy alerts +| Parameter | Description | Default | +|:-------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------|:------------------| +| `alerting.ntfy` | Configuration for alerts of type `ntfy` | `{}` | +| `alerting.ntfy.topic` | Topic at which the alert will be sent | Required `""` | +| `alerting.ntfy.url` | The URL of the target server | `https://ntfy.sh` | +| `alerting.ntfy.token` | [Access token](https://docs.ntfy.sh/publish/#access-tokens) for restricted topics | `""` | +| `alerting.ntfy.email` | E-mail address for additional e-mail notifications | `""` | +| `alerting.ntfy.click` | Website opened when notification is clicked | `""` | +| `alerting.ntfy.priority` | The priority of the alert | `3` | +| `alerting.ntfy.disable-firebase` | Whether message push delivery via firebase should be disabled. [ntfy.sh defaults to enabled](https://docs.ntfy.sh/publish/#disable-firebase) | `false` | +| `alerting.ntfy.disable-cache` | Whether server side message caching should be disabled. [ntfy.sh defaults to enabled](https://docs.ntfy.sh/publish/#message-caching) | `false` | +| `alerting.ntfy.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert) | N/A | +| `alerting.ntfy.overrides` | List of overrides that may be prioritized over the default configuration | `[]` | +| `alerting.ntfy.overrides[].group` | Endpoint group for which the configuration will be overridden by this configuration | `""` | +| `alerting.ntfy.overrides[].*` | See `alerting.ntfy.*` parameters | `{}` | + +[ntfy](https://github.com/binwiederhier/ntfy) is an amazing project that allows you to subscribe to desktop +and mobile notifications, making it an awesome addition to Gatus. + +Example: +```yaml +alerting: + ntfy: + topic: "gatus-test-topic" + priority: 2 + token: faketoken + default-alert: + failure-threshold: 3 + send-on-resolved: true + # You can also add group-specific to keys, which will + # override the to key above for the specified groups + overrides: + - group: "other" + topic: "gatus-other-test-topic" + priority: 4 + click: "https://example.com" + +endpoints: + - name: website + interval: 5m + url: "https://twin.sh/health" + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" + alerts: + - type: ntfy + - name: other example + group: other + interval: 30m + url: "https://example.com" + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + alerts: + - type: ntfy + description: example +``` + + +#### Configuring Opsgenie alerts +| Parameter | Description | Default | +|:----------------------------------|:-------------------------------------------------------------------------------------------|:---------------------| +| `alerting.opsgenie` | Configuration for alerts of type `opsgenie` | `{}` | +| `alerting.opsgenie.api-key` | Opsgenie API Key | Required `""` | +| `alerting.opsgenie.priority` | Priority level of the alert. | `P1` | +| `alerting.opsgenie.source` | Source field of the alert. | `gatus` | +| `alerting.opsgenie.entity-prefix` | Entity field prefix. | `gatus-` | +| `alerting.opsgenie.alias-prefix` | Alias field prefix. | `gatus-healthcheck-` | +| `alerting.opsgenie.tags` | Tags of alert. | `[]` | +| `alerting.opsgenie.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert) | N/A | + +Opsgenie provider will automatically open and close alerts. + +```yaml +alerting: + opsgenie: + api-key: "00000000-0000-0000-0000-000000000000" +``` + + +#### Configuring PagerDuty alerts +| Parameter | Description | Default | +|:---------------------------------------|:-------------------------------------------------------------------------------------------|:--------| +| `alerting.pagerduty` | Configuration for alerts of type `pagerduty` | `{}` | +| `alerting.pagerduty.integration-key` | PagerDuty Events API v2 integration key | `""` | +| `alerting.pagerduty.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert) | N/A | +| `alerting.pagerduty.overrides` | List of overrides that may be prioritized over the default configuration | `[]` | +| `alerting.pagerduty.overrides[].group` | Endpoint group for which the configuration will be overridden by this configuration | `""` | +| `alerting.pagerduty.overrides[].*` | See `alerting.pagerduty.*` parameters | `{}` | + +It is highly recommended to set `endpoints[].alerts[].send-on-resolved` to `true` for alerts +of type `pagerduty`, because unlike other alerts, the operation resulting from setting said +parameter to `true` will not create another incident but mark the incident as resolved on +PagerDuty instead. + +Behavior: +- By default, `alerting.pagerduty.integration-key` is used as the integration key +- If the endpoint being evaluated belongs to a group (`endpoints[].group`) matching the value of `alerting.pagerduty.overrides[].group`, the provider will use that override's integration key instead of `alerting.pagerduty.integration-key`'s + +```yaml +alerting: + pagerduty: + integration-key: "********************************" + # You can also add group-specific integration keys, which will + # override the integration key above for the specified groups + overrides: + - group: "core" + integration-key: "********************************" + +endpoints: + - name: website + url: "https://twin.sh/health" + interval: 30s + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" + alerts: + - type: pagerduty + failure-threshold: 3 + success-threshold: 5 + send-on-resolved: true + description: "healthcheck failed" + + - name: back-end + group: core + url: "https://example.org/" + interval: 5m + conditions: + - "[STATUS] == 200" + - "[CERTIFICATE_EXPIRATION] > 48h" + alerts: + - type: pagerduty + failure-threshold: 3 + success-threshold: 5 + send-on-resolved: true + description: "healthcheck failed" +``` + + +#### Configuring Pushover alerts +| Parameter | Description | Default | +|:--------------------------------------|:------------------------------------------------------------------------------------------------|:-----------------------------| +| `alerting.pushover` | Configuration for alerts of type `pushover` | `{}` | +| `alerting.pushover.application-token` | Pushover application token | `""` | +| `alerting.pushover.user-key` | User or group key | `""` | +| `alerting.pushover.title` | Fixed title for all messages sent via Pushover | `"Gatus: "` | +| `alerting.pushover.priority` | Priority of all messages, ranging from -2 (very low) to 2 (emergency) | `0` | +| `alerting.pushover.resolved-priority` | Override the priority of messages on resolved, ranging from -2 (very low) to 2 (emergency) | `0` | +| `alerting.pushover.sound` | Sound of all messages
See [sounds](https://pushover.net/api#sounds) for all valid choices. | `""` | +| `alerting.pushover.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert) | N/A | + +```yaml +alerting: + pushover: + application-token: "******************************" + user-key: "******************************" + +endpoints: + - name: website + url: "https://twin.sh/health" + interval: 30s + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" + alerts: + - type: pushover + failure-threshold: 3 + success-threshold: 5 + send-on-resolved: true + description: "healthcheck failed" +``` + + +#### Configuring Slack alerts +| Parameter | Description | Default | +|:-----------------------------------|:-------------------------------------------------------------------------------------------|:--------------| +| `alerting.slack` | Configuration for alerts of type `slack` | `{}` | +| `alerting.slack.webhook-url` | Slack Webhook URL | Required `""` | +| `alerting.slack.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert) | N/A | +| `alerting.slack.overrides` | List of overrides that may be prioritized over the default configuration | `[]` | +| `alerting.slack.overrides[].group` | Endpoint group for which the configuration will be overridden by this configuration | `""` | +| `alerting.slack.overrides[].*` | See `alerting.slack.*` parameters | `{}` | + +```yaml +alerting: + slack: + webhook-url: "https://hooks.slack.com/services/**********/**********/**********" + +endpoints: + - name: website + url: "https://twin.sh/health" + interval: 30s + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" + alerts: + - type: slack + description: "healthcheck failed 3 times in a row" + send-on-resolved: true + - type: slack + failure-threshold: 5 + description: "healthcheck failed 5 times in a row" + send-on-resolved: true +``` + +Here's an example of what the notifications look like: + +![Slack notifications](.github/assets/slack-alerts.png) + + +#### Configuring Teams alerts *(Deprecated)* + +> [!CAUTION] +> **Deprecated:** Office 365 Connectors within Microsoft Teams are being retired ([Source: Microsoft DevBlog](https://devblogs.microsoft.com/microsoft365dev/retirement-of-office-365-connectors-within-microsoft-teams/)). +> Existing connectors will continue to work until December 2025. The new [Teams Workflow Alerts](#configuring-teams-workflow-alerts) should be used with Microsoft Workflows instead of this legacy configuration. + +| Parameter | Description | Default | +|:-----------------------------------|:-------------------------------------------------------------------------------------------|:--------------------| +| `alerting.teams` | Configuration for alerts of type `teams` | `{}` | +| `alerting.teams.webhook-url` | Teams Webhook URL | Required `""` | +| `alerting.teams.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert) | N/A | +| `alerting.teams.title` | Title of the notification | `"🚨 Gatus"` | +| `alerting.teams.client.insecure` | Whether to skip TLS verification | `false` | +| `alerting.teams.overrides` | List of overrides that may be prioritized over the default configuration | `[]` | +| `alerting.teams.overrides[].group` | Endpoint group for which the configuration will be overridden by this configuration | `""` | +| `alerting.teams.overrides[].*` | See `alerting.teams.*` parameters | `{}` | + +```yaml +alerting: + teams: + webhook-url: "https://********.webhook.office.com/webhookb2/************" + client: + insecure: false + # You can also add group-specific to keys, which will + # override the to key above for the specified groups + overrides: + - group: "core" + webhook-url: "https://********.webhook.office.com/webhookb3/************" + +endpoints: + - name: website + url: "https://twin.sh/health" + interval: 30s + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" + alerts: + - type: teams + description: "healthcheck failed" + send-on-resolved: true + + - name: back-end + group: core + url: "https://example.org/" + interval: 5m + conditions: + - "[STATUS] == 200" + - "[CERTIFICATE_EXPIRATION] > 48h" + alerts: + - type: teams + description: "healthcheck failed" + send-on-resolved: true +``` + +Here's an example of what the notifications look like: + +![Teams notifications](.github/assets/teams-alerts.png) + +#### Configuring Teams Workflow alerts + +> [!NOTE] +> This alert is compatible with Workflows for Microsoft Teams. To setup the workflow and get the webhook URL, follow the [Microsoft Documentation](https://support.microsoft.com/en-us/office/create-incoming-webhooks-with-workflows-for-microsoft-teams-8ae491c7-0394-4861-ba59-055e33f75498). + +| Parameter | Description | Default | +|:---------------------------------------------|:-------------------------------------------------------------------------------------------|:-------------------| +| `alerting.teams-workflows` | Configuration for alerts of type `teams` | `{}` | +| `alerting.teams-workflows.webhook-url` | Teams Webhook URL | Required `""` | +| `alerting.teams-workflows.title` | Title of the notification | `"⛑ Gatus"` | +| `alerting.teams-workflows.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert) | N/A | +| `alerting.teams-workflows.overrides` | List of overrides that may be prioritized over the default configuration | `[]` | +| `alerting.teams-workflows.overrides[].group` | Endpoint group for which the configuration will be overridden by this configuration | `""` | +| `alerting.teams-workflows.overrides[].*` | See `alerting.teams-workflows.*` parameters | `{}` | + +```yaml +alerting: + teams-workflows: + webhook-url: "https://********.webhook.office.com/webhookb2/************" + # You can also add group-specific to keys, which will + # override the to key above for the specified groups + overrides: + - group: "core" + webhook-url: "https://********.webhook.office.com/webhookb3/************" + +endpoints: + - name: website + url: "https://twin.sh/health" + interval: 30s + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" + alerts: + - type: teams-workflows + description: "healthcheck failed" + send-on-resolved: true + + - name: back-end + group: core + url: "https://example.org/" + interval: 5m + conditions: + - "[STATUS] == 200" + - "[CERTIFICATE_EXPIRATION] > 48h" + alerts: + - type: teams-workflows + description: "healthcheck failed" + send-on-resolved: true +``` + +Here's an example of what the notifications look like: + +![Teams Workflow notifications](.github/assets/teams-workflows-alerts.png) + + +#### Configuring Telegram alerts +| Parameter | Description | Default | +|:--------------------------------------|:-------------------------------------------------------------------------------------------|:---------------------------| +| `alerting.telegram` | Configuration for alerts of type `telegram` | `{}` | +| `alerting.telegram.token` | Telegram Bot Token | Required `""` | +| `alerting.telegram.id` | Telegram User ID | Required `""` | +| `alerting.telegram.api-url` | Telegram API URL | `https://api.telegram.org` | +| `alerting.telegram.client` | Client configuration.
See [Client configuration](#client-configuration). | `{}` | +| `alerting.telegram.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert) | N/A | +| `alerting.telegram.overrides` | List of overrides that may be prioritized over the default configuration | `[]` | +| `alerting.telegram.overrides[].group` | Endpoint group for which the configuration will be overridden by this configuration | `""` | +| `alerting.telegram.overrides[].*` | See `alerting.telegram.*` parameters | `{}` | + +```yaml +alerting: + telegram: + token: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" + id: "0123456789" + +endpoints: + - name: website + url: "https://twin.sh/health" + interval: 30s + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + alerts: + - type: telegram + send-on-resolved: true +``` + +Here's an example of what the notifications look like: + +![Telegram notifications](.github/assets/telegram-alerts.png) + + +#### Configuring Twilio alerts +| Parameter | Description | Default | +|:--------------------------------|:-------------------------------------------------------------------------------------------|:--------------| +| `alerting.twilio` | Settings for alerts of type `twilio` | `{}` | +| `alerting.twilio.sid` | Twilio account SID | Required `""` | +| `alerting.twilio.token` | Twilio auth token | Required `""` | +| `alerting.twilio.from` | Number to send Twilio alerts from | Required `""` | +| `alerting.twilio.to` | Number to send twilio alerts to | Required `""` | +| `alerting.twilio.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert) | N/A | + +```yaml +alerting: + twilio: + sid: "..." + token: "..." + from: "+1-234-567-8901" + to: "+1-234-567-8901" + +endpoints: + - name: website + interval: 30s + url: "https://twin.sh/health" + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" + alerts: + - type: twilio + failure-threshold: 5 + send-on-resolved: true + description: "healthcheck failed" +``` + + +If the `access-key-id` and `secret-access-key` are not defined Gatus will fall back to IAM authentication. + +Make sure you have the ability to use `ses:SendEmail`. + +#### Configuring Zulip alerts +| Parameter | Description | Default | +|:-----------------------------------|:------------------------------------------------------------------------------------|:--------------| +| `alerting.zulip` | Configuration for alerts of type `discord` | `{}` | +| `alerting.zulip.bot-email` | Bot Email | Required `""` | +| `alerting.zulip.bot-api-key` | Bot API key | Required `""` | +| `alerting.zulip.domain` | Full organization domain (e.g.: yourZulipDomain.zulipchat.com) | Required `""` | +| `alerting.zulip.channel-id` | The channel ID where Gatus will send the alerts | Required `""` | +| `alerting.zulip.overrides` | List of overrides that may be prioritized over the default configuration | `[]` | +| `alerting.zulip.overrides[].group` | Endpoint group for which the configuration will be overridden by this configuration | `""` | +| `alerting.zulip.overrides[].*` | See `alerting.zulip.*` parameters | `{}` | + +```yaml +alerting: + zulip: + bot-email: gatus-bot@some.zulip.org + bot-api-key: "********************************" + domain: some.zulip.org + channel-id: 123456 + +endpoints: + - name: website + url: "https://twin.sh/health" + interval: 5m + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" + alerts: + - type: zulip + description: "healthcheck failed" + send-on-resolved: true +``` + + +#### Configuring custom alerts +| Parameter | Description | Default | +|:--------------------------------|:-------------------------------------------------------------------------------------------|:--------------| +| `alerting.custom` | Configuration for custom actions on failure or alerts | `{}` | +| `alerting.custom.url` | Custom alerting request url | Required `""` | +| `alerting.custom.method` | Request method | `GET` | +| `alerting.custom.body` | Custom alerting request body. | `""` | +| `alerting.custom.headers` | Custom alerting request headers | `{}` | +| `alerting.custom.client` | Client configuration.
See [Client configuration](#client-configuration). | `{}` | +| `alerting.custom.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert) | N/A | + +While they're called alerts, you can use this feature to call anything. + +For instance, you could automate rollbacks by having an application that keeps tracks of new deployments, and by +leveraging Gatus, you could have Gatus call that application endpoint when an endpoint starts failing. Your application +would then check if the endpoint that started failing was part of the recently deployed application, and if it was, +then automatically roll it back. + +Furthermore, you may use the following placeholders in the body (`alerting.custom.body`) and in the url (`alerting.custom.url`): +- `[ALERT_DESCRIPTION]` (resolved from `endpoints[].alerts[].description`) +- `[ENDPOINT_NAME]` (resolved from `endpoints[].name`) +- `[ENDPOINT_GROUP]` (resolved from `endpoints[].group`) +- `[ENDPOINT_URL]` (resolved from `endpoints[].url`) +- `[RESULT_ERRORS]` (resolved from the health evaluation of a given health check) + +If you have an alert using the `custom` provider with `send-on-resolved` set to `true`, you can use the +`[ALERT_TRIGGERED_OR_RESOLVED]` placeholder to differentiate the notifications. +The aforementioned placeholder will be replaced by `TRIGGERED` or `RESOLVED` accordingly, though it can be modified +(details at the end of this section). + +For all intents and purposes, we'll configure the custom alert with a Slack webhook, but you can call anything you want. +```yaml +alerting: + custom: + url: "https://hooks.slack.com/services/**********/**********/**********" + method: "POST" + body: | + { + "text": "[ALERT_TRIGGERED_OR_RESOLVED]: [ENDPOINT_GROUP] - [ENDPOINT_NAME] - [ALERT_DESCRIPTION] - [RESULT_ERRORS]" + } +endpoints: + - name: website + url: "https://twin.sh/health" + interval: 30s + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" + alerts: + - type: custom + failure-threshold: 10 + success-threshold: 3 + send-on-resolved: true + description: "health check failed" +``` + +Note that you can customize the resolved values for the `[ALERT_TRIGGERED_OR_RESOLVED]` placeholder like so: +```yaml +alerting: + custom: + placeholders: + ALERT_TRIGGERED_OR_RESOLVED: + TRIGGERED: "partial_outage" + RESOLVED: "operational" +``` +As a result, the `[ALERT_TRIGGERED_OR_RESOLVED]` in the body of first example of this section would be replaced by +`partial_outage` when an alert is triggered and `operational` when an alert is resolved. + + +#### Setting a default alert +| Parameter | Description | Default | +|:---------------------------------------------|:------------------------------------------------------------------------------|:--------| +| `alerting.*.default-alert.enabled` | Whether to enable the alert | N/A | +| `alerting.*.default-alert.failure-threshold` | Number of failures in a row needed before triggering the alert | N/A | +| `alerting.*.default-alert.success-threshold` | Number of successes in a row before an ongoing incident is marked as resolved | N/A | +| `alerting.*.default-alert.send-on-resolved` | Whether to send a notification once a triggered alert is marked as resolved | N/A | +| `alerting.*.default-alert.description` | Description of the alert. Will be included in the alert sent | N/A | + +> ⚠ You must still specify the `type` of the alert in the endpoint configuration even if you set the default alert of a provider. + +While you can specify the alert configuration directly in the endpoint definition, it's tedious and may lead to a very +long configuration file. + +To avoid such problem, you can use the `default-alert` parameter present in each provider configuration: +```yaml +alerting: + slack: + webhook-url: "https://hooks.slack.com/services/**********/**********/**********" + default-alert: + description: "health check failed" + send-on-resolved: true + failure-threshold: 5 + success-threshold: 5 +``` + +As a result, your Gatus configuration looks a lot tidier: +```yaml +endpoints: + - name: example + url: "https://example.org" + conditions: + - "[STATUS] == 200" + alerts: + - type: slack + + - name: other-example + url: "https://example.com" + conditions: + - "[STATUS] == 200" + alerts: + - type: slack +``` + +It also allows you to do things like this: +```yaml +endpoints: + - name: example + url: "https://example.org" + conditions: + - "[STATUS] == 200" + alerts: + - type: slack + failure-threshold: 5 + - type: slack + failure-threshold: 10 + - type: slack + failure-threshold: 15 +``` + +Of course, you can also mix alert types: +```yaml +alerting: + slack: + webhook-url: "https://hooks.slack.com/services/**********/**********/**********" + default-alert: + failure-threshold: 3 + pagerduty: + integration-key: "********************************" + default-alert: + failure-threshold: 5 + +endpoints: + - name: endpoint-1 + url: "https://example.org" + conditions: + - "[STATUS] == 200" + alerts: + - type: slack + - type: pagerduty + + - name: endpoint-2 + url: "https://example.org" + conditions: + - "[STATUS] == 200" + alerts: + - type: slack + - type: pagerduty +``` + + +### Maintenance +If you have maintenance windows, you may not want to be annoyed by alerts. +To do that, you'll have to use the maintenance configuration: + +| Parameter | Description | Default | +|:-----------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------| +| `maintenance.enabled` | Whether the maintenance period is enabled | `true` | +| `maintenance.start` | Time at which the maintenance window starts in `hh:mm` format (e.g. `23:00`) | Required `""` | +| `maintenance.duration` | Duration of the maintenance window (e.g. `1h`, `30m`) | Required `""` | +| `maintenance.timezone` | Timezone of the maintenance window format (e.g. `Europe/Amsterdam`).
See [List of tz database time zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) for more info | `UTC` | +| `maintenance.every` | Days on which the maintenance period applies (e.g. `[Monday, Thursday]`).
If left empty, the maintenance window applies every day | `[]` | + +Here's an example: +```yaml +maintenance: + start: 23:00 + duration: 1h + timezone: "Europe/Amsterdam" + every: [Monday, Thursday] +``` +Note that you can also specify each day on separate lines: +```yaml +maintenance: + start: 23:00 + duration: 1h + timezone: "Europe/Amsterdam" + every: + - Monday + - Thursday +``` +You can also specify maintenance windows on a per-endpoint basis: +```yaml +endpoints: + - name: endpoint-1 + url: "https://example.org" + maintenance-windows: + - start: "07:30" + duration: 40m + timezone: "Europe/Berlin" + - start: "14:30" + duration: 1h + timezone: "Europe/Berlin" +``` + + +### Security +| Parameter | Description | Default | +|:-----------------|:-----------------------------|:--------| +| `security` | Security configuration | `{}` | +| `security.basic` | HTTP Basic configuration | `{}` | +| `security.oidc` | OpenID Connect configuration | `{}` | + + +#### Basic Authentication +| Parameter | Description | Default | +|:----------------------------------------|:-----------------------------------------------------------------------------------|:--------------| +| `security.basic` | HTTP Basic configuration | `{}` | +| `security.basic.username` | Username for Basic authentication. | Required `""` | +| `security.basic.password-bcrypt-base64` | Password hashed with Bcrypt and then encoded with base64 for Basic authentication. | Required `""` | + +The example below will require that you authenticate with the username `john.doe` and the password `hunter2`: +```yaml +security: + basic: + username: "john.doe" + password-bcrypt-base64: "JDJhJDEwJHRiMnRFakxWazZLdXBzRERQazB1TE8vckRLY05Yb1hSdnoxWU0yQ1FaYXZRSW1McmladDYu" +``` + +> ⚠ Make sure to carefully select the cost of the bcrypt hash. The higher the cost, the longer it takes to compute the hash, +> and basic auth verifies the password against the hash on every request. As of 2023-01-06, I suggest a cost of 9. + + +#### OIDC +| Parameter | Description | Default | +|:---------------------------------|:---------------------------------------------------------------|:--------------| +| `security.oidc` | OpenID Connect configuration | `{}` | +| `security.oidc.issuer-url` | Issuer URL | Required `""` | +| `security.oidc.redirect-url` | Redirect URL. Must end with `/authorization-code/callback` | Required `""` | +| `security.oidc.client-id` | Client id | Required `""` | +| `security.oidc.client-secret` | Client secret | Required `""` | +| `security.oidc.scopes` | Scopes to request. The only scope you need is `openid`. | Required `[]` | +| `security.oidc.allowed-subjects` | List of subjects to allow. If empty, all subjects are allowed. | `[]` | + +```yaml +security: + oidc: + issuer-url: "https://example.okta.com" + redirect-url: "https://status.example.com/authorization-code/callback" + client-id: "123456789" + client-secret: "abcdefghijk" + scopes: ["openid"] + # You may optionally specify a list of allowed subjects. If this is not specified, all subjects will be allowed. + #allowed-subjects: ["johndoe@example.com"] +``` + +Confused? Read [Securing Gatus with OIDC using Auth0](https://twin.sh/articles/56/securing-gatus-with-oidc-using-auth0). + + +### TLS Encryption +Gatus supports basic encryption with TLS. To enable this, certificate files in PEM format have to be provided. + +The example below shows an example configuration which makes gatus respond on port 4443 to HTTPS requests: +```yaml +web: + port: 4443 + tls: + certificate-file: "certificate.crt" + private-key-file: "private.key" +``` + + +### Metrics +To enable metrics, you must set `metrics` to `true`. Doing so will expose Prometheus-friendly metrics at the `/metrics` +endpoint on the same port your application is configured to run on (`web.port`). + +| Metric name | Type | Description | Labels | Relevant endpoint types | +|:---------------------------------------------|:--------|:---------------------------------------------------------------------------|:--------------------------------|:------------------------| +| gatus_results_total | counter | Number of results per endpoint per success state | key, group, name, type, success | All | +| gatus_results_code_total | counter | Total number of results by code | key, group, name, type, code | DNS, HTTP | +| gatus_results_connected_total | counter | Total number of results in which a connection was successfully established | key, group, name, type | All | +| gatus_results_duration_seconds | gauge | Duration of the request in seconds | key, group, name, type | All | +| gatus_results_certificate_expiration_seconds | gauge | Number of seconds until the certificate expires | key, group, name, type | HTTP, STARTTLS | +| gatus_results_endpoint_success | gauge | Displays whether or not the endpoint was a success (0 failure, 1 success) | key, group, name, type | All | + +See [examples/docker-compose-grafana-prometheus](.examples/docker-compose-grafana-prometheus) for further documentation as well as an example. + + +### Connectivity +| Parameter | Description | Default | +|:--------------------------------|:-------------------------------------------|:--------------| +| `connectivity` | Connectivity configuration | `{}` | +| `connectivity.checker` | Connectivity checker configuration | Required `{}` | +| `connectivity.checker.target` | Host to use for validating connectivity | Required `""` | +| `connectivity.checker.interval` | Interval at which to validate connectivity | `1m` | + +While Gatus is used to monitor other services, it is possible for Gatus itself to lose connectivity to the internet. +In order to prevent Gatus from reporting endpoints as unhealthy when Gatus itself is unhealthy, you may configure +Gatus to periodically check for internet connectivity. + +All endpoint executions are skipped while the connectivity checker deems connectivity to be down. + +```yaml +connectivity: + checker: + target: 1.1.1.1:53 + interval: 60s +``` + + +### Remote instances (EXPERIMENTAL) +This feature allows you to retrieve endpoint statuses from a remote Gatus instance. + +There are two main use cases for this: +- You have multiple Gatus instances running on different machines, and you wish to visually expose the statuses through a single dashboard +- You have one or more Gatus instances that are not publicly accessible (e.g. behind a firewall), and you wish to retrieve + +This is an experimental feature. It may be removed or updated in a breaking manner at any time. Furthermore, +there are known issues with this feature. If you'd like to provide some feedback, please write a comment in [#64](https://github.com/TwiN/gatus/issues/64). +Use at your own risk. + +| Parameter | Description | Default | +|:-----------------------------------|:---------------------------------------------|:--------------| +| `remote` | Remote configuration | `{}` | +| `remote.instances` | List of remote instances | Required `[]` | +| `remote.instances.endpoint-prefix` | String to prefix all endpoint names with | `""` | +| `remote.instances.url` | URL from which to retrieve endpoint statuses | Required `""` | + +```yaml +remote: + instances: + - endpoint-prefix: "status.example.org-" + url: "https://status.example.org/api/v1/endpoints/statuses" +``` + + +## Deployment +Many examples can be found in the [.examples](.examples) folder, but this section will focus on the most popular ways of deploying Gatus. + + +### Docker +To run Gatus locally with Docker: +```console +docker run -p 8080:8080 --name gatus twinproduction/gatus +``` + +Other than using one of the examples provided in the [.examples](.examples) folder, you can also try it out locally by +creating a configuration file, we'll call it `config.yaml` for this example, and running the following +command: +```console +docker run -p 8080:8080 --mount type=bind,source="$(pwd)"/config.yaml,target=/config/config.yaml --name gatus twinproduction/gatus +``` + +If you're on Windows, replace `"$(pwd)"` by the absolute path to your current directory, e.g.: +```console +docker run -p 8080:8080 --mount type=bind,source=C:/Users/Chris/Desktop/config.yaml,target=/config/config.yaml --name gatus twinproduction/gatus +``` + +To build the image locally: +```console +docker build . -t twinproduction/gatus +``` + + +### Helm Chart +[Helm](https://helm.sh) must be installed to use the chart. +Please refer to Helm's [documentation](https://helm.sh/docs/) to get started. + +Once Helm is set up properly, add the repository as follows: + +```console +helm repo add twin https://twin.github.io/helm-charts +helm repo update +helm install gatus twin/gatus +``` + +To get more details, please check [chart's configuration](https://github.com/TwiN/helm-charts/blob/master/charts/gatus/README.md). + + +### Terraform +Gatus can be deployed on Terraform by using the following module: [terraform-kubernetes-gatus](https://github.com/TwiN/terraform-kubernetes-gatus). + + +## Running the tests +```console +go test -v ./... +``` + + +## Using in Production +See the [Deployment](#deployment) section. + + +## FAQ +### Sending a GraphQL request +By setting `endpoints[].graphql` to true, the body will automatically be wrapped by the standard GraphQL `query` parameter. + +For instance, the following configuration: +```yaml +endpoints: + - name: filter-users-by-gender + url: http://localhost:8080/playground + method: POST + graphql: true + body: | + { + users(gender: "female") { + id + name + gender + avatar + } + } + conditions: + - "[STATUS] == 200" + - "[BODY].data.users[0].gender == female" +``` + +will send a `POST` request to `http://localhost:8080/playground` with the following body: +```json +{"query":" {\n users(gender: \"female\") {\n id\n name\n gender\n avatar\n }\n }"} +``` + + +### Recommended interval +> 📝 This does not apply if `disable-monitoring-lock` is set to `true`, as the monitoring lock is what +> tells Gatus to only evaluate one endpoint at a time. + +To ensure that Gatus provides reliable and accurate results (i.e. response time), Gatus only evaluates one endpoint at a time +In other words, even if you have multiple endpoints with the same interval, they will not execute at the same time. + +You can test this yourself by running Gatus with several endpoints configured with a very short, unrealistic interval, +such as 1ms. You'll notice that the response time does not fluctuate - that is because while endpoints are evaluated on +different goroutines, there's a global lock that prevents multiple endpoints from running at the same time. + +Unfortunately, there is a drawback. If you have a lot of endpoints, including some that are very slow or prone to timing out +(the default timeout is 10s), then it means that for the entire duration of the request, no other endpoint can be evaluated. + +The interval does not include the duration of the request itself, which means that if an endpoint has an interval of 30s +and the request takes 2s to complete, the timestamp between two evaluations will be 32s, not 30s. + +While this does not prevent Gatus' from performing health checks on all other endpoints, it may cause Gatus to be unable +to respect the configured interval, for instance: +- Endpoint A has an interval of 5s, and times out after 10s to complete +- Endpoint B has an interval of 5s, and takes 1ms to complete +- Endpoint B will be unable to run every 5s, because endpoint A's health evaluation takes longer than its interval + +To sum it up, while Gatus can handle any interval you throw at it, you're better off having slow requests with +higher interval. + +As a rule of thumb, I personally set the interval for more complex health checks to `5m` (5 minutes) and +simple health checks used for alerting (PagerDuty/Twilio) to `30s`. + + +### Default timeouts +| Endpoint type | Timeout | +|:--------------|:--------| +| HTTP | 10s | +| TCP | 10s | +| ICMP | 10s | + +To modify the timeout, see [Client configuration](#client-configuration). + + +### Monitoring a TCP endpoint +By prefixing `endpoints[].url` with `tcp://`, you can monitor TCP endpoints at a very basic level: +```yaml +endpoints: + - name: redis + url: "tcp://127.0.0.1:6379" + interval: 30s + conditions: + - "[CONNECTED] == true" +``` + +Placeholders `[STATUS]` and `[BODY]` as well as the fields `endpoints[].body`, `endpoints[].headers`, +`endpoints[].method` and `endpoints[].graphql` are not supported for TCP endpoints. + +This works for applications such as databases (Postgres, MySQL, etc.) and caches (Redis, Memcached, etc.). + +> 📝 `[CONNECTED] == true` does not guarantee that the endpoint itself is healthy - it only guarantees that there's +> something at the given address listening to the given port, and that a connection to that address was successfully +> established. + + +### Monitoring a UDP endpoint +By prefixing `endpoints[].url` with `udp://`, you can monitor UDP endpoints at a very basic level: +```yaml +endpoints: + - name: example + url: "udp://example.org:80" + conditions: + - "[CONNECTED] == true" +``` + +Placeholders `[STATUS]` and `[BODY]` as well as the fields `endpoints[].body`, `endpoints[].headers`, +`endpoints[].method` and `endpoints[].graphql` are not supported for UDP endpoints. + +This works for UDP based application. + + +### Monitoring a SCTP endpoint +By prefixing `endpoints[].url` with `sctp://`, you can monitor Stream Control Transmission Protocol (SCTP) endpoints at a very basic level: +```yaml +endpoints: + - name: example + url: "sctp://127.0.0.1:38412" + conditions: + - "[CONNECTED] == true" +``` + +Placeholders `[STATUS]` and `[BODY]` as well as the fields `endpoints[].body`, `endpoints[].headers`, +`endpoints[].method` and `endpoints[].graphql` are not supported for SCTP endpoints. + +This works for SCTP based application. + + +### Monitoring a WebSocket endpoint +By prefixing `endpoints[].url` with `ws://` or `wss://`, you can monitor WebSocket endpoints at a very basic level: +```yaml +endpoints: + - name: example + url: "wss://example.com/" + body: "status" + conditions: + - "[CONNECTED] == true" + - "[BODY].result >= 0" +``` + +The `[BODY]` placeholder contains the output of the query, and `[CONNECTED]` +shows whether the connection was successfully established. + + +### Monitoring an endpoint using ICMP +By prefixing `endpoints[].url` with `icmp://`, you can monitor endpoints at a very basic level using ICMP, or more +commonly known as "ping" or "echo": +```yaml +endpoints: + - name: ping-example + url: "icmp://example.com" + conditions: + - "[CONNECTED] == true" +``` + +Only the placeholders `[CONNECTED]`, `[IP]` and `[RESPONSE_TIME]` are supported for endpoints of type ICMP. +You can specify a domain prefixed by `icmp://`, or an IP address prefixed by `icmp://`. + +If you run Gatus on Linux, please read the Linux section on https://github.com/prometheus-community/pro-bing#linux +if you encounter any problems. + + +### Monitoring an endpoint using DNS queries +Defining a `dns` configuration in an endpoint will automatically mark said endpoint as an endpoint of type DNS: +```yaml +endpoints: + - name: example-dns-query + url: "8.8.8.8" # Address of the DNS server to use + dns: + query-name: "example.com" + query-type: "A" + conditions: + - "[BODY] == 93.184.215.14" + - "[DNS_RCODE] == NOERROR" +``` + +There are two placeholders that can be used in the conditions for endpoints of type DNS: +- The placeholder `[BODY]` resolves to the output of the query. For instance, a query of type `A` would return an IPv4. +- The placeholder `[DNS_RCODE]` resolves to the name associated to the response code returned by the query, such as +`NOERROR`, `FORMERR`, `SERVFAIL`, `NXDOMAIN`, etc. + + +### Monitoring an endpoint using SSH +You can monitor endpoints using SSH by prefixing `endpoints[].url` with `ssh://`: +```yaml +endpoints: + - name: ssh-example + url: "ssh://example.com:22" # port is optional. Default is 22. + ssh: + username: "username" + password: "password" + body: | + { + "command": "uptime" + } + interval: 1m + conditions: + - "[CONNECTED] == true" + - "[STATUS] == 0" +``` + +you can also use no authentication to monitor the endpoint by not specifying the username +and password fields. + +```yaml +endpoints: + - name: ssh-example + url: "ssh://example.com:22" # port is optional. Default is 22. + ssh: + username: "" + password: "" + + interval: 1m + conditions: + - "[CONNECTED] == true" + - "[STATUS] == 0" +``` + +The following placeholders are supported for endpoints of type SSH: +- `[CONNECTED]` resolves to `true` if the SSH connection was successful, `false` otherwise +- `[STATUS]` resolves the exit code of the command executed on the remote server (e.g. `0` for success) + + +### Monitoring an endpoint using STARTTLS +If you have an email server that you want to ensure there are no problems with, monitoring it through STARTTLS +will serve as a good initial indicator: +```yaml +endpoints: + - name: starttls-smtp-example + url: "starttls://smtp.gmail.com:587" + interval: 30m + client: + timeout: 5s + conditions: + - "[CONNECTED] == true" + - "[CERTIFICATE_EXPIRATION] > 48h" +``` + + +### Monitoring an endpoint using TLS +Monitoring endpoints using SSL/TLS encryption, such as LDAP over TLS, can help detect certificate expiration: +```yaml +endpoints: + - name: tls-ldaps-example + url: "tls://ldap.example.com:636" + interval: 30m + client: + timeout: 5s + conditions: + - "[CONNECTED] == true" + - "[CERTIFICATE_EXPIRATION] > 48h" +``` + + +### Monitoring domain expiration +You can monitor the expiration of a domain with all endpoint types except for DNS by using the `[DOMAIN_EXPIRATION]` +placeholder: +```yaml +endpoints: + - name: check-domain-and-certificate-expiration + url: "https://example.org" + interval: 1h + conditions: + - "[DOMAIN_EXPIRATION] > 720h" + - "[CERTIFICATE_EXPIRATION] > 240h" +``` + +> ⚠ The usage of the `[DOMAIN_EXPIRATION]` placeholder requires Gatus to send a request to the official IANA WHOIS service [through a library](https://github.com/TwiN/whois) +> and in some cases, a secondary request to a TLD-specific WHOIS server (e.g. `whois.nic.sh`). +> To prevent the WHOIS service from throttling your IP address if you send too many requests, Gatus will prevent you from +> using the `[DOMAIN_EXPIRATION]` placeholder on an endpoint with an interval of less than `5m`. + + +### disable-monitoring-lock +Setting `disable-monitoring-lock` to `true` means that multiple endpoints could be monitored at the same time (i.e. parallel execution). + +While this behavior wouldn't generally be harmful, conditions using the `[RESPONSE_TIME]` placeholder could be impacted +by the evaluation of multiple endpoints at the same time, therefore, the default value for this parameter is `false`. + +There are three main reasons why you might want to disable the monitoring lock: +- You're using Gatus for load testing (each endpoint are periodically evaluated on a different goroutine, so +technically, if you create 100 endpoints with a 1 seconds interval, Gatus will send 100 requests per second) +- You have a _lot_ of endpoints to monitor +- You want to test multiple endpoints at very short intervals (< 5s) + + +### Reloading configuration on the fly +For the sake of convenience, Gatus automatically reloads the configuration on the fly if the loaded configuration file +is updated while Gatus is running. + +By default, the application will exit if the updating configuration is invalid, but you can configure +Gatus to continue running if the configuration file is updated with an invalid configuration by +setting `skip-invalid-config-update` to `true`. + +Keep in mind that it is in your best interest to ensure the validity of the configuration file after each update you +apply to the configuration file while Gatus is running by looking at the log and making sure that you do not see the +following message: +``` +The configuration file was updated, but it is not valid. The old configuration will continue being used. +``` +Failure to do so may result in Gatus being unable to start if the application is restarted for whatever reason. + +I recommend not setting `skip-invalid-config-update` to `true` to avoid a situation like this, but the choice is yours +to make. + +**If you are not using a file storage**, updating the configuration while Gatus is running is effectively +the same as restarting the application. + +> 📝 Updates may not be detected if the config file is bound instead of the config folder. See [#151](https://github.com/TwiN/gatus/issues/151). + + +### Endpoint groups +Endpoint groups are used for grouping multiple endpoints together on the dashboard. + +```yaml +endpoints: + - name: frontend + group: core + url: "https://example.org/" + interval: 5m + conditions: + - "[STATUS] == 200" + + - name: backend + group: core + url: "https://example.org/" + interval: 5m + conditions: + - "[STATUS] == 200" + + - name: monitoring + group: internal + url: "https://example.org/" + interval: 5m + conditions: + - "[STATUS] == 200" + + - name: nas + group: internal + url: "https://example.org/" + interval: 5m + conditions: + - "[STATUS] == 200" + + - name: random endpoint that is not part of a group + url: "https://example.org/" + interval: 5m + conditions: + - "[STATUS] == 200" +``` + +The configuration above will result in a dashboard that looks like this: + +![Gatus Endpoint Groups](.github/assets/endpoint-groups.png) + + +### Exposing Gatus on a custom path +Currently, you can expose the Gatus UI using a fully qualified domain name (FQDN) such as `status.example.org`. However, it does not support path-based routing, which means you cannot expose it through a URL like `example.org/status/`. + +For more information, see https://github.com/TwiN/gatus/issues/88. + + +### Exposing Gatus on a custom port +By default, Gatus is exposed on port `8080`, but you may specify a different port by setting the `web.port` parameter: +```yaml +web: + port: 8081 +``` + +If you're using a PaaS like Heroku that doesn't let you set a custom port and exposes it through an environment +variable instead, you can use that environment variable directly in the configuration file: +```yaml +web: + port: ${PORT} +``` + + +### Configuring a startup delay +If, for any reason, you need Gatus to wait for a given amount of time before monitoring the endpoints on application start, you can use the `GATUS_DELAY_START_SECONDS` environment variable to make Gatus sleep on startup. + + +### Keeping your configuration small +While not specific to Gatus, you can leverage YAML anchors to create a default configuration. +If you have a large configuration file, this should help you keep things clean. + +
+ Example + +```yaml +default-endpoint: &defaults + group: core + interval: 5m + client: + insecure: true + timeout: 30s + conditions: + - "[STATUS] == 200" + +endpoints: + - name: anchor-example-1 + <<: *defaults # This will merge the configuration under &defaults with this endpoint + url: "https://example.org" + + - name: anchor-example-2 + <<: *defaults + group: example # This will override the group defined in &defaults + url: "https://example.com" + + - name: anchor-example-3 + <<: *defaults + url: "https://twin.sh/health" + conditions: # This will override the conditions defined in &defaults + - "[STATUS] == 200" + - "[BODY].status == UP" +``` +
+ + +### Proxy client configuration +You can configure a proxy for the client to use by setting the `proxy-url` parameter in the client configuration. + +```yaml +endpoints: + - name: website + url: "https://twin.sh/health" + client: + proxy-url: http://proxy.example.com:8080 + conditions: + - "[STATUS] == 200" +``` + + +### How to fix 431 Request Header Fields Too Large error +Depending on where your environment is deployed and what kind of middleware or reverse proxy sits in front of Gatus, +you may run into this issue. This could be because the request headers are too large, e.g. big cookies. + +By default, `web.read-buffer-size` is set to `8192`, but increasing this value like so will increase the read buffer size: +```yaml +web: + read-buffer-size: 32768 +``` + + +### Badges +#### Uptime +![Uptime 1h](https://status.twin.sh/api/v1/endpoints/core_blog-external/uptimes/1h/badge.svg) +![Uptime 24h](https://status.twin.sh/api/v1/endpoints/core_blog-external/uptimes/24h/badge.svg) +![Uptime 7d](https://status.twin.sh/api/v1/endpoints/core_blog-external/uptimes/7d/badge.svg) +![Uptime 30d](https://status.twin.sh/api/v1/endpoints/core_blog-external/uptimes/30d/badge.svg) + +Gatus can automatically generate an SVG badge for one of your monitored endpoints. +This allows you to put badges in your individual applications' README or even create your own status page if you +desire. + +The path to generate a badge is the following: +``` +/api/v1/endpoints/{key}/uptimes/{duration}/badge.svg +``` +Where: +- `{duration}` is `30d`, `7d`, `24h` or `1h` +- `{key}` has the pattern `_` in which both variables have ` `, `/`, `_`, `,` and `.` replaced by `-`. + +For instance, if you want the uptime during the last 24 hours from the endpoint `frontend` in the group `core`, +the URL would look like this: +``` +https://example.com/api/v1/endpoints/core_frontend/uptimes/7d/badge.svg +``` +If you want to display an endpoint that is not part of a group, you must leave the group value empty: +``` +https://example.com/api/v1/endpoints/_frontend/uptimes/7d/badge.svg +``` +Example: +``` +![Uptime 24h](https://status.twin.sh/api/v1/endpoints/core_blog-external/uptimes/24h/badge.svg) +``` +If you'd like to see a visual example of each badge available, you can simply navigate to the endpoint's detail page. + + +#### Health +![Health](https://status.twin.sh/api/v1/endpoints/core_blog-external/health/badge.svg) + +The path to generate a badge is the following: +``` +/api/v1/endpoints/{key}/health/badge.svg +``` +Where: +- `{key}` has the pattern `_` in which both variables have ` `, `/`, `_`, `,` and `.` replaced by `-`. + +For instance, if you want the current status of the endpoint `frontend` in the group `core`, +the URL would look like this: +``` +https://example.com/api/v1/endpoints/core_frontend/health/badge.svg +``` + + +#### Health (Shields.io) +![Health](https://img.shields.io/endpoint?url=https%3A%2F%2Fstatus.twin.sh%2Fapi%2Fv1%2Fendpoints%2Fcore_blog-external%2Fhealth%2Fbadge.shields) + +The path to generate a badge is the following: +``` +/api/v1/endpoints/{key}/health/badge.shields +``` +Where: +- `{key}` has the pattern `_` in which both variables have ` `, `/`, `_`, `,` and `.` replaced by `-`. + +For instance, if you want the current status of the endpoint `frontend` in the group `core`, +the URL would look like this: +``` +https://example.com/api/v1/endpoints/core_frontend/health/badge.shields +``` + +See more information about the Shields.io badge endpoint [here](https://shields.io/badges/endpoint-badge). + + +#### Response time +![Response time 1h](https://status.twin.sh/api/v1/endpoints/core_blog-external/response-times/1h/badge.svg) +![Response time 24h](https://status.twin.sh/api/v1/endpoints/core_blog-external/response-times/24h/badge.svg) +![Response time 7d](https://status.twin.sh/api/v1/endpoints/core_blog-external/response-times/7d/badge.svg) +![Response time 30d](https://status.twin.sh/api/v1/endpoints/core_blog-external/response-times/30d/badge.svg) + +The endpoint to generate a badge is the following: +``` +/api/v1/endpoints/{key}/response-times/{duration}/badge.svg +``` +Where: +- `{duration}` is `30d`, `7d`, `24h` or `1h` +- `{key}` has the pattern `_` in which both variables have ` `, `/`, `_`, `,` and `.` replaced by `-`. + +#### Response time (chart) +![Response time 24h](https://status.twin.sh/api/v1/endpoints/core_blog-external/response-times/24h/chart.svg) +![Response time 7d](https://status.twin.sh/api/v1/endpoints/core_blog-external/response-times/7d/chart.svg) +![Response time 30d](https://status.twin.sh/api/v1/endpoints/core_blog-external/response-times/30d/chart.svg) + +The endpoint to generate a response time chart is the following: +``` +/api/v1/endpoints/{key}/response-times/{duration}/chart.svg +``` +Where: +- `{duration}` is `30d`, `7d`, or `24h` +- `{key}` has the pattern `_` in which both variables have ` `, `/`, `_`, `,` and `.` replaced by `-`. + +##### How to change the color thresholds of the response time badge +To change the response time badges' threshold, a corresponding configuration can be added to an endpoint. +The values in the array correspond to the levels [Awesome, Great, Good, Passable, Bad] +All five values must be given in milliseconds (ms). + +```yaml +endpoints: +- name: nas + group: internal + url: "https://example.org/" + interval: 5m + conditions: + - "[STATUS] == 200" + ui: + badge: + response-time: + thresholds: [550, 850, 1350, 1650, 1750] +``` + + +### API +Gatus provides a simple read-only API that can be queried in order to programmatically determine endpoint status and history. + +All endpoints are available via a GET request to the following endpoint: +``` +/api/v1/endpoints/statuses +```` +Example: https://status.twin.sh/api/v1/endpoints/statuses + +Specific endpoints can also be queried by using the following pattern: +``` +/api/v1/endpoints/{group}_{endpoint}/statuses +``` +Example: https://status.twin.sh/api/v1/endpoints/core_blog-home/statuses + +Gzip compression will be used if the `Accept-Encoding` HTTP header contains `gzip`. + +The API will return a JSON payload with the `Content-Type` response header set to `application/json`. +No such header is required to query the API. + +#### Raw Data +Gatus exposes the raw data for one of your monitored endpoints. +This allows you to track and aggregate data in your own applications for monitored endpoints. For instance if you want to track uptime for a period longer than 7 days. + +##### Uptime +The path to get raw uptime data for an endpoint is: +``` +/api/v1/endpoints/{key}/uptimes/{duration} +``` +Where: +- `{duration}` is `30d`, `7d`, `24h` or `1h` +- `{key}` has the pattern `_` in which both variables have ` `, `/`, `_`, `,` and `.` replaced by `-`. + +For instance, if you want the raw uptime data for the last 24 hours from the endpoint `frontend` in the group `core`, the URL would look like this: +``` +https://example.com/api/v1/endpoints/core_frontend/uptimes/24h +``` + +### Installing as binary +You can download Gatus as a binary using the following command: +``` +go install github.com/TwiN/gatus/v5@latest +``` + + +### High level design overview +![Gatus diagram](.github/assets/gatus-diagram.jpg) diff --git a/alerting/alert/alert.go b/alerting/alert/alert.go new file mode 100644 index 00000000..52eb5f5d --- /dev/null +++ b/alerting/alert/alert.go @@ -0,0 +1,128 @@ +package alert + +import ( + "crypto/sha256" + "encoding/hex" + "errors" + "strconv" + "strings" + + "github.com/TwiN/logr" + "gopkg.in/yaml.v3" +) + +var ( + // ErrAlertWithInvalidDescription is the error with which Gatus will panic if an alert has an invalid character + ErrAlertWithInvalidDescription = errors.New("alert description must not have \" or \\") +) + +// Alert is endpoint.Endpoint's alert configuration +type Alert struct { + // Type of alert (required) + Type Type `yaml:"type"` + + // Enabled defines whether the alert is enabled + // + // Use Alert.IsEnabled() to retrieve the value of this field. + // + // This is a pointer, because it is populated by YAML and we need to know whether it was explicitly set to a value + // or not for provider.ParseWithDefaultAlert to work. + Enabled *bool `yaml:"enabled,omitempty"` + + // FailureThreshold is the number of failures in a row needed before triggering the alert + FailureThreshold int `yaml:"failure-threshold"` + + // SuccessThreshold defines how many successful executions must happen in a row before an ongoing incident is marked as resolved + SuccessThreshold int `yaml:"success-threshold"` + + // Description of the alert. Will be included in the alert sent. + // + // This is a pointer, because it is populated by YAML and we need to know whether it was explicitly set to a value + // or not for provider.ParseWithDefaultAlert to work. + Description *string `yaml:"description,omitempty"` + + // SendOnResolved defines whether to send a second notification when the issue has been resolved + // + // This is a pointer, because it is populated by YAML and we need to know whether it was explicitly set to a value + // or not for provider.ParseWithDefaultAlert to work. Use Alert.IsSendingOnResolved() for a non-pointer + SendOnResolved *bool `yaml:"send-on-resolved,omitempty"` + + // ProviderOverride is an optional field that can be used to override the provider's configuration + // It is freeform so that it can be used for any provider-specific configuration. + ProviderOverride map[string]any `yaml:"provider-override,omitempty"` + + // ResolveKey is an optional field that is used by some providers (i.e. PagerDuty's dedup_key) to resolve + // ongoing/triggered incidents + ResolveKey string `yaml:"-"` + + // Triggered is used to determine whether an alert has been triggered. When an alert is resolved, this value + // should be set back to false. It is used to prevent the same alert from going out twice. + // + // This value should only be modified if the provider.AlertProvider's Send function does not return an error for an + // alert that hasn't been triggered yet. This doubles as a lazy retry. The reason why this behavior isn't also + // applied for alerts that are already triggered and has become "healthy" again is to prevent a case where, for + // some reason, the alert provider always returns errors when trying to send the resolved notification + // (SendOnResolved). + Triggered bool `yaml:"-"` +} + +// ValidateAndSetDefaults validates the alert's configuration and sets the default value of fields that have one +func (alert *Alert) ValidateAndSetDefaults() error { + if alert.FailureThreshold <= 0 { + alert.FailureThreshold = 3 + } + if alert.SuccessThreshold <= 0 { + alert.SuccessThreshold = 2 + } + if strings.ContainsAny(alert.GetDescription(), "\"\\") { + return ErrAlertWithInvalidDescription + } + return nil +} + +// GetDescription retrieves the description of the alert +func (alert *Alert) GetDescription() string { + if alert.Description == nil { + return "" + } + return *alert.Description +} + +// IsEnabled returns whether an alert is enabled or not +// Returns true if not set +func (alert *Alert) IsEnabled() bool { + if alert.Enabled == nil { + return true + } + return *alert.Enabled +} + +// IsSendingOnResolved returns whether an alert is sending on resolve or not +func (alert *Alert) IsSendingOnResolved() bool { + if alert.SendOnResolved == nil { + return false + } + return *alert.SendOnResolved +} + +// Checksum returns a checksum of the alert +// Used to determine which persisted triggered alert should be deleted on application start +func (alert *Alert) Checksum() string { + hash := sha256.New() + hash.Write([]byte(string(alert.Type) + "_" + + strconv.FormatBool(alert.IsEnabled()) + "_" + + strconv.FormatBool(alert.IsSendingOnResolved()) + "_" + + strconv.Itoa(alert.SuccessThreshold) + "_" + + strconv.Itoa(alert.FailureThreshold) + "_" + + alert.GetDescription()), + ) + return hex.EncodeToString(hash.Sum(nil)) +} + +func (alert *Alert) ProviderOverrideAsBytes() []byte { + yamlBytes, err := yaml.Marshal(alert.ProviderOverride) + if err != nil { + logr.Warnf("[alert.ProviderOverrideAsBytes] Failed to marshal alert override of type=%s as bytes: %v", alert.Type, err) + } + return yamlBytes +} diff --git a/alerting/alert/alert_test.go b/alerting/alert/alert_test.go new file mode 100644 index 00000000..ffe03eee --- /dev/null +++ b/alerting/alert/alert_test.go @@ -0,0 +1,192 @@ +package alert + +import ( + "errors" + "testing" +) + +func TestAlert_ValidateAndSetDefaults(t *testing.T) { + invalidDescription := "\"" + scenarios := []struct { + name string + alert Alert + expectedError error + expectedSuccessThreshold int + expectedFailureThreshold int + }{ + { + name: "valid-empty", + alert: Alert{ + Description: nil, + FailureThreshold: 0, + SuccessThreshold: 0, + }, + expectedError: nil, + expectedFailureThreshold: 3, + expectedSuccessThreshold: 2, + }, + { + name: "invalid-description", + alert: Alert{ + Description: &invalidDescription, + FailureThreshold: 10, + SuccessThreshold: 5, + }, + expectedError: ErrAlertWithInvalidDescription, + expectedFailureThreshold: 10, + expectedSuccessThreshold: 5, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + if err := scenario.alert.ValidateAndSetDefaults(); !errors.Is(err, scenario.expectedError) { + t.Errorf("expected error %v, got %v", scenario.expectedError, err) + } + if scenario.alert.SuccessThreshold != scenario.expectedSuccessThreshold { + t.Errorf("expected success threshold %v, got %v", scenario.expectedSuccessThreshold, scenario.alert.SuccessThreshold) + } + if scenario.alert.FailureThreshold != scenario.expectedFailureThreshold { + t.Errorf("expected failure threshold %v, got %v", scenario.expectedFailureThreshold, scenario.alert.FailureThreshold) + } + }) + } +} + +func TestAlert_IsEnabled(t *testing.T) { + if !(&Alert{Enabled: nil}).IsEnabled() { + t.Error("alert.IsEnabled() should've returned true, because Enabled was set to nil") + } + if value := false; (&Alert{Enabled: &value}).IsEnabled() { + t.Error("alert.IsEnabled() should've returned false, because Enabled was set to false") + } + if value := true; !(&Alert{Enabled: &value}).IsEnabled() { + t.Error("alert.IsEnabled() should've returned true, because Enabled was set to true") + } +} + +func TestAlert_GetDescription(t *testing.T) { + if (&Alert{Description: nil}).GetDescription() != "" { + t.Error("alert.GetDescription() should've returned an empty string, because Description was set to nil") + } + if value := "description"; (&Alert{Description: &value}).GetDescription() != value { + t.Error("alert.GetDescription() should've returned false, because Description was set to 'description'") + } +} + +func TestAlert_IsSendingOnResolved(t *testing.T) { + if (&Alert{SendOnResolved: nil}).IsSendingOnResolved() { + t.Error("alert.IsSendingOnResolved() should've returned false, because SendOnResolved was set to nil") + } + if value := false; (&Alert{SendOnResolved: &value}).IsSendingOnResolved() { + t.Error("alert.IsSendingOnResolved() should've returned false, because SendOnResolved was set to false") + } + if value := true; !(&Alert{SendOnResolved: &value}).IsSendingOnResolved() { + t.Error("alert.IsSendingOnResolved() should've returned true, because SendOnResolved was set to true") + } +} + +func TestAlert_Checksum(t *testing.T) { + description1, description2 := "a", "b" + yes, no := true, false + scenarios := []struct { + name string + alert Alert + expected string + }{ + { + name: "barebone", + alert: Alert{ + Type: TypeDiscord, + }, + expected: "fed0580e44ed5701dbba73afa1f14b2c53ca5a7b8067a860441c212916057fe3", + }, + { + name: "with-description-1", + alert: Alert{ + Type: TypeDiscord, + Description: &description1, + }, + expected: "005f407ebe506e74a4aeb46f74c28b376debead7011e1b085da3840f72ba9707", + }, + { + name: "with-description-2", + alert: Alert{ + Type: TypeDiscord, + Description: &description2, + }, + expected: "3c2c4a9570cdc614006993c21f79a860a7f5afea10cf70d1a79d3c49342ef2c8", + }, + { + name: "with-description-2-and-enabled-false", + alert: Alert{ + Type: TypeDiscord, + Enabled: &no, + Description: &description2, + }, + expected: "837945c2b4cd5e961db3e63e10c348d4f1c3446ba68cf5a48e35a1ae22cf0c22", + }, + { + name: "with-description-2-and-enabled-true", + alert: Alert{ + Type: TypeDiscord, + Enabled: &yes, // it defaults to true if not set, but just to make sure + Description: &description2, + }, + expected: "3c2c4a9570cdc614006993c21f79a860a7f5afea10cf70d1a79d3c49342ef2c8", + }, + { + name: "with-description-2-and-enabled-true-and-send-on-resolved-true", + alert: Alert{ + Type: TypeDiscord, + Enabled: &yes, + SendOnResolved: &yes, + Description: &description2, + }, + expected: "bf1436995a880eb4a352c74c5dfee1f1b5ff6b9fc55aef9bf411b3631adfd80c", + }, + { + name: "with-description-2-and-failure-threshold-7", + alert: Alert{ + Type: TypeSlack, + FailureThreshold: 7, + Description: &description2, + }, + expected: "8bd479e18bda393d4c924f5a0d962e825002168dedaa88b445e435db7bacffd3", + }, + { + name: "with-description-2-and-failure-threshold-9", + alert: Alert{ + Type: TypeSlack, + FailureThreshold: 9, + Description: &description2, + }, + expected: "5abdfce5236e344996d264d526e769c07cb0d3d329a999769a1ff84b157ca6f1", + }, + { + name: "with-description-2-and-success-threshold-5", + alert: Alert{ + Type: TypeSlack, + SuccessThreshold: 7, + Description: &description2, + }, + expected: "c0000e73626b80e212cfc24830de7094568f648e37f3e16f9e68c7f8ef75c34c", + }, + { + name: "with-description-2-and-success-threshold-1", + alert: Alert{ + Type: TypeSlack, + SuccessThreshold: 1, + Description: &description2, + }, + expected: "5c28963b3a76104cfa4a0d79c89dd29ec596c8cfa4b1af210ec83d6d41587b5f", + }, + } + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + scenario.alert.ValidateAndSetDefaults() + if checksum := scenario.alert.Checksum(); checksum != scenario.expected { + t.Errorf("expected checksum %v, got %v", scenario.expected, checksum) + } + }) + } +} diff --git a/alerting/alert/type.go b/alerting/alert/type.go new file mode 100644 index 00000000..3875d4d8 --- /dev/null +++ b/alerting/alert/type.go @@ -0,0 +1,79 @@ +package alert + +// Type is the type of the alert. +// The value will generally be the name of the alert provider +type Type string + +const ( + // TypeAWSSES is the Type for the awsses alerting provider + TypeAWSSES Type = "aws-ses" + + // TypeCustom is the Type for the custom alerting provider + TypeCustom Type = "custom" + + // TypeDiscord is the Type for the discord alerting provider + TypeDiscord Type = "discord" + + // TypeEmail is the Type for the email alerting provider + TypeEmail Type = "email" + + // TypeGitHub is the Type for the github alerting provider + TypeGitHub Type = "github" + + // TypeGitLab is the Type for the gitlab alerting provider + TypeGitLab Type = "gitlab" + + // TypeGitea is the Type for the gitea alerting provider + TypeGitea Type = "gitea" + + // TypeGoogleChat is the Type for the googlechat alerting provider + TypeGoogleChat Type = "googlechat" + + // TypeGotify is the Type for the gotify alerting provider + TypeGotify Type = "gotify" + + // TypeIncidentIO is the Type for the incident-io alerting provider + TypeIncidentIO Type = "incident-io" + + // TypeJetBrainsSpace is the Type for the jetbrains alerting provider + TypeJetBrainsSpace Type = "jetbrainsspace" + + // TypeMatrix is the Type for the matrix alerting provider + TypeMatrix Type = "matrix" + + // TypeMattermost is the Type for the mattermost alerting provider + TypeMattermost Type = "mattermost" + + // TypeMessagebird is the Type for the messagebird alerting provider + TypeMessagebird Type = "messagebird" + + // TypeNtfy is the Type for the ntfy alerting provider + TypeNtfy Type = "ntfy" + + // TypeOpsgenie is the Type for the opsgenie alerting provider + TypeOpsgenie Type = "opsgenie" + + // TypePagerDuty is the Type for the pagerduty alerting provider + TypePagerDuty Type = "pagerduty" + + // TypePushover is the Type for the pushover alerting provider + TypePushover Type = "pushover" + + // TypeSlack is the Type for the slack alerting provider + TypeSlack Type = "slack" + + // TypeTeams is the Type for the teams alerting provider + TypeTeams Type = "teams" + + // TypeTeamsWorkflows is the Type for the teams-workflows alerting provider + TypeTeamsWorkflows Type = "teams-workflows" + + // TypeTelegram is the Type for the telegram alerting provider + TypeTelegram Type = "telegram" + + // TypeTwilio is the Type for the twilio alerting provider + TypeTwilio Type = "twilio" + + // TypeZulip is the Type for the Zulip alerting provider + TypeZulip Type = "zulip" +) diff --git a/alerting/config.go b/alerting/config.go new file mode 100644 index 00000000..8378a840 --- /dev/null +++ b/alerting/config.go @@ -0,0 +1,139 @@ +package alerting + +import ( + "reflect" + "strings" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/alerting/provider" + "github.com/TwiN/gatus/v5/alerting/provider/awsses" + "github.com/TwiN/gatus/v5/alerting/provider/custom" + "github.com/TwiN/gatus/v5/alerting/provider/discord" + "github.com/TwiN/gatus/v5/alerting/provider/email" + "github.com/TwiN/gatus/v5/alerting/provider/gitea" + "github.com/TwiN/gatus/v5/alerting/provider/github" + "github.com/TwiN/gatus/v5/alerting/provider/gitlab" + "github.com/TwiN/gatus/v5/alerting/provider/googlechat" + "github.com/TwiN/gatus/v5/alerting/provider/gotify" + "github.com/TwiN/gatus/v5/alerting/provider/incidentio" + "github.com/TwiN/gatus/v5/alerting/provider/jetbrainsspace" + "github.com/TwiN/gatus/v5/alerting/provider/matrix" + "github.com/TwiN/gatus/v5/alerting/provider/mattermost" + "github.com/TwiN/gatus/v5/alerting/provider/messagebird" + "github.com/TwiN/gatus/v5/alerting/provider/ntfy" + "github.com/TwiN/gatus/v5/alerting/provider/opsgenie" + "github.com/TwiN/gatus/v5/alerting/provider/pagerduty" + "github.com/TwiN/gatus/v5/alerting/provider/pushover" + "github.com/TwiN/gatus/v5/alerting/provider/slack" + "github.com/TwiN/gatus/v5/alerting/provider/teams" + "github.com/TwiN/gatus/v5/alerting/provider/teamsworkflows" + "github.com/TwiN/gatus/v5/alerting/provider/telegram" + "github.com/TwiN/gatus/v5/alerting/provider/twilio" + "github.com/TwiN/gatus/v5/alerting/provider/zulip" + "github.com/TwiN/logr" +) + +// Config is the configuration for alerting providers +type Config struct { + // AWSSimpleEmailService is the configuration for the aws-ses alerting provider + AWSSimpleEmailService *awsses.AlertProvider `yaml:"aws-ses,omitempty"` + + // Custom is the configuration for the custom alerting provider + Custom *custom.AlertProvider `yaml:"custom,omitempty"` + + // Discord is the configuration for the discord alerting provider + Discord *discord.AlertProvider `yaml:"discord,omitempty"` + + // Email is the configuration for the email alerting provider + Email *email.AlertProvider `yaml:"email,omitempty"` + + // GitHub is the configuration for the github alerting provider + GitHub *github.AlertProvider `yaml:"github,omitempty"` + + // GitLab is the configuration for the gitlab alerting provider + GitLab *gitlab.AlertProvider `yaml:"gitlab,omitempty"` + + // Gitea is the configuration for the gitea alerting provider + Gitea *gitea.AlertProvider `yaml:"gitea,omitempty"` + + // GoogleChat is the configuration for the googlechat alerting provider + GoogleChat *googlechat.AlertProvider `yaml:"googlechat,omitempty"` + + // Gotify is the configuration for the gotify alerting provider + Gotify *gotify.AlertProvider `yaml:"gotify,omitempty"` + + // IncidentIO is the configuration for the incident-io alerting provider + IncidentIO *incidentio.AlertProvider `yaml:"incident-io,omitempty"` + + // JetBrainsSpace is the configuration for the jetbrains space alerting provider + JetBrainsSpace *jetbrainsspace.AlertProvider `yaml:"jetbrainsspace,omitempty"` + + // Matrix is the configuration for the matrix alerting provider + Matrix *matrix.AlertProvider `yaml:"matrix,omitempty"` + + // Mattermost is the configuration for the mattermost alerting provider + Mattermost *mattermost.AlertProvider `yaml:"mattermost,omitempty"` + + // Messagebird is the configuration for the messagebird alerting provider + Messagebird *messagebird.AlertProvider `yaml:"messagebird,omitempty"` + + // Ntfy is the configuration for the ntfy alerting provider + Ntfy *ntfy.AlertProvider `yaml:"ntfy,omitempty"` + + // Opsgenie is the configuration for the opsgenie alerting provider + Opsgenie *opsgenie.AlertProvider `yaml:"opsgenie,omitempty"` + + // PagerDuty is the configuration for the pagerduty alerting provider + PagerDuty *pagerduty.AlertProvider `yaml:"pagerduty,omitempty"` + + // Pushover is the configuration for the pushover alerting provider + Pushover *pushover.AlertProvider `yaml:"pushover,omitempty"` + + // Slack is the configuration for the slack alerting provider + Slack *slack.AlertProvider `yaml:"slack,omitempty"` + + // Teams is the configuration for the teams alerting provider + Teams *teams.AlertProvider `yaml:"teams,omitempty"` + + // TeamsWorkflows is the configuration for the teams alerting provider using the new Workflow App Webhook Connector + TeamsWorkflows *teamsworkflows.AlertProvider `yaml:"teams-workflows,omitempty"` + + // Telegram is the configuration for the telegram alerting provider + Telegram *telegram.AlertProvider `yaml:"telegram,omitempty"` + + // Twilio is the configuration for the twilio alerting provider + Twilio *twilio.AlertProvider `yaml:"twilio,omitempty"` + + // Zulip is the configuration for the zulip alerting provider + Zulip *zulip.AlertProvider `yaml:"zulip,omitempty"` +} + +// GetAlertingProviderByAlertType returns an provider.AlertProvider by its corresponding alert.Type +func (config *Config) GetAlertingProviderByAlertType(alertType alert.Type) provider.AlertProvider { + entityType := reflect.TypeOf(config).Elem() + for i := 0; i < entityType.NumField(); i++ { + field := entityType.Field(i) + tag := strings.Split(field.Tag.Get("yaml"), ",")[0] + if tag == string(alertType) { + fieldValue := reflect.ValueOf(config).Elem().Field(i) + if fieldValue.IsNil() { + return nil + } + return fieldValue.Interface().(provider.AlertProvider) + } + } + logr.Infof("[alerting.GetAlertingProviderByAlertType] No alerting provider found for alert type %s", alertType) + return nil +} + +// SetAlertingProviderToNil Sets an alerting provider to nil to avoid having to revalidate it every time an +// alert of its corresponding type is sent. +func (config *Config) SetAlertingProviderToNil(p provider.AlertProvider) { + entityType := reflect.TypeOf(config).Elem() + for i := 0; i < entityType.NumField(); i++ { + field := entityType.Field(i) + if field.Type == reflect.TypeOf(p) { + reflect.ValueOf(config).Elem().Field(i).Set(reflect.Zero(field.Type)) + } + } +} diff --git a/alerting/provider/awsses/awsses.go b/alerting/provider/awsses/awsses.go new file mode 100644 index 00000000..aa071450 --- /dev/null +++ b/alerting/provider/awsses/awsses.go @@ -0,0 +1,228 @@ +package awsses + +import ( + "errors" + "fmt" + "strings" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/logr" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ses" + "gopkg.in/yaml.v3" +) + +const ( + CharSet = "UTF-8" +) + +var ( + ErrDuplicateGroupOverride = errors.New("duplicate group override") + ErrMissingFromOrToFields = errors.New("from and to fields are required") + ErrInvalidAWSAuthConfig = errors.New("either both or neither of access-key-id and secret-access-key must be specified") +) + +type Config struct { + AccessKeyID string `yaml:"access-key-id"` + SecretAccessKey string `yaml:"secret-access-key"` + Region string `yaml:"region"` + + From string `yaml:"from"` + To string `yaml:"to"` +} + +func (cfg *Config) Validate() error { + if len(cfg.From) == 0 || len(cfg.To) == 0 { + return ErrMissingFromOrToFields + } + if !((len(cfg.AccessKeyID) == 0 && len(cfg.SecretAccessKey) == 0) || (len(cfg.AccessKeyID) > 0 && len(cfg.SecretAccessKey) > 0)) { + // if both AccessKeyID and SecretAccessKey are specified, we'll use these to authenticate, + // otherwise if neither are specified, then we'll fall back on IAM authentication. + return ErrInvalidAWSAuthConfig + } + return nil +} + +func (cfg *Config) Merge(override *Config) { + if len(override.AccessKeyID) > 0 { + cfg.AccessKeyID = override.AccessKeyID + } + if len(override.SecretAccessKey) > 0 { + cfg.SecretAccessKey = override.SecretAccessKey + } + if len(override.Region) > 0 { + cfg.Region = override.Region + } + if len(override.From) > 0 { + cfg.From = override.From + } + if len(override.To) > 0 { + cfg.To = override.To + } +} + +// AlertProvider is the configuration necessary for sending an alert using AWS Simple Email Service +type AlertProvider struct { + DefaultConfig Config `yaml:",inline"` + + // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type + DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` + + // Overrides is a list of Override that may be prioritized over the default configuration + Overrides []Override `yaml:"overrides,omitempty"` +} + +// Override is a case under which the default integration is overridden +type Override struct { + Group string `yaml:"group"` + Config `yaml:",inline"` +} + +// Validate the provider's configuration +func (provider *AlertProvider) Validate() error { + registeredGroups := make(map[string]bool) + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if isAlreadyRegistered := registeredGroups[override.Group]; isAlreadyRegistered || override.Group == "" || len(override.To) == 0 { + return ErrDuplicateGroupOverride + } + registeredGroups[override.Group] = true + } + } + return provider.DefaultConfig.Validate() +} + +// Send an alert using the provider +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + cfg, err := provider.GetConfig(ep.Group, alert) + if err != nil { + return err + } + awsSession, err := provider.createSession(cfg) + if err != nil { + return err + } + svc := ses.New(awsSession) + subject, body := provider.buildMessageSubjectAndBody(ep, alert, result, resolved) + emails := strings.Split(cfg.To, ",") + + input := &ses.SendEmailInput{ + Destination: &ses.Destination{ + ToAddresses: aws.StringSlice(emails), + }, + Message: &ses.Message{ + Body: &ses.Body{ + Text: &ses.Content{ + Charset: aws.String(CharSet), + Data: aws.String(body), + }, + }, + Subject: &ses.Content{ + Charset: aws.String(CharSet), + Data: aws.String(subject), + }, + }, + Source: aws.String(cfg.From), + } + if _, err = svc.SendEmail(input); err != nil { + if aerr, ok := err.(awserr.Error); ok { + switch aerr.Code() { + case ses.ErrCodeMessageRejected: + logr.Error(ses.ErrCodeMessageRejected + ": " + aerr.Error()) + case ses.ErrCodeMailFromDomainNotVerifiedException: + logr.Error(ses.ErrCodeMailFromDomainNotVerifiedException + ": " + aerr.Error()) + case ses.ErrCodeConfigurationSetDoesNotExistException: + logr.Error(ses.ErrCodeConfigurationSetDoesNotExistException + ": " + aerr.Error()) + default: + logr.Error(aerr.Error()) + } + } else { + // Print the error, cast err to awserr.Error to get the Code and + // Message from an error. + logr.Error(err.Error()) + } + + return err + } + return nil +} + +func (provider *AlertProvider) createSession(cfg *Config) (*session.Session, error) { + awsConfig := &aws.Config{ + Region: aws.String(cfg.Region), + } + if len(cfg.AccessKeyID) > 0 && len(cfg.SecretAccessKey) > 0 { + awsConfig.Credentials = credentials.NewStaticCredentials(cfg.AccessKeyID, cfg.SecretAccessKey, "") + } + return session.NewSession(awsConfig) +} + +// buildMessageSubjectAndBody builds the message subject and body +func (provider *AlertProvider) buildMessageSubjectAndBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) (string, string) { + var subject, message string + if resolved { + subject = fmt.Sprintf("[%s] Alert resolved", ep.DisplayName()) + message = fmt.Sprintf("An alert for %s has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) + } else { + subject = fmt.Sprintf("[%s] Alert triggered", ep.DisplayName()) + message = fmt.Sprintf("An alert for %s has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) + } + var formattedConditionResults string + if len(result.ConditionResults) > 0 { + formattedConditionResults = "\n\nCondition results:\n" + for _, conditionResult := range result.ConditionResults { + var prefix string + if conditionResult.Success { + prefix = "✅" + } else { + prefix = "❌" + } + formattedConditionResults += fmt.Sprintf("%s %s\n", prefix, conditionResult.Condition) + } + } + var description string + if alertDescription := alert.GetDescription(); len(alertDescription) > 0 { + description = "\n\nAlert description: " + alertDescription + } + return subject, message + description + formattedConditionResults +} + +// GetDefaultAlert returns the provider's default alert configuration +func (provider *AlertProvider) GetDefaultAlert() *alert.Alert { + return provider.DefaultAlert +} + +// GetConfig returns the configuration for the provider with the overrides applied +func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) { + cfg := provider.DefaultConfig + // Handle group overrides + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if group == override.Group { + cfg.Merge(&override.Config) + break + } + } + } + // Handle alert overrides + if len(alert.ProviderOverride) != 0 { + overrideConfig := Config{} + if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil { + return nil, err + } + cfg.Merge(&overrideConfig) + } + // Validate the configuration + err := cfg.Validate() + return &cfg, err +} + +// ValidateOverrides validates the alert's provider override and, if present, the group override +func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error { + _, err := provider.GetConfig(group, alert) + return err +} diff --git a/alerting/provider/awsses/awsses_test.go b/alerting/provider/awsses/awsses_test.go new file mode 100644 index 00000000..348a2db6 --- /dev/null +++ b/alerting/provider/awsses/awsses_test.go @@ -0,0 +1,250 @@ +package awsses + +import ( + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/config/endpoint" +) + +func TestAlertProvider_Validate(t *testing.T) { + invalidProvider := AlertProvider{} + if err := invalidProvider.Validate(); err == nil { + t.Error("provider shouldn't have been valid") + } + invalidProviderWithOneKey := AlertProvider{DefaultConfig: Config{From: "from@example.com", To: "to@example.com", AccessKeyID: "1"}} + if err := invalidProviderWithOneKey.Validate(); err == nil { + t.Error("provider shouldn't have been valid") + } + validProvider := AlertProvider{DefaultConfig: Config{From: "from@example.com", To: "to@example.com"}} + if err := validProvider.Validate(); err != nil { + t.Error("provider should've been valid") + } + validProviderWithKeys := AlertProvider{DefaultConfig: Config{From: "from@example.com", To: "to@example.com", AccessKeyID: "1", SecretAccessKey: "1"}} + if err := validProviderWithKeys.Validate(); err != nil { + t.Error("provider should've been valid") + } +} + +func TestAlertProvider_ValidateWithOverride(t *testing.T) { + providerWithInvalidOverrideGroup := AlertProvider{ + Overrides: []Override{ + { + Config: Config{To: "to@example.com"}, + Group: "", + }, + }, + } + if err := providerWithInvalidOverrideGroup.Validate(); err == nil { + t.Error("provider Group shouldn't have been valid") + } + providerWithInvalidOverrideTo := AlertProvider{ + Overrides: []Override{ + { + Config: Config{To: ""}, + Group: "group", + }, + }, + } + if err := providerWithInvalidOverrideTo.Validate(); err == nil { + t.Error("provider integration key shouldn't have been valid") + } + providerWithValidOverride := AlertProvider{ + DefaultConfig: Config{ + From: "from@example.com", + To: "to@example.com", + }, + Overrides: []Override{ + { + Config: Config{To: "to@example.com"}, + Group: "group", + }, + }, + } + if err := providerWithValidOverride.Validate(); err != nil { + t.Error("provider should've been valid") + } +} + +func TestAlertProvider_buildRequestBody(t *testing.T) { + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + ExpectedSubject string + ExpectedBody string + }{ + { + Name: "triggered", + Provider: AlertProvider{}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedSubject: "[endpoint-name] Alert triggered", + ExpectedBody: "An alert for endpoint-name has been triggered due to having failed 3 time(s) in a row\n\nAlert description: description-1\n\nCondition results:\n❌ [CONNECTED] == true\n❌ [STATUS] == 200\n", + }, + { + Name: "resolved", + Provider: AlertProvider{}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedSubject: "[endpoint-name] Alert resolved", + ExpectedBody: "An alert for endpoint-name has been resolved after passing successfully 5 time(s) in a row\n\nAlert description: description-2\n\nCondition results:\n✅ [CONNECTED] == true\n✅ [STATUS] == 200\n", + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + subject, body := scenario.Provider.buildMessageSubjectAndBody( + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if subject != scenario.ExpectedSubject { + t.Errorf("expected subject to be %s, got %s", scenario.ExpectedSubject, subject) + } + if body != scenario.ExpectedBody { + t.Errorf("expected body to be %s, got %s", scenario.ExpectedBody, body) + } + }) + } +} + +func TestAlertProvider_GetDefaultAlert(t *testing.T) { + if (&AlertProvider{DefaultAlert: &alert.Alert{}}).GetDefaultAlert() == nil { + t.Error("expected default alert to be not nil") + } + if (&AlertProvider{DefaultAlert: nil}).GetDefaultAlert() != nil { + t.Error("expected default alert to be nil") + } +} + +func TestAlertProvider_getConfigWithOverrides(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + InputGroup string + InputAlert alert.Alert + ExpectedOutput Config + }{ + { + Name: "provider-no-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{ + From: "from@example.com", + To: "to@example.com", + }, + Overrides: nil, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{From: "from@example.com", To: "to@example.com"}, + }, + { + Name: "provider-no-override-specify-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{ + From: "from@example.com", + To: "to@example.com", + }, + Overrides: nil, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{From: "from@example.com", To: "to@example.com"}, + }, + { + Name: "provider-with-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{ + From: "from@example.com", + To: "to@example.com", + }, + Overrides: []Override{ + { + Group: "group", + Config: Config{To: "groupto@example.com"}, + }, + }, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{From: "from@example.com", To: "to@example.com"}, + }, + { + Name: "provider-with-override-specify-group-should-override", + Provider: AlertProvider{ + DefaultConfig: Config{ + From: "from@example.com", + To: "to@example.com", + }, + Overrides: []Override{ + { + Group: "group", + Config: Config{To: "groupto@example.com", SecretAccessKey: "wow", AccessKeyID: "noway"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{From: "from@example.com", To: "groupto@example.com", SecretAccessKey: "wow", AccessKeyID: "noway"}, + }, + { + Name: "provider-with-override-specify-group-but-alert-override-should-override-group-override", + Provider: AlertProvider{ + DefaultConfig: Config{ + From: "from@example.com", + To: "to@example.com", + }, + Overrides: []Override{ + { + Group: "group", + Config: Config{From: "from@example.com", To: "groupto@example.com", SecretAccessKey: "sekrit"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{ + ProviderOverride: map[string]any{ + "to": "alertto@example.com", + "access-key-id": 123, + }, + }, + ExpectedOutput: Config{To: "alertto@example.com", From: "from@example.com", AccessKeyID: "123", SecretAccessKey: "sekrit"}, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + got, err := scenario.Provider.GetConfig(scenario.InputGroup, &scenario.InputAlert) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if got.From != scenario.ExpectedOutput.From { + t.Errorf("expected From to be %s, got %s", scenario.ExpectedOutput.From, got.From) + } + if got.To != scenario.ExpectedOutput.To { + t.Errorf("expected To to be %s, got %s", scenario.ExpectedOutput.To, got.To) + } + if got.AccessKeyID != scenario.ExpectedOutput.AccessKeyID { + t.Errorf("expected AccessKeyID to be %s, got %s", scenario.ExpectedOutput.AccessKeyID, got.AccessKeyID) + } + if got.SecretAccessKey != scenario.ExpectedOutput.SecretAccessKey { + t.Errorf("expected SecretAccessKey to be %s, got %s", scenario.ExpectedOutput.SecretAccessKey, got.SecretAccessKey) + } + if got.Region != scenario.ExpectedOutput.Region { + t.Errorf("expected Region to be %s, got %s", scenario.ExpectedOutput.Region, got.Region) + } + // Test ValidateOverrides as well, since it really just calls GetConfig + if err = scenario.Provider.ValidateOverrides(scenario.InputGroup, &scenario.InputAlert); err != nil { + t.Errorf("unexpected error: %s", err) + } + }) + } +} diff --git a/alerting/provider/custom/custom.go b/alerting/provider/custom/custom.go new file mode 100644 index 00000000..c65e34a5 --- /dev/null +++ b/alerting/provider/custom/custom.go @@ -0,0 +1,179 @@ +package custom + +import ( + "bytes" + "errors" + "fmt" + "io" + "net/http" + "strings" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "gopkg.in/yaml.v3" +) + +var ( + ErrURLNotSet = errors.New("url not set") +) + +type Config struct { + URL string `yaml:"url"` + Method string `yaml:"method,omitempty"` + Body string `yaml:"body,omitempty"` + Headers map[string]string `yaml:"headers,omitempty"` + Placeholders map[string]map[string]string `yaml:"placeholders,omitempty"` + + // ClientConfig is the configuration of the client used to communicate with the provider's target + ClientConfig *client.Config `yaml:"client,omitempty"` +} + +func (cfg *Config) Validate() error { + if len(cfg.URL) == 0 { + return ErrURLNotSet + } + return nil +} + +func (cfg *Config) Merge(override *Config) { + if override.ClientConfig != nil { + cfg.ClientConfig = override.ClientConfig + } + if len(override.URL) > 0 { + cfg.URL = override.URL + } + if len(override.Method) > 0 { + cfg.Method = override.Method + } + if len(override.Body) > 0 { + cfg.Body = override.Body + } + if len(override.Headers) > 0 { + cfg.Headers = override.Headers + } + if len(override.Placeholders) > 0 { + cfg.Placeholders = override.Placeholders + } +} + +// AlertProvider is the configuration necessary for sending an alert using a custom HTTP request +// Technically, all alert providers should be reachable using the custom alert provider +type AlertProvider struct { + DefaultConfig Config `yaml:",inline"` + + // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type + DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` + + // Overrides is a list of Override that may be prioritized over the default configuration + Overrides []Override `yaml:"overrides,omitempty"` +} + +// Override is a case under which the default integration is overridden +type Override struct { + Group string `yaml:"group"` + Config `yaml:",inline"` +} + +// Validate the provider's configuration +func (provider *AlertProvider) Validate() error { + return provider.DefaultConfig.Validate() +} + +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + cfg, err := provider.GetConfig(ep.Group, alert) + if err != nil { + return err + } + request := provider.buildHTTPRequest(cfg, ep, alert, result, resolved) + response, err := client.GetHTTPClient(cfg.ClientConfig).Do(request) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode > 399 { + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body)) + } + return err +} + +func (provider *AlertProvider) buildHTTPRequest(cfg *Config, ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) *http.Request { + body, url, method := cfg.Body, cfg.URL, cfg.Method + body = strings.ReplaceAll(body, "[ALERT_DESCRIPTION]", alert.GetDescription()) + url = strings.ReplaceAll(url, "[ALERT_DESCRIPTION]", alert.GetDescription()) + body = strings.ReplaceAll(body, "[ENDPOINT_NAME]", ep.Name) + url = strings.ReplaceAll(url, "[ENDPOINT_NAME]", ep.Name) + body = strings.ReplaceAll(body, "[ENDPOINT_GROUP]", ep.Group) + url = strings.ReplaceAll(url, "[ENDPOINT_GROUP]", ep.Group) + body = strings.ReplaceAll(body, "[ENDPOINT_URL]", ep.URL) + url = strings.ReplaceAll(url, "[ENDPOINT_URL]", ep.URL) + body = strings.ReplaceAll(body, "[RESULT_ERRORS]", strings.Join(result.Errors, ",")) + url = strings.ReplaceAll(url, "[RESULT_ERRORS]", strings.Join(result.Errors, ",")) + if resolved { + body = strings.ReplaceAll(body, "[ALERT_TRIGGERED_OR_RESOLVED]", provider.GetAlertStatePlaceholderValue(cfg, true)) + url = strings.ReplaceAll(url, "[ALERT_TRIGGERED_OR_RESOLVED]", provider.GetAlertStatePlaceholderValue(cfg, true)) + } else { + body = strings.ReplaceAll(body, "[ALERT_TRIGGERED_OR_RESOLVED]", provider.GetAlertStatePlaceholderValue(cfg, false)) + url = strings.ReplaceAll(url, "[ALERT_TRIGGERED_OR_RESOLVED]", provider.GetAlertStatePlaceholderValue(cfg, false)) + } + if len(method) == 0 { + method = http.MethodGet + } + bodyBuffer := bytes.NewBuffer([]byte(body)) + request, _ := http.NewRequest(method, url, bodyBuffer) + for k, v := range cfg.Headers { + request.Header.Set(k, v) + } + return request +} + +// GetAlertStatePlaceholderValue returns the Placeholder value for ALERT_TRIGGERED_OR_RESOLVED if configured +func (provider *AlertProvider) GetAlertStatePlaceholderValue(cfg *Config, resolved bool) string { + status := "TRIGGERED" + if resolved { + status = "RESOLVED" + } + if _, ok := cfg.Placeholders["ALERT_TRIGGERED_OR_RESOLVED"]; ok { + if val, ok := cfg.Placeholders["ALERT_TRIGGERED_OR_RESOLVED"][status]; ok { + return val + } + } + return status +} + +// GetDefaultAlert returns the provider's default alert configuration +func (provider *AlertProvider) GetDefaultAlert() *alert.Alert { + return provider.DefaultAlert +} + +// GetConfig returns the configuration for the provider with the overrides applied +func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) { + cfg := provider.DefaultConfig + // Handle group overrides + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if group == override.Group { + cfg.Merge(&override.Config) + break + } + } + } + // Handle alert overrides + if len(alert.ProviderOverride) != 0 { + overrideConfig := Config{} + if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil { + return nil, err + } + cfg.Merge(&overrideConfig) + } + // Validate the configuration + err := cfg.Validate() + return &cfg, err +} + +// ValidateOverrides validates the alert's provider override and, if present, the group override +func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error { + _, err := provider.GetConfig(group, alert) + return err +} diff --git a/alerting/provider/custom/custom_test.go b/alerting/provider/custom/custom_test.go new file mode 100644 index 00000000..cf2697cd --- /dev/null +++ b/alerting/provider/custom/custom_test.go @@ -0,0 +1,395 @@ +package custom + +import ( + "fmt" + "io" + "net/http" + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/test" +) + +func TestAlertProvider_Validate(t *testing.T) { + t.Run("invalid-provider", func(t *testing.T) { + invalidProvider := AlertProvider{DefaultConfig: Config{URL: ""}} + if err := invalidProvider.Validate(); err == nil { + t.Error("provider shouldn't have been valid") + } + }) + t.Run("valid-provider", func(t *testing.T) { + validProvider := AlertProvider{DefaultConfig: Config{URL: "https://example.com"}} + if err := validProvider.Validate(); err != nil { + t.Error("provider should've been valid") + } + }) +} + +func TestAlertProvider_Send(t *testing.T) { + defer client.InjectHTTPClient(nil) + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + MockRoundTripper test.MockRoundTripper + ExpectedError bool + }{ + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{URL: "https://example.com"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "triggered-error", + Provider: AlertProvider{DefaultConfig: Config{URL: "https://example.com"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + { + Name: "resolved", + Provider: AlertProvider{DefaultConfig: Config{URL: "https://example.com"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "resolved-error", + Provider: AlertProvider{DefaultConfig: Config{URL: "https://example.com"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) + err := scenario.Provider.Send( + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if scenario.ExpectedError && err == nil { + t.Error("expected error, got none") + } + if !scenario.ExpectedError && err != nil { + t.Error("expected no error, got", err.Error()) + } + }) + } +} + +func TestAlertProvider_buildHTTPRequest(t *testing.T) { + alertProvider := &AlertProvider{ + DefaultConfig: Config{ + URL: "https://example.com/[ENDPOINT_GROUP]/[ENDPOINT_NAME]?event=[ALERT_TRIGGERED_OR_RESOLVED]&description=[ALERT_DESCRIPTION]&url=[ENDPOINT_URL]", + Body: "[ENDPOINT_NAME],[ENDPOINT_GROUP],[ALERT_DESCRIPTION],[ENDPOINT_URL],[ALERT_TRIGGERED_OR_RESOLVED]", + }, + } + alertDescription := "alert-description" + scenarios := []struct { + AlertProvider *AlertProvider + Resolved bool + ExpectedURL string + ExpectedBody string + }{ + { + AlertProvider: alertProvider, + Resolved: true, + ExpectedURL: "https://example.com/endpoint-group/endpoint-name?event=RESOLVED&description=alert-description&url=https://example.com", + ExpectedBody: "endpoint-name,endpoint-group,alert-description,https://example.com,RESOLVED", + }, + { + AlertProvider: alertProvider, + Resolved: false, + ExpectedURL: "https://example.com/endpoint-group/endpoint-name?event=TRIGGERED&description=alert-description&url=https://example.com", + ExpectedBody: "endpoint-name,endpoint-group,alert-description,https://example.com,TRIGGERED", + }, + } + for _, scenario := range scenarios { + t.Run(fmt.Sprintf("resolved-%v-with-default-placeholders", scenario.Resolved), func(t *testing.T) { + request := alertProvider.buildHTTPRequest( + &alertProvider.DefaultConfig, + &endpoint.Endpoint{Name: "endpoint-name", Group: "endpoint-group", URL: "https://example.com"}, + &alert.Alert{Description: &alertDescription}, + &endpoint.Result{Errors: []string{}}, + scenario.Resolved, + ) + if request.URL.String() != scenario.ExpectedURL { + t.Error("expected URL to be", scenario.ExpectedURL, "got", request.URL.String()) + } + body, _ := io.ReadAll(request.Body) + if string(body) != scenario.ExpectedBody { + t.Error("expected body to be", scenario.ExpectedBody, "got", string(body)) + } + }) + } +} + +func TestAlertProviderWithResultErrors_buildHTTPRequest(t *testing.T) { + alertProvider := &AlertProvider{ + DefaultConfig: Config{ + URL: "https://example.com/[ENDPOINT_GROUP]/[ENDPOINT_NAME]?event=[ALERT_TRIGGERED_OR_RESOLVED]&description=[ALERT_DESCRIPTION]&url=[ENDPOINT_URL]&error=[RESULT_ERRORS]", + Body: "[ENDPOINT_NAME],[ENDPOINT_GROUP],[ALERT_DESCRIPTION],[ENDPOINT_URL],[ALERT_TRIGGERED_OR_RESOLVED],[RESULT_ERRORS]", + }, + } + alertDescription := "alert-description" + scenarios := []struct { + AlertProvider *AlertProvider + Resolved bool + ExpectedURL string + ExpectedBody string + Errors []string + }{ + { + AlertProvider: alertProvider, + Resolved: true, + ExpectedURL: "https://example.com/endpoint-group/endpoint-name?event=RESOLVED&description=alert-description&url=https://example.com&error=", + ExpectedBody: "endpoint-name,endpoint-group,alert-description,https://example.com,RESOLVED,", + }, + { + AlertProvider: alertProvider, + Resolved: false, + ExpectedURL: "https://example.com/endpoint-group/endpoint-name?event=TRIGGERED&description=alert-description&url=https://example.com&error=error1,error2", + ExpectedBody: "endpoint-name,endpoint-group,alert-description,https://example.com,TRIGGERED,error1,error2", + Errors: []string{"error1", "error2"}, + }, + } + for _, scenario := range scenarios { + t.Run(fmt.Sprintf("resolved-%v-with-default-placeholders-and-result-errors", scenario.Resolved), func(t *testing.T) { + request := alertProvider.buildHTTPRequest( + &alertProvider.DefaultConfig, + &endpoint.Endpoint{Name: "endpoint-name", Group: "endpoint-group", URL: "https://example.com"}, + &alert.Alert{Description: &alertDescription}, + &endpoint.Result{Errors: scenario.Errors}, + scenario.Resolved, + ) + if request.URL.String() != scenario.ExpectedURL { + t.Error("expected URL to be", scenario.ExpectedURL, "got", request.URL.String()) + } + body, _ := io.ReadAll(request.Body) + if string(body) != scenario.ExpectedBody { + t.Error("expected body to be", scenario.ExpectedBody, "got", string(body)) + } + }) + } +} + +func TestAlertProvider_buildHTTPRequestWithCustomPlaceholder(t *testing.T) { + alertProvider := &AlertProvider{ + DefaultConfig: Config{ + URL: "https://example.com/[ENDPOINT_GROUP]/[ENDPOINT_NAME]?event=[ALERT_TRIGGERED_OR_RESOLVED]&description=[ALERT_DESCRIPTION]", + Body: "[ENDPOINT_NAME],[ENDPOINT_GROUP],[ALERT_DESCRIPTION],[ALERT_TRIGGERED_OR_RESOLVED]", + Headers: nil, + Placeholders: map[string]map[string]string{ + "ALERT_TRIGGERED_OR_RESOLVED": { + "RESOLVED": "fixed", + "TRIGGERED": "boom", + }, + }, + }, + } + alertDescription := "alert-description" + scenarios := []struct { + AlertProvider *AlertProvider + Resolved bool + ExpectedURL string + ExpectedBody string + }{ + { + AlertProvider: alertProvider, + Resolved: true, + ExpectedURL: "https://example.com/endpoint-group/endpoint-name?event=fixed&description=alert-description", + ExpectedBody: "endpoint-name,endpoint-group,alert-description,fixed", + }, + { + AlertProvider: alertProvider, + Resolved: false, + ExpectedURL: "https://example.com/endpoint-group/endpoint-name?event=boom&description=alert-description", + ExpectedBody: "endpoint-name,endpoint-group,alert-description,boom", + }, + } + for _, scenario := range scenarios { + t.Run(fmt.Sprintf("resolved-%v-with-custom-placeholders", scenario.Resolved), func(t *testing.T) { + request := alertProvider.buildHTTPRequest( + &alertProvider.DefaultConfig, + &endpoint.Endpoint{Name: "endpoint-name", Group: "endpoint-group"}, + &alert.Alert{Description: &alertDescription}, + &endpoint.Result{}, + scenario.Resolved, + ) + if request.URL.String() != scenario.ExpectedURL { + t.Error("expected URL to be", scenario.ExpectedURL, "got", request.URL.String()) + } + body, _ := io.ReadAll(request.Body) + if string(body) != scenario.ExpectedBody { + t.Error("expected body to be", scenario.ExpectedBody, "got", string(body)) + } + }) + } +} + +func TestAlertProvider_GetAlertStatePlaceholderValueDefaults(t *testing.T) { + alertProvider := &AlertProvider{ + DefaultConfig: Config{ + URL: "https://example.com/[ENDPOINT_NAME]?event=[ALERT_TRIGGERED_OR_RESOLVED]&description=[ALERT_DESCRIPTION]", + Body: "[ENDPOINT_NAME],[ENDPOINT_GROUP],[ALERT_DESCRIPTION],[ALERT_TRIGGERED_OR_RESOLVED]", + }, + } + if alertProvider.GetAlertStatePlaceholderValue(&alertProvider.DefaultConfig, true) != "RESOLVED" { + t.Error("expected RESOLVED, got", alertProvider.GetAlertStatePlaceholderValue(&alertProvider.DefaultConfig, true)) + } + if alertProvider.GetAlertStatePlaceholderValue(&alertProvider.DefaultConfig, false) != "TRIGGERED" { + t.Error("expected TRIGGERED, got", alertProvider.GetAlertStatePlaceholderValue(&alertProvider.DefaultConfig, false)) + } +} + +func TestAlertProvider_GetDefaultAlert(t *testing.T) { + if (&AlertProvider{DefaultAlert: &alert.Alert{}}).GetDefaultAlert() == nil { + t.Error("expected default alert to be not nil") + } + if (&AlertProvider{DefaultAlert: nil}).GetDefaultAlert() != nil { + t.Error("expected default alert to be nil") + } +} + +func TestAlertProvider_GetConfig(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + InputGroup string + InputAlert alert.Alert + ExpectedOutput Config + }{ + { + Name: "provider-no-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{URL: "http://example.com", Body: "default-body"}, + Overrides: nil, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{URL: "http://example.com", Body: "default-body"}, + }, + { + Name: "provider-no-override-specify-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{URL: "http://example.com"}, + Overrides: nil, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{URL: "http://example.com"}, + }, + { + Name: "provider-with-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{URL: "http://example.com"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{URL: "http://group-example.com", Headers: map[string]string{"Cache": "true"}}, + }, + }, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{URL: "http://example.com", Headers: map[string]string{"Cache": "true"}}, + }, + { + Name: "provider-with-override-specify-group-should-override", + Provider: AlertProvider{ + DefaultConfig: Config{URL: "http://example.com", Body: "default-body"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{URL: "http://group-example.com", Body: "group-body"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{URL: "http://group-example.com", Body: "group-body"}, + }, + { + Name: "provider-with-group-override-and-alert-override--alert-override-should-take-precedence", + Provider: AlertProvider{ + DefaultConfig: Config{URL: "http://example.com"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{URL: "http://group-example.com"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{ProviderOverride: map[string]any{"url": "http://alert-example.com", "body": "alert-body"}}, + ExpectedOutput: Config{URL: "http://alert-example.com", Body: "alert-body"}, + }, + { + Name: "provider-with-partial-overrides", + Provider: AlertProvider{ + DefaultConfig: Config{URL: "http://example.com"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{Method: "POST"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{ProviderOverride: map[string]any{"body": "alert-body"}}, + ExpectedOutput: Config{URL: "http://example.com", Body: "alert-body", Method: "POST"}, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + got, err := scenario.Provider.GetConfig(scenario.InputGroup, &scenario.InputAlert) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if got.URL != scenario.ExpectedOutput.URL { + t.Errorf("expected webhook URL to be %s, got %s", scenario.ExpectedOutput.URL, got.URL) + } + if got.Body != scenario.ExpectedOutput.Body { + t.Errorf("expected body to be %s, got %s", scenario.ExpectedOutput.Body, got.Body) + } + if got.Headers != nil { + for key, value := range scenario.ExpectedOutput.Headers { + if got.Headers[key] != value { + t.Errorf("expected header %s to be %s, got %s", key, value, got.Headers[key]) + } + } + } + // Test ValidateOverrides as well, since it really just calls GetConfig + if err = scenario.Provider.ValidateOverrides(scenario.InputGroup, &scenario.InputAlert); err != nil { + t.Errorf("unexpected error: %s", err) + } + }) + } +} diff --git a/alerting/provider/discord/discord.go b/alerting/provider/discord/discord.go new file mode 100644 index 00000000..97f4e727 --- /dev/null +++ b/alerting/provider/discord/discord.go @@ -0,0 +1,199 @@ +package discord + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "gopkg.in/yaml.v3" +) + +var ( + ErrWebhookURLNotSet = errors.New("webhook-url not set") + ErrDuplicateGroupOverride = errors.New("duplicate group override") +) + +type Config struct { + WebhookURL string `yaml:"webhook-url"` + Title string `yaml:"title,omitempty"` // Title of the message that will be sent +} + +func (cfg *Config) Validate() error { + if len(cfg.WebhookURL) == 0 { + return ErrWebhookURLNotSet + } + return nil +} + +func (cfg *Config) Merge(override *Config) { + if len(override.WebhookURL) > 0 { + cfg.WebhookURL = override.WebhookURL + } + if len(override.Title) > 0 { + cfg.Title = override.Title + } +} + +// AlertProvider is the configuration necessary for sending an alert using Discord +type AlertProvider struct { + DefaultConfig Config `yaml:",inline"` + + // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type + DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` + + // Overrides is a list of Override that may be prioritized over the default configuration + Overrides []Override `yaml:"overrides,omitempty"` +} + +type Override struct { + Group string `yaml:"group"` + Config `yaml:",inline"` +} + +// Validate the provider's configuration +func (provider *AlertProvider) Validate() error { + registeredGroups := make(map[string]bool) + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if isAlreadyRegistered := registeredGroups[override.Group]; isAlreadyRegistered || override.Group == "" || len(override.WebhookURL) == 0 { + return ErrDuplicateGroupOverride + } + registeredGroups[override.Group] = true + } + } + return provider.DefaultConfig.Validate() +} + +// Send an alert using the provider +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + cfg, err := provider.GetConfig(ep.Group, alert) + if err != nil { + return err + } + buffer := bytes.NewBuffer(provider.buildRequestBody(cfg, ep, alert, result, resolved)) + request, err := http.NewRequest(http.MethodPost, cfg.WebhookURL, buffer) + if err != nil { + return err + } + request.Header.Set("Content-Type", "application/json") + response, err := client.GetHTTPClient(nil).Do(request) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode > 399 { + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body)) + } + return err +} + +type Body struct { + Content string `json:"content"` + Embeds []Embed `json:"embeds"` +} + +type Embed struct { + Title string `json:"title"` + Description string `json:"description"` + Color int `json:"color"` + Fields []Field `json:"fields,omitempty"` +} + +type Field struct { + Name string `json:"name"` + Value string `json:"value"` + Inline bool `json:"inline"` +} + +// buildRequestBody builds the request body for the provider +func (provider *AlertProvider) buildRequestBody(cfg *Config, ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { + var message string + var colorCode int + if resolved { + message = fmt.Sprintf("An alert for **%s** has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) + colorCode = 3066993 + } else { + message = fmt.Sprintf("An alert for **%s** has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) + colorCode = 15158332 + } + var formattedConditionResults string + for _, conditionResult := range result.ConditionResults { + var prefix string + if conditionResult.Success { + prefix = ":white_check_mark:" + } else { + prefix = ":x:" + } + formattedConditionResults += fmt.Sprintf("%s - `%s`\n", prefix, conditionResult.Condition) + } + var description string + if alertDescription := alert.GetDescription(); len(alertDescription) > 0 { + description = ":\n> " + alertDescription + } + title := ":helmet_with_white_cross: Gatus" + if cfg.Title != "" { + title = cfg.Title + } + body := Body{ + Content: "", + Embeds: []Embed{ + { + Title: title, + Description: message + description, + Color: colorCode, + }, + }, + } + if len(formattedConditionResults) > 0 { + body.Embeds[0].Fields = append(body.Embeds[0].Fields, Field{ + Name: "Condition results", + Value: formattedConditionResults, + Inline: false, + }) + } + bodyAsJSON, _ := json.Marshal(body) + return bodyAsJSON +} + +// GetDefaultAlert returns the provider's default alert configuration +func (provider *AlertProvider) GetDefaultAlert() *alert.Alert { + return provider.DefaultAlert +} + +// GetConfig returns the configuration for the provider with the overrides applied +func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) { + cfg := provider.DefaultConfig + // Handle group overrides + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if group == override.Group { + cfg.Merge(&override.Config) + break + } + } + } + // Handle alert overrides + if len(alert.ProviderOverride) != 0 { + overrideConfig := Config{} + if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil { + return nil, err + } + cfg.Merge(&overrideConfig) + } + // Validate the configuration + err := cfg.Validate() + return &cfg, err +} + +// ValidateOverrides validates the alert's provider override and, if present, the group override +func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error { + _, err := provider.GetConfig(group, alert) + return err +} diff --git a/alerting/provider/discord/discord_test.go b/alerting/provider/discord/discord_test.go new file mode 100644 index 00000000..20aaed6d --- /dev/null +++ b/alerting/provider/discord/discord_test.go @@ -0,0 +1,332 @@ +package discord + +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/test" +) + +func TestAlertProvider_Validate(t *testing.T) { + invalidProvider := AlertProvider{DefaultConfig: Config{WebhookURL: ""}} + if err := invalidProvider.Validate(); err == nil { + t.Error("provider shouldn't have been valid") + } + validProvider := AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}} + if err := validProvider.Validate(); err != nil { + t.Error("provider should've been valid") + } +} + +func TestAlertProvider_ValidateWithOverride(t *testing.T) { + providerWithInvalidOverrideGroup := AlertProvider{ + Overrides: []Override{ + { + Config: Config{WebhookURL: "http://example.com"}, + Group: "", + }, + }, + } + if err := providerWithInvalidOverrideGroup.Validate(); err == nil { + t.Error("provider Group shouldn't have been valid") + } + providerWithInvalidOverrideTo := AlertProvider{ + Overrides: []Override{ + { + Config: Config{WebhookURL: ""}, + Group: "group", + }, + }, + } + if err := providerWithInvalidOverrideTo.Validate(); err == nil { + t.Error("provider integration key shouldn't have been valid") + } + providerWithValidOverride := AlertProvider{ + DefaultConfig: Config{ + WebhookURL: "http://example.com", + }, + Overrides: []Override{ + { + Config: Config{WebhookURL: "http://example.com"}, + Group: "group", + }, + }, + } + if err := providerWithValidOverride.Validate(); err != nil { + t.Error("provider should've been valid") + } +} + +func TestAlertProvider_Send(t *testing.T) { + defer client.InjectHTTPClient(nil) + firstDescription := "description-1" + secondDescription := "description-2" + title := "provider-title" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + MockRoundTripper test.MockRoundTripper + ExpectedError bool + }{ + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "triggered-error", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + { + Name: "resolved", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "resolved-error", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + { + Name: "triggered-with-modified-title", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com", Title: title}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "triggered-with-webhook-override", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3, ProviderOverride: map[string]any{"webhook-url": "http://example01.com"}}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) + err := scenario.Provider.Send( + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if scenario.ExpectedError && err == nil { + t.Error("expected error, got none") + } + if !scenario.ExpectedError && err != nil { + t.Error("expected no error, got", err.Error()) + } + }) + } +} + +func TestAlertProvider_buildRequestBody(t *testing.T) { + firstDescription := "description-1" + secondDescription := "description-2" + title := "provider-title" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + NoConditions bool + Resolved bool + ExpectedBody string + }{ + { + Name: "triggered", + Provider: AlertProvider{}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: "{\"content\":\"\",\"embeds\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"description\":\"An alert for **endpoint-name** has been triggered due to having failed 3 time(s) in a row:\\n\\u003e description-1\",\"color\":15158332,\"fields\":[{\"name\":\"Condition results\",\"value\":\":x: - `[CONNECTED] == true`\\n:x: - `[STATUS] == 200`\\n:x: - `[BODY] != \\\"\\\"`\\n\",\"inline\":false}]}]}", + }, + { + Name: "resolved", + Provider: AlertProvider{}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: "{\"content\":\"\",\"embeds\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"description\":\"An alert for **endpoint-name** has been resolved after passing successfully 5 time(s) in a row:\\n\\u003e description-2\",\"color\":3066993,\"fields\":[{\"name\":\"Condition results\",\"value\":\":white_check_mark: - `[CONNECTED] == true`\\n:white_check_mark: - `[STATUS] == 200`\\n:white_check_mark: - `[BODY] != \\\"\\\"`\\n\",\"inline\":false}]}]}", + }, + { + Name: "triggered-with-modified-title", + Provider: AlertProvider{DefaultConfig: Config{Title: title}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: "{\"content\":\"\",\"embeds\":[{\"title\":\"provider-title\",\"description\":\"An alert for **endpoint-name** has been triggered due to having failed 3 time(s) in a row:\\n\\u003e description-1\",\"color\":15158332,\"fields\":[{\"name\":\"Condition results\",\"value\":\":x: - `[CONNECTED] == true`\\n:x: - `[STATUS] == 200`\\n:x: - `[BODY] != \\\"\\\"`\\n\",\"inline\":false}]}]}", + }, + { + Name: "triggered-with-no-conditions", + NoConditions: true, + Provider: AlertProvider{DefaultConfig: Config{Title: title}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: "{\"content\":\"\",\"embeds\":[{\"title\":\"provider-title\",\"description\":\"An alert for **endpoint-name** has been triggered due to having failed 3 time(s) in a row:\\n\\u003e description-1\",\"color\":15158332}]}", + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + var conditionResults []*endpoint.ConditionResult + if !scenario.NoConditions { + conditionResults = []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + {Condition: "[BODY] != \"\"", Success: scenario.Resolved}, + } + } + body := scenario.Provider.buildRequestBody( + &scenario.Provider.DefaultConfig, + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: conditionResults, + }, + scenario.Resolved, + ) + if string(body) != scenario.ExpectedBody { + t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body) + } + out := make(map[string]interface{}) + if err := json.Unmarshal(body, &out); err != nil { + t.Error("expected body to be valid JSON, got error:", err.Error()) + } + }) + } +} + +func TestAlertProvider_GetDefaultAlert(t *testing.T) { + if (&AlertProvider{DefaultAlert: &alert.Alert{}}).GetDefaultAlert() == nil { + t.Error("expected default alert to be not nil") + } + if (&AlertProvider{DefaultAlert: nil}).GetDefaultAlert() != nil { + t.Error("expected default alert to be nil") + } +} + +func TestAlertProvider_GetConfig(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + InputGroup string + InputAlert alert.Alert + ExpectedOutput Config + }{ + { + Name: "provider-no-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: nil, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://example.com"}, + }, + { + Name: "provider-no-override-specify-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: nil, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://example.com"}, + }, + { + Name: "provider-with-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{WebhookURL: "http://group-example.com"}, + }, + }, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://example.com"}, + }, + { + Name: "provider-with-override-specify-group-should-override", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{WebhookURL: "http://group-example.com"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://group-example.com"}, + }, + { + Name: "provider-with-group-override-and-alert-override--alert-override-should-take-precedence", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{WebhookURL: "http://group-example.com"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{ProviderOverride: map[string]any{"webhook-url": "http://alert-example.com"}}, + ExpectedOutput: Config{WebhookURL: "http://alert-example.com"}, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + got, err := scenario.Provider.GetConfig(scenario.InputGroup, &scenario.InputAlert) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if got.WebhookURL != scenario.ExpectedOutput.WebhookURL { + t.Errorf("expected webhook URL to be %s, got %s", scenario.ExpectedOutput.WebhookURL, got.WebhookURL) + } + // Test ValidateOverrides as well, since it really just calls GetConfig + if err = scenario.Provider.ValidateOverrides(scenario.InputGroup, &scenario.InputAlert); err != nil { + t.Errorf("unexpected error: %s", err) + } + }) + } +} diff --git a/alerting/provider/email/email.go b/alerting/provider/email/email.go new file mode 100644 index 00000000..79bece3f --- /dev/null +++ b/alerting/provider/email/email.go @@ -0,0 +1,206 @@ +package email + +import ( + "crypto/tls" + "errors" + "fmt" + "math" + "strings" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + gomail "gopkg.in/mail.v2" + "gopkg.in/yaml.v3" +) + +var ( + ErrDuplicateGroupOverride = errors.New("duplicate group override") + ErrMissingFromOrToFields = errors.New("from and to fields are required") + ErrInvalidPort = errors.New("port must be between 1 and 65535 inclusively") + ErrMissingHost = errors.New("host is required") +) + +type Config struct { + From string `yaml:"from"` + Username string `yaml:"username"` + Password string `yaml:"password"` + Host string `yaml:"host"` + Port int `yaml:"port"` + To string `yaml:"to"` + + // ClientConfig is the configuration of the client used to communicate with the provider's target + ClientConfig *client.Config `yaml:"client,omitempty"` +} + +func (cfg *Config) Validate() error { + if len(cfg.From) == 0 || len(cfg.To) == 0 { + return ErrMissingFromOrToFields + } + if cfg.Port < 1 || cfg.Port > math.MaxUint16 { + return ErrInvalidPort + } + if len(cfg.Host) == 0 { + return ErrMissingHost + } + return nil +} + +func (cfg *Config) Merge(override *Config) { + if override.ClientConfig != nil { + cfg.ClientConfig = override.ClientConfig + } + if len(override.From) > 0 { + cfg.From = override.From + } + if len(override.Username) > 0 { + cfg.Username = override.Username + } + if len(override.Password) > 0 { + cfg.Password = override.Password + } + if len(override.Host) > 0 { + cfg.Host = override.Host + } + if override.Port > 0 { + cfg.Port = override.Port + } + if len(override.To) > 0 { + cfg.To = override.To + } +} + +// AlertProvider is the configuration necessary for sending an alert using SMTP +type AlertProvider struct { + DefaultConfig Config `yaml:",inline"` + + // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type + DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` + + // Overrides is a list of Override that may be prioritized over the default configuration + Overrides []Override `yaml:"overrides,omitempty"` +} + +// Override is a case under which the default integration is overridden +type Override struct { + Group string `yaml:"group"` + Config `yaml:",inline"` +} + +// Validate the provider's configuration +func (provider *AlertProvider) Validate() error { + registeredGroups := make(map[string]bool) + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if isAlreadyRegistered := registeredGroups[override.Group]; isAlreadyRegistered || override.Group == "" || len(override.To) == 0 { + return ErrDuplicateGroupOverride + } + registeredGroups[override.Group] = true + } + } + return provider.DefaultConfig.Validate() +} + +// Send an alert using the provider +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + cfg, err := provider.GetConfig(ep.Group, alert) + if err != nil { + return err + } + var username string + if len(cfg.Username) > 0 { + username = cfg.Username + } else { + username = cfg.From + } + subject, body := provider.buildMessageSubjectAndBody(ep, alert, result, resolved) + m := gomail.NewMessage() + m.SetHeader("From", cfg.From) + m.SetHeader("To", strings.Split(cfg.To, ",")...) + m.SetHeader("Subject", subject) + m.SetBody("text/plain", body) + var d *gomail.Dialer + if len(cfg.Password) == 0 { + // Get the domain in the From address + localName := "localhost" + fromParts := strings.Split(cfg.From, `@`) + if len(fromParts) == 2 { + localName = fromParts[1] + } + // Create a dialer with no authentication + d = &gomail.Dialer{Host: cfg.Host, Port: cfg.Port, LocalName: localName} + } else { + // Create an authenticated dialer + d = gomail.NewDialer(cfg.Host, cfg.Port, username, cfg.Password) + } + if cfg.ClientConfig != nil && cfg.ClientConfig.Insecure { + d.TLSConfig = &tls.Config{InsecureSkipVerify: true} + } + return d.DialAndSend(m) +} + +// buildMessageSubjectAndBody builds the message subject and body +func (provider *AlertProvider) buildMessageSubjectAndBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) (string, string) { + var subject, message string + if resolved { + subject = fmt.Sprintf("[%s] Alert resolved", ep.DisplayName()) + message = fmt.Sprintf("An alert for %s has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) + } else { + subject = fmt.Sprintf("[%s] Alert triggered", ep.DisplayName()) + message = fmt.Sprintf("An alert for %s has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) + } + var formattedConditionResults string + if len(result.ConditionResults) > 0 { + formattedConditionResults = "\n\nCondition results:\n" + for _, conditionResult := range result.ConditionResults { + var prefix string + if conditionResult.Success { + prefix = "✅" + } else { + prefix = "❌" + } + formattedConditionResults += fmt.Sprintf("%s %s\n", prefix, conditionResult.Condition) + } + } + var description string + if alertDescription := alert.GetDescription(); len(alertDescription) > 0 { + description = "\n\nAlert description: " + alertDescription + } + return subject, message + description + formattedConditionResults +} + +// GetDefaultAlert returns the provider's default alert configuration +func (provider *AlertProvider) GetDefaultAlert() *alert.Alert { + return provider.DefaultAlert +} + +// GetConfig returns the configuration for the provider with the overrides applied +func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) { + cfg := provider.DefaultConfig + // Handle group overrides + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if group == override.Group { + cfg.Merge(&override.Config) + break + } + } + } + // Handle alert overrides + if len(alert.ProviderOverride) != 0 { + overrideConfig := Config{} + if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil { + return nil, err + } + cfg.Merge(&overrideConfig) + } + // Validate the configuration + err := cfg.Validate() + return &cfg, err +} + +// ValidateOverrides validates the alert's provider override and, if present, the group override +func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error { + _, err := provider.GetConfig(group, alert) + return err +} diff --git a/alerting/provider/email/email_test.go b/alerting/provider/email/email_test.go new file mode 100644 index 00000000..00e398aa --- /dev/null +++ b/alerting/provider/email/email_test.go @@ -0,0 +1,232 @@ +package email + +import ( + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/config/endpoint" +) + +func TestAlertProvider_Validate(t *testing.T) { + invalidProvider := AlertProvider{} + if err := invalidProvider.Validate(); err == nil { + t.Error("provider shouldn't have been valid") + } + validProvider := AlertProvider{DefaultConfig: Config{From: "from@example.com", Password: "password", Host: "smtp.gmail.com", Port: 587, To: "to@example.com"}} + if err := validProvider.Validate(); err != nil { + t.Error("provider should've been valid") + } +} + +func TestAlertProvider_ValidateWithNoCredentials(t *testing.T) { + validProvider := AlertProvider{DefaultConfig: Config{From: "from@example.com", Host: "smtp-relay.gmail.com", Port: 587, To: "to@example.com"}} + if err := validProvider.Validate(); err != nil { + t.Error("provider should've been valid") + } +} + +func TestAlertProvider_ValidateWithOverride(t *testing.T) { + providerWithInvalidOverrideGroup := AlertProvider{ + Overrides: []Override{ + { + Config: Config{To: "to@example.com"}, + Group: "", + }, + }, + } + if err := providerWithInvalidOverrideGroup.Validate(); err == nil { + t.Error("provider Group shouldn't have been valid") + } + providerWithInvalidOverrideTo := AlertProvider{ + Overrides: []Override{ + { + Config: Config{To: ""}, + Group: "group", + }, + }, + } + if err := providerWithInvalidOverrideTo.Validate(); err == nil { + t.Error("provider integration key shouldn't have been valid") + } + providerWithValidOverride := AlertProvider{ + DefaultConfig: Config{ + From: "from@example.com", + Password: "password", + Host: "smtp.gmail.com", + Port: 587, + To: "to@example.com", + }, + Overrides: []Override{ + { + Config: Config{To: "to@example.com"}, + Group: "group", + }, + }, + } + if err := providerWithValidOverride.Validate(); err != nil { + t.Error("provider should've been valid") + } +} + +func TestAlertProvider_buildRequestBody(t *testing.T) { + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + ExpectedSubject string + ExpectedBody string + }{ + { + Name: "triggered", + Provider: AlertProvider{}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedSubject: "[endpoint-name] Alert triggered", + ExpectedBody: "An alert for endpoint-name has been triggered due to having failed 3 time(s) in a row\n\nAlert description: description-1\n\nCondition results:\n❌ [CONNECTED] == true\n❌ [STATUS] == 200\n", + }, + { + Name: "resolved", + Provider: AlertProvider{}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedSubject: "[endpoint-name] Alert resolved", + ExpectedBody: "An alert for endpoint-name has been resolved after passing successfully 5 time(s) in a row\n\nAlert description: description-2\n\nCondition results:\n✅ [CONNECTED] == true\n✅ [STATUS] == 200\n", + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + subject, body := scenario.Provider.buildMessageSubjectAndBody( + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if subject != scenario.ExpectedSubject { + t.Errorf("expected subject to be %s, got %s", scenario.ExpectedSubject, subject) + } + if body != scenario.ExpectedBody { + t.Errorf("expected body to be %s, got %s", scenario.ExpectedBody, body) + } + }) + } +} + +func TestAlertProvider_GetDefaultAlert(t *testing.T) { + if (&AlertProvider{DefaultAlert: &alert.Alert{}}).GetDefaultAlert() == nil { + t.Error("expected default alert to be not nil") + } + if (&AlertProvider{DefaultAlert: nil}).GetDefaultAlert() != nil { + t.Error("expected default alert to be nil") + } +} + +func TestAlertProvider_GetConfig(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + InputGroup string + InputAlert alert.Alert + ExpectedOutput Config + }{ + { + Name: "provider-no-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{From: "from@example.com", To: "to@example.com", Host: "smtp.gmail.com", Port: 587, Password: "password"}, + Overrides: nil, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{From: "from@example.com", To: "to@example.com", Host: "smtp.gmail.com", Port: 587, Password: "password"}, + }, + { + Name: "provider-no-override-specify-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{From: "from@example.com", To: "to@example.com", Host: "smtp.gmail.com", Port: 587, Password: "password"}, + Overrides: nil, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{From: "from@example.com", To: "to@example.com", Host: "smtp.gmail.com", Port: 587, Password: "password"}, + }, + { + Name: "provider-with-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{From: "from@example.com", To: "to@example.com", Host: "smtp.gmail.com", Port: 587, Password: "password"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{To: "to01@example.com"}, + }, + }, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{From: "from@example.com", To: "to@example.com", Host: "smtp.gmail.com", Port: 587, Password: "password"}, + }, + { + Name: "provider-with-override-specify-group-should-override", + Provider: AlertProvider{ + DefaultConfig: Config{From: "from@example.com", To: "to@example.com", Host: "smtp.gmail.com", Port: 587, Password: "password"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{To: "group-to@example.com"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{From: "from@example.com", To: "group-to@example.com", Host: "smtp.gmail.com", Port: 587, Password: "password"}, + }, + { + Name: "provider-with-group-override-and-alert-override--alert-override-should-take-precedence", + Provider: AlertProvider{ + DefaultConfig: Config{From: "from@example.com", To: "to@example.com", Host: "smtp.gmail.com", Port: 587, Password: "password"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{To: "group-to@example.com"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{ProviderOverride: map[string]any{"to": "alert-to@example.com", "host": "smtp.example.com", "port": 588, "password": "hunter2"}}, + ExpectedOutput: Config{From: "from@example.com", To: "alert-to@example.com", Host: "smtp.example.com", Port: 588, Password: "hunter2"}, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + got, err := scenario.Provider.GetConfig(scenario.InputGroup, &scenario.InputAlert) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if got.From != scenario.ExpectedOutput.From { + t.Errorf("expected from to be %s, got %s", scenario.ExpectedOutput.From, got.From) + } + if got.To != scenario.ExpectedOutput.To { + t.Errorf("expected to be %s, got %s", scenario.ExpectedOutput.To, got.To) + } + if got.Host != scenario.ExpectedOutput.Host { + t.Errorf("expected host to be %s, got %s", scenario.ExpectedOutput.Host, got.Host) + } + if got.Port != scenario.ExpectedOutput.Port { + t.Errorf("expected port to be %d, got %d", scenario.ExpectedOutput.Port, got.Port) + } + if got.Password != scenario.ExpectedOutput.Password { + t.Errorf("expected password to be %s, got %s", scenario.ExpectedOutput.Password, got.Password) + } + // Test ValidateOverrides as well, since it really just calls GetConfig + if err = scenario.Provider.ValidateOverrides(scenario.InputGroup, &scenario.InputAlert); err != nil { + t.Errorf("unexpected error: %s", err) + } + }) + } +} diff --git a/alerting/provider/gitea/gitea.go b/alerting/provider/gitea/gitea.go new file mode 100644 index 00000000..5dd3315d --- /dev/null +++ b/alerting/provider/gitea/gitea.go @@ -0,0 +1,217 @@ +package gitea + +import ( + "crypto/tls" + "errors" + "fmt" + "net/http" + "net/url" + "strings" + + "code.gitea.io/sdk/gitea" + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "gopkg.in/yaml.v3" +) + +var ( + ErrRepositoryURLNotSet = errors.New("repository-url not set") + ErrInvalidRepositoryURL = errors.New("invalid repository-url") + ErrTokenNotSet = errors.New("token not set") +) + +type Config struct { + RepositoryURL string `yaml:"repository-url"` // The URL of the Gitea repository to create issues in + Token string `yaml:"token"` // Token requires at least RW on issues and RO on metadata + Assignees []string `yaml:"assignees,omitempty"` // Assignees is a list of users to assign the issue to + + username string + repositoryOwner string + repositoryName string + giteaClient *gitea.Client + + // ClientConfig is the configuration of the client used to communicate with the provider's target + ClientConfig *client.Config `yaml:"client,omitempty"` +} + +func (cfg *Config) Validate() error { + if len(cfg.RepositoryURL) == 0 { + return ErrRepositoryURLNotSet + } + if len(cfg.Token) == 0 { + return ErrTokenNotSet + } + // Validate format of the repository URL + repositoryURL, err := url.Parse(cfg.RepositoryURL) + if err != nil { + return err + } + baseURL := repositoryURL.Scheme + "://" + repositoryURL.Host + pathParts := strings.Split(repositoryURL.Path, "/") + if len(pathParts) != 3 { + return ErrInvalidRepositoryURL + } + if cfg.repositoryOwner == pathParts[1] && cfg.repositoryName == pathParts[2] && cfg.giteaClient != nil { + // Already validated, let's skip the rest of the validation to avoid unnecessary API calls + return nil + } + cfg.repositoryOwner = pathParts[1] + cfg.repositoryName = pathParts[2] + opts := []gitea.ClientOption{ + gitea.SetToken(cfg.Token), + } + if cfg.ClientConfig != nil && cfg.ClientConfig.Insecure { + // add new http client for skip verify + httpClient := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + } + opts = append(opts, gitea.SetHTTPClient(httpClient)) + } + cfg.giteaClient, err = gitea.NewClient(baseURL, opts...) + if err != nil { + return err + } + user, _, err := cfg.giteaClient.GetMyUserInfo() + if err != nil { + return err + } + cfg.username = user.UserName + return nil +} + +func (cfg *Config) Merge(override *Config) { + if override.ClientConfig != nil { + cfg.ClientConfig = override.ClientConfig + } + if len(override.RepositoryURL) > 0 { + cfg.RepositoryURL = override.RepositoryURL + } + if len(override.Token) > 0 { + cfg.Token = override.Token + } + if len(override.Assignees) > 0 { + cfg.Assignees = override.Assignees + } +} + +// AlertProvider is the configuration necessary for sending an alert using Discord +type AlertProvider struct { + DefaultConfig Config `yaml:",inline"` + + // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type + DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` +} + +// Validate the provider's configuration +func (provider *AlertProvider) Validate() error { + return provider.DefaultConfig.Validate() +} + +// Send creates an issue in the designed RepositoryURL if the resolved parameter passed is false, +// or closes the relevant issue(s) if the resolved parameter passed is true. +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + cfg, err := provider.GetConfig(ep.Group, alert) + if err != nil { + return err + } + title := "alert(gatus): " + ep.DisplayName() + if !resolved { + _, _, err = cfg.giteaClient.CreateIssue( + cfg.repositoryOwner, + cfg.repositoryName, + gitea.CreateIssueOption{ + Title: title, + Body: provider.buildIssueBody(ep, alert, result), + Assignees: cfg.Assignees, + }, + ) + if err != nil { + return fmt.Errorf("failed to create issue: %w", err) + } + return nil + } + issues, _, err := cfg.giteaClient.ListRepoIssues( + cfg.repositoryOwner, + cfg.repositoryName, + gitea.ListIssueOption{ + State: gitea.StateOpen, + CreatedBy: cfg.username, + ListOptions: gitea.ListOptions{ + Page: 100, + }, + }, + ) + if err != nil { + return fmt.Errorf("failed to list issues: %w", err) + } + for _, issue := range issues { + if issue.Title == title { + stateClosed := gitea.StateClosed + _, _, err = cfg.giteaClient.EditIssue( + cfg.repositoryOwner, + cfg.repositoryName, + issue.ID, + gitea.EditIssueOption{ + State: &stateClosed, + }, + ) + if err != nil { + return fmt.Errorf("failed to close issue: %w", err) + } + } + } + return nil +} + +// buildIssueBody builds the body of the issue +func (provider *AlertProvider) buildIssueBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result) string { + var formattedConditionResults string + if len(result.ConditionResults) > 0 { + formattedConditionResults = "\n\n## Condition results\n" + for _, conditionResult := range result.ConditionResults { + var prefix string + if conditionResult.Success { + prefix = ":white_check_mark:" + } else { + prefix = ":x:" + } + formattedConditionResults += fmt.Sprintf("- %s - `%s`\n", prefix, conditionResult.Condition) + } + } + var description string + if alertDescription := alert.GetDescription(); len(alertDescription) > 0 { + description = ":\n> " + alertDescription + } + message := fmt.Sprintf("An alert for **%s** has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) + return message + description + formattedConditionResults +} + +// GetDefaultAlert returns the provider's default alert configuration +func (provider *AlertProvider) GetDefaultAlert() *alert.Alert { + return provider.DefaultAlert +} + +// GetConfig returns the configuration for the provider with the overrides applied +func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) { + cfg := provider.DefaultConfig + // Handle alert overrides + if len(alert.ProviderOverride) != 0 { + overrideConfig := Config{} + if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil { + return nil, err + } + cfg.Merge(&overrideConfig) + } + // Validate the configuration (we're returning the cfg here even if there's an error mostly for testing purposes) + err := cfg.Validate() + return &cfg, err +} + +// ValidateOverrides validates the alert's provider override and, if present, the group override +func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error { + _, err := provider.GetConfig(group, alert) + return err +} diff --git a/alerting/provider/gitea/gitea_test.go b/alerting/provider/gitea/gitea_test.go new file mode 100644 index 00000000..b2c601ca --- /dev/null +++ b/alerting/provider/gitea/gitea_test.go @@ -0,0 +1,229 @@ +package gitea + +import ( + "net/http" + "strings" + "testing" + + "code.gitea.io/sdk/gitea" + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/test" +) + +func TestAlertProvider_Validate(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + ExpectedError bool + }{ + { + Name: "invalid", + Provider: AlertProvider{DefaultConfig: Config{RepositoryURL: "", Token: ""}}, + ExpectedError: true, + }, + { + Name: "invalid-token", + Provider: AlertProvider{DefaultConfig: Config{RepositoryURL: "https://gitea.com/TwiN/test", Token: "12345"}}, + ExpectedError: true, + }, + { + Name: "missing-repository-name", + Provider: AlertProvider{DefaultConfig: Config{RepositoryURL: "https://gitea.com/TwiN", Token: "12345"}}, + ExpectedError: true, + }, + { + Name: "enterprise-client", + Provider: AlertProvider{DefaultConfig: Config{RepositoryURL: "https://gitea.example.com/TwiN/test", Token: "12345"}}, + ExpectedError: false, + }, + { + Name: "invalid-url", + Provider: AlertProvider{DefaultConfig: Config{RepositoryURL: "gitea.com/TwiN/test", Token: "12345"}}, + ExpectedError: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + err := scenario.Provider.Validate() + if scenario.ExpectedError && err == nil { + t.Error("expected error, got none") + } + if !scenario.ExpectedError && err != nil && !strings.Contains(err.Error(), "user does not exist") && !strings.Contains(err.Error(), "no such host") { + t.Error("expected no error, got", err.Error()) + } + }) + } +} + +func TestAlertProvider_Send(t *testing.T) { + defer client.InjectHTTPClient(nil) + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + MockRoundTripper test.MockRoundTripper + ExpectedError bool + }{ + { + Name: "triggered-error", + Provider: AlertProvider{DefaultConfig: Config{RepositoryURL: "https://gitea.com/TwiN/test", Token: "12345"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedError: true, + }, + { + Name: "resolved-error", + Provider: AlertProvider{DefaultConfig: Config{RepositoryURL: "https://gitea.com/TwiN/test", Token: "12345"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedError: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + cfg, err := scenario.Provider.GetConfig("", &scenario.Alert) + if err != nil && !strings.Contains(err.Error(), "user does not exist") && !strings.Contains(err.Error(), "no such host") { + t.Error("expected no error, got", err.Error()) + } + cfg.giteaClient, _ = gitea.NewClient("https://gitea.com") + client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) + err = scenario.Provider.Send( + &endpoint.Endpoint{Name: "endpoint-name", Group: "endpoint-group"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if scenario.ExpectedError && err == nil { + t.Error("expected error, got none") + } + if !scenario.ExpectedError && err != nil { + t.Error("expected no error, got", err.Error()) + } + }) + } +} + +func TestAlertProvider_buildRequestBody(t *testing.T) { + firstDescription := "description-1" + scenarios := []struct { + Name string + Endpoint endpoint.Endpoint + Provider AlertProvider + Alert alert.Alert + NoConditions bool + ExpectedBody string + }{ + { + Name: "triggered", + Endpoint: endpoint.Endpoint{Name: "endpoint-name", URL: "https://example.org"}, + Provider: AlertProvider{}, + Alert: alert.Alert{Description: &firstDescription, FailureThreshold: 3}, + ExpectedBody: "An alert for **endpoint-name** has been triggered due to having failed 3 time(s) in a row:\n> description-1\n\n## Condition results\n- :white_check_mark: - `[CONNECTED] == true`\n- :x: - `[STATUS] == 200`", + }, + { + Name: "triggered-with-no-description", + Endpoint: endpoint.Endpoint{Name: "endpoint-name", URL: "https://example.org"}, + Provider: AlertProvider{}, + Alert: alert.Alert{FailureThreshold: 10}, + ExpectedBody: "An alert for **endpoint-name** has been triggered due to having failed 10 time(s) in a row\n\n## Condition results\n- :white_check_mark: - `[CONNECTED] == true`\n- :x: - `[STATUS] == 200`", + }, + { + Name: "triggered-with-no-conditions", + NoConditions: true, + Endpoint: endpoint.Endpoint{Name: "endpoint-name", URL: "https://example.org"}, + Provider: AlertProvider{}, + Alert: alert.Alert{Description: &firstDescription, FailureThreshold: 10}, + ExpectedBody: "An alert for **endpoint-name** has been triggered due to having failed 10 time(s) in a row:\n> description-1", + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + var conditionResults []*endpoint.ConditionResult + if !scenario.NoConditions { + conditionResults = []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: true}, + {Condition: "[STATUS] == 200", Success: false}, + } + } + body := scenario.Provider.buildIssueBody( + &scenario.Endpoint, + &scenario.Alert, + &endpoint.Result{ConditionResults: conditionResults}, + ) + if strings.TrimSpace(body) != strings.TrimSpace(scenario.ExpectedBody) { + t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body) + } + }) + } +} + +func TestAlertProvider_GetDefaultAlert(t *testing.T) { + if (&AlertProvider{DefaultAlert: &alert.Alert{}}).GetDefaultAlert() == nil { + t.Error("expected default alert to be not nil") + } + if (&AlertProvider{DefaultAlert: nil}).GetDefaultAlert() != nil { + t.Error("expected default alert to be nil") + } +} + +func TestAlertProvider_GetConfig(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + InputAlert alert.Alert + ExpectedOutput Config + }{ + { + Name: "provider-no-override-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{RepositoryURL: "https://gitea.com/TwiN/test", Token: "12345"}, + }, + InputAlert: alert.Alert{}, + ExpectedOutput: Config{RepositoryURL: "https://gitea.com/TwiN/test", Token: "12345"}, + }, + { + Name: "provider-with-alert-override", + Provider: AlertProvider{ + DefaultConfig: Config{RepositoryURL: "https://gitea.com/TwiN/test", Token: "12345"}, + }, + InputAlert: alert.Alert{ProviderOverride: map[string]any{"repository-url": "https://gitea.com/TwiN/alert-test", "token": "54321", "assignees": []string{"TwiN"}}}, + ExpectedOutput: Config{RepositoryURL: "https://gitea.com/TwiN/alert-test", Token: "54321", Assignees: []string{"TwiN"}}, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + got, err := scenario.Provider.GetConfig("", &scenario.InputAlert) + if err != nil && !strings.Contains(err.Error(), "user does not exist") && !strings.Contains(err.Error(), "no such host") { + t.Fatalf("unexpected error: %s", err) + } + if got.RepositoryURL != scenario.ExpectedOutput.RepositoryURL { + t.Errorf("expected repository URL %s, got %s", scenario.ExpectedOutput.RepositoryURL, got.RepositoryURL) + } + if got.Token != scenario.ExpectedOutput.Token { + t.Errorf("expected token %s, got %s", scenario.ExpectedOutput.Token, got.Token) + } + if len(got.Assignees) != len(scenario.ExpectedOutput.Assignees) { + t.Errorf("expected %d assignees, got %d", len(scenario.ExpectedOutput.Assignees), len(got.Assignees)) + } + for i, assignee := range got.Assignees { + if assignee != scenario.ExpectedOutput.Assignees[i] { + t.Errorf("expected assignee %s, got %s", scenario.ExpectedOutput.Assignees[i], assignee) + } + } + // Test ValidateOverrides as well, since it really just calls GetConfig + if err = scenario.Provider.ValidateOverrides("", &scenario.InputAlert); err != nil && !strings.Contains(err.Error(), "user does not exist") { + t.Errorf("unexpected error: %s", err) + } + }) + } +} diff --git a/alerting/provider/github/github.go b/alerting/provider/github/github.go new file mode 100644 index 00000000..b89131a7 --- /dev/null +++ b/alerting/provider/github/github.go @@ -0,0 +1,190 @@ +package github + +import ( + "context" + "errors" + "fmt" + "net/url" + "strings" + "time" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/google/go-github/v48/github" + "golang.org/x/oauth2" + "gopkg.in/yaml.v3" +) + +var ( + ErrRepositoryURLNotSet = errors.New("repository-url not set") + ErrInvalidRepositoryURL = errors.New("invalid repository-url") + ErrTokenNotSet = errors.New("token not set") +) + +type Config struct { + RepositoryURL string `yaml:"repository-url"` // The URL of the GitHub repository to create issues in + Token string `yaml:"token"` // Token requires at least RW on issues and RO on metadata + + username string + repositoryOwner string + repositoryName string + githubClient *github.Client +} + +func (cfg *Config) Validate() error { + if len(cfg.RepositoryURL) == 0 { + return ErrRepositoryURLNotSet + } + if len(cfg.Token) == 0 { + return ErrTokenNotSet + } + // Validate format of the repository URL + repositoryURL, err := url.Parse(cfg.RepositoryURL) + if err != nil { + return err + } + baseURL := repositoryURL.Scheme + "://" + repositoryURL.Host + pathParts := strings.Split(repositoryURL.Path, "/") + if len(pathParts) != 3 { + return ErrInvalidRepositoryURL + } + if cfg.repositoryOwner == pathParts[1] && cfg.repositoryName == pathParts[2] && cfg.githubClient != nil { + // Already validated, let's skip the rest of the validation to avoid unnecessary API calls + return nil + } + cfg.repositoryOwner = pathParts[1] + cfg.repositoryName = pathParts[2] + // Create oauth2 HTTP client with GitHub token + httpClientWithStaticTokenSource := oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(&oauth2.Token{ + AccessToken: cfg.Token, + })) + // Create GitHub client + if baseURL == "https://github.com" { + cfg.githubClient = github.NewClient(httpClientWithStaticTokenSource) + } else { + cfg.githubClient, err = github.NewEnterpriseClient(baseURL, baseURL, httpClientWithStaticTokenSource) + if err != nil { + return fmt.Errorf("failed to create enterprise GitHub client: %w", err) + } + } + // Retrieve the username once to validate that the token is valid + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + user, _, err := cfg.githubClient.Users.Get(ctx, "") + if err != nil { + return fmt.Errorf("failed to retrieve GitHub user: %w", err) + } + cfg.username = *user.Login + return nil +} + +func (cfg *Config) Merge(override *Config) { + if len(override.RepositoryURL) > 0 { + cfg.RepositoryURL = override.RepositoryURL + } + if len(override.Token) > 0 { + cfg.Token = override.Token + } +} + +// AlertProvider is the configuration necessary for sending an alert using Discord +type AlertProvider struct { + DefaultConfig Config `yaml:",inline"` + + // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type + DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` +} + +// Validate the provider's configuration +func (provider *AlertProvider) Validate() error { + return provider.DefaultConfig.Validate() +} + +// Send creates an issue in the designed RepositoryURL if the resolved parameter passed is false, +// or closes the relevant issue(s) if the resolved parameter passed is true. +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + cfg, err := provider.GetConfig(ep.Group, alert) + if err != nil { + return err + } + title := "alert(gatus): " + ep.DisplayName() + if !resolved { + _, _, err := cfg.githubClient.Issues.Create(context.Background(), cfg.repositoryOwner, cfg.repositoryName, &github.IssueRequest{ + Title: github.String(title), + Body: github.String(provider.buildIssueBody(ep, alert, result)), + }) + if err != nil { + return fmt.Errorf("failed to create issue: %w", err) + } + } else { + issues, _, err := cfg.githubClient.Issues.ListByRepo(context.Background(), cfg.repositoryOwner, cfg.repositoryName, &github.IssueListByRepoOptions{ + State: "open", + Creator: cfg.username, + ListOptions: github.ListOptions{PerPage: 100}, + }) + if err != nil { + return fmt.Errorf("failed to list issues: %w", err) + } + for _, issue := range issues { + if *issue.Title == title { + _, _, err = cfg.githubClient.Issues.Edit(context.Background(), cfg.repositoryOwner, cfg.repositoryName, *issue.Number, &github.IssueRequest{ + State: github.String("closed"), + }) + if err != nil { + return fmt.Errorf("failed to close issue: %w", err) + } + } + } + } + return nil +} + +// buildIssueBody builds the body of the issue +func (provider *AlertProvider) buildIssueBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result) string { + var formattedConditionResults string + if len(result.ConditionResults) > 0 { + formattedConditionResults = "\n\n## Condition results\n" + for _, conditionResult := range result.ConditionResults { + var prefix string + if conditionResult.Success { + prefix = ":white_check_mark:" + } else { + prefix = ":x:" + } + formattedConditionResults += fmt.Sprintf("- %s - `%s`\n", prefix, conditionResult.Condition) + } + } + var description string + if alertDescription := alert.GetDescription(); len(alertDescription) > 0 { + description = ":\n> " + alertDescription + } + message := fmt.Sprintf("An alert for **%s** has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) + return message + description + formattedConditionResults +} + +// GetDefaultAlert returns the provider's default alert configuration +func (provider *AlertProvider) GetDefaultAlert() *alert.Alert { + return provider.DefaultAlert +} + +// GetConfig returns the configuration for the provider with the overrides applied +func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) { + cfg := provider.DefaultConfig + // Handle alert overrides + if len(alert.ProviderOverride) != 0 { + overrideConfig := Config{} + if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil { + return nil, err + } + cfg.Merge(&overrideConfig) + } + // Validate the configuration (we're returning the cfg here even if there's an error mostly for testing purposes) + err := cfg.Validate() + return &cfg, err +} + +// ValidateOverrides validates the alert's provider override and, if present, the group override +func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error { + _, err := provider.GetConfig(group, alert) + return err +} diff --git a/alerting/provider/github/github_test.go b/alerting/provider/github/github_test.go new file mode 100644 index 00000000..a701c8ba --- /dev/null +++ b/alerting/provider/github/github_test.go @@ -0,0 +1,221 @@ +package github + +import ( + "net/http" + "strings" + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/test" + "github.com/google/go-github/v48/github" +) + +func TestAlertProvider_Validate(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + ExpectedError bool + }{ + { + Name: "invalid", + Provider: AlertProvider{DefaultConfig: Config{RepositoryURL: "", Token: ""}}, + ExpectedError: true, + }, + { + Name: "invalid-token", + Provider: AlertProvider{DefaultConfig: Config{RepositoryURL: "https://github.com/TwiN/test", Token: "12345"}}, + ExpectedError: true, + }, + { + Name: "missing-repository-name", + Provider: AlertProvider{DefaultConfig: Config{RepositoryURL: "https://github.com/TwiN", Token: "12345"}}, + ExpectedError: true, + }, + { + Name: "enterprise-client", + Provider: AlertProvider{DefaultConfig: Config{RepositoryURL: "https://github.example.com/TwiN/test", Token: "12345"}}, + ExpectedError: true, + }, + { + Name: "invalid-url", + Provider: AlertProvider{DefaultConfig: Config{RepositoryURL: "github.com/TwiN/test", Token: "12345"}}, + ExpectedError: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + err := scenario.Provider.Validate() + if scenario.ExpectedError && err == nil { + t.Error("expected error, got none") + } + if !scenario.ExpectedError && err != nil && !strings.Contains(err.Error(), "user does not exist") && !strings.Contains(err.Error(), "no such host") { + t.Error("expected no error, got", err.Error()) + } + }) + } +} + +func TestAlertProvider_Send(t *testing.T) { + defer client.InjectHTTPClient(nil) + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + MockRoundTripper test.MockRoundTripper + ExpectedError bool + }{ + { + Name: "triggered-error", + Provider: AlertProvider{DefaultConfig: Config{RepositoryURL: "https://github.com/TwiN/test", Token: "12345"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedError: true, + }, + { + Name: "resolved-error", + Provider: AlertProvider{DefaultConfig: Config{RepositoryURL: "https://github.com/TwiN/test", Token: "12345"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedError: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + cfg, err := scenario.Provider.GetConfig("", &scenario.Alert) + if err != nil && !strings.Contains(err.Error(), "failed to retrieve GitHub user") && !strings.Contains(err.Error(), "no such host") { + t.Error("expected no error, got", err.Error()) + } + cfg.githubClient = github.NewClient(nil) + client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) + err = scenario.Provider.Send( + &endpoint.Endpoint{Name: "endpoint-name", Group: "endpoint-group"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if scenario.ExpectedError && err == nil { + t.Error("expected error, got none") + } + if !scenario.ExpectedError && err != nil { + t.Error("expected no error, got", err.Error()) + } + }) + } +} + +func TestAlertProvider_buildRequestBody(t *testing.T) { + firstDescription := "description-1" + scenarios := []struct { + Name string + Endpoint endpoint.Endpoint + Provider AlertProvider + Alert alert.Alert + NoConditions bool + ExpectedBody string + }{ + { + Name: "triggered", + Endpoint: endpoint.Endpoint{Name: "endpoint-name", URL: "https://example.org"}, + Provider: AlertProvider{}, + Alert: alert.Alert{Description: &firstDescription, FailureThreshold: 3}, + ExpectedBody: "An alert for **endpoint-name** has been triggered due to having failed 3 time(s) in a row:\n> description-1\n\n## Condition results\n- :white_check_mark: - `[CONNECTED] == true`\n- :x: - `[STATUS] == 200`", + }, + { + Name: "triggered-with-no-description", + Endpoint: endpoint.Endpoint{Name: "endpoint-name", URL: "https://example.org"}, + Provider: AlertProvider{}, + Alert: alert.Alert{FailureThreshold: 10}, + ExpectedBody: "An alert for **endpoint-name** has been triggered due to having failed 10 time(s) in a row\n\n## Condition results\n- :white_check_mark: - `[CONNECTED] == true`\n- :x: - `[STATUS] == 200`", + }, + { + Name: "triggered-with-no-conditions", + NoConditions: true, + Endpoint: endpoint.Endpoint{Name: "endpoint-name", URL: "https://example.org"}, + Provider: AlertProvider{}, + Alert: alert.Alert{Description: &firstDescription, FailureThreshold: 10}, + ExpectedBody: "An alert for **endpoint-name** has been triggered due to having failed 10 time(s) in a row:\n> description-1", + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + var conditionResults []*endpoint.ConditionResult + if !scenario.NoConditions { + conditionResults = []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: true}, + {Condition: "[STATUS] == 200", Success: false}, + } + } + body := scenario.Provider.buildIssueBody( + &scenario.Endpoint, + &scenario.Alert, + &endpoint.Result{ConditionResults: conditionResults}, + ) + if strings.TrimSpace(body) != strings.TrimSpace(scenario.ExpectedBody) { + t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body) + } + }) + } +} + +func TestAlertProvider_GetDefaultAlert(t *testing.T) { + if (&AlertProvider{DefaultAlert: &alert.Alert{}}).GetDefaultAlert() == nil { + t.Error("expected default alert to be not nil") + } + if (&AlertProvider{DefaultAlert: nil}).GetDefaultAlert() != nil { + t.Error("expected default alert to be nil") + } +} + +func TestAlertProvider_GetConfig(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + InputAlert alert.Alert + ExpectedOutput Config + }{ + { + Name: "provider-no-override-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{RepositoryURL: "https://github.com/TwiN/test", Token: "12345"}, + }, + InputAlert: alert.Alert{}, + ExpectedOutput: Config{RepositoryURL: "https://github.com/TwiN/test", Token: "12345"}, + }, + { + Name: "provider-with-alert-override", + Provider: AlertProvider{ + DefaultConfig: Config{RepositoryURL: "https://github.com/TwiN/test", Token: "12345"}, + }, + InputAlert: alert.Alert{ProviderOverride: map[string]any{"repository-url": "https://github.com/TwiN/alert-test", "token": "54321"}}, + ExpectedOutput: Config{RepositoryURL: "https://github.com/TwiN/alert-test", Token: "54321"}, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + got, err := scenario.Provider.GetConfig("", &scenario.InputAlert) + if err != nil && !strings.Contains(err.Error(), "failed to retrieve GitHub user") && !strings.Contains(err.Error(), "no such host") { + t.Fatalf("unexpected error: %s", err) + } + if got.RepositoryURL != scenario.ExpectedOutput.RepositoryURL { + t.Errorf("expected repository URL %s, got %s", scenario.ExpectedOutput.RepositoryURL, got.RepositoryURL) + } + if got.Token != scenario.ExpectedOutput.Token { + t.Errorf("expected token %s, got %s", scenario.ExpectedOutput.Token, got.Token) + } + // Test ValidateOverrides as well, since it really just calls GetConfig + if err = scenario.Provider.ValidateOverrides("", &scenario.InputAlert); err != nil && !strings.Contains(err.Error(), "failed to retrieve GitHub user") { + t.Errorf("unexpected error: %s", err) + } + }) + } +} diff --git a/alerting/provider/gitlab/gitlab.go b/alerting/provider/gitlab/gitlab.go new file mode 100644 index 00000000..0ee65d2f --- /dev/null +++ b/alerting/provider/gitlab/gitlab.go @@ -0,0 +1,204 @@ +package gitlab + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/google/uuid" + "gopkg.in/yaml.v3" +) + +const ( + DefaultSeverity = "critical" + DefaultMonitoringTool = "gatus" +) + +var ( + ErrInvalidWebhookURL = fmt.Errorf("invalid webhook-url") + ErrAuthorizationKeyNotSet = fmt.Errorf("authorization-key not set") +) + +type Config struct { + WebhookURL string `yaml:"webhook-url"` // The webhook url provided by GitLab + AuthorizationKey string `yaml:"authorization-key"` // The authorization key provided by GitLab + Severity string `yaml:"severity,omitempty"` // Severity can be one of: critical, high, medium, low, info, unknown. Defaults to critical + MonitoringTool string `yaml:"monitoring-tool,omitempty"` // MonitoringTool overrides the name sent to gitlab. Defaults to gatus + EnvironmentName string `yaml:"environment-name,omitempty"` // EnvironmentName is the name of the associated GitLab environment. Required to display alerts on a dashboard. + Service string `yaml:"service,omitempty"` // Service affected. Defaults to the endpoint's display name +} + +func (cfg *Config) Validate() error { + if len(cfg.WebhookURL) == 0 { + return ErrInvalidWebhookURL + } else if _, err := url.Parse(cfg.WebhookURL); err != nil { + return ErrInvalidWebhookURL + } + if len(cfg.AuthorizationKey) == 0 { + return ErrAuthorizationKeyNotSet + } + if len(cfg.Severity) == 0 { + cfg.Severity = DefaultSeverity + } + if len(cfg.MonitoringTool) == 0 { + cfg.MonitoringTool = DefaultMonitoringTool + } + return nil +} + +func (cfg *Config) Merge(override *Config) { + if len(override.WebhookURL) > 0 { + cfg.WebhookURL = override.WebhookURL + } + if len(override.AuthorizationKey) > 0 { + cfg.AuthorizationKey = override.AuthorizationKey + } + if len(override.Severity) > 0 { + cfg.Severity = override.Severity + } + if len(override.MonitoringTool) > 0 { + cfg.MonitoringTool = override.MonitoringTool + } + if len(override.EnvironmentName) > 0 { + cfg.EnvironmentName = override.EnvironmentName + } + if len(override.Service) > 0 { + cfg.Service = override.Service + } +} + +// AlertProvider is the configuration necessary for sending an alert using GitLab +type AlertProvider struct { + DefaultConfig Config `yaml:",inline"` + + // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type + DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` +} + +// Validate the provider's configuration +func (provider *AlertProvider) Validate() error { + return provider.DefaultConfig.Validate() +} + +// Send creates an issue in the designed RepositoryURL if the resolved parameter passed is false, +// or closes the relevant issue(s) if the resolved parameter passed is true. +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + cfg, err := provider.GetConfig(ep.Group, alert) + if err != nil { + return err + } + if len(alert.ResolveKey) == 0 { + alert.ResolveKey = uuid.NewString() + } + buffer := bytes.NewBuffer(provider.buildAlertBody(cfg, ep, alert, result, resolved)) + request, err := http.NewRequest(http.MethodPost, cfg.WebhookURL, buffer) + if err != nil { + return err + } + request.Header.Set("Content-Type", "application/json") + request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", cfg.AuthorizationKey)) + response, err := client.GetHTTPClient(nil).Do(request) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode > 399 { + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body)) + } + return err +} + +type AlertBody struct { + Title string `json:"title,omitempty"` // The title of the alert. + Description string `json:"description,omitempty"` // A high-level summary of the problem. + StartTime string `json:"start_time,omitempty"` // The time of the alert. If none is provided, a current time is used. + EndTime string `json:"end_time,omitempty"` // The resolution time of the alert. If provided, the alert is resolved. + Service string `json:"service,omitempty"` // The affected service. + MonitoringTool string `json:"monitoring_tool,omitempty"` // The name of the associated monitoring tool. + Hosts string `json:"hosts,omitempty"` // One or more hosts, as to where this incident occurred. + Severity string `json:"severity,omitempty"` // The severity of the alert. Case-insensitive. Can be one of: critical, high, medium, low, info, unknown. Defaults to critical if missing or value is not in this list. + Fingerprint string `json:"fingerprint,omitempty"` // The unique identifier of the alert. This can be used to group occurrences of the same alert. + GitlabEnvironmentName string `json:"gitlab_environment_name,omitempty"` // The name of the associated GitLab environment. Required to display alerts on a dashboard. +} + +// buildAlertBody builds the body of the alert +func (provider *AlertProvider) buildAlertBody(cfg *Config, ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { + service := cfg.Service + if len(service) == 0 { + service = ep.DisplayName() + } + body := AlertBody{ + Title: fmt.Sprintf("alert(%s): %s", cfg.MonitoringTool, service), + StartTime: result.Timestamp.Format(time.RFC3339), + Service: service, + MonitoringTool: cfg.MonitoringTool, + Hosts: ep.URL, + GitlabEnvironmentName: cfg.EnvironmentName, + Severity: cfg.Severity, + Fingerprint: alert.ResolveKey, + } + if resolved { + body.EndTime = result.Timestamp.Format(time.RFC3339) + } + var formattedConditionResults string + if len(result.ConditionResults) > 0 { + formattedConditionResults = "\n\n## Condition results\n" + for _, conditionResult := range result.ConditionResults { + var prefix string + if conditionResult.Success { + prefix = ":white_check_mark:" + } else { + prefix = ":x:" + } + formattedConditionResults += fmt.Sprintf("- %s - `%s`\n", prefix, conditionResult.Condition) + } + } + var description string + if alertDescription := alert.GetDescription(); len(alertDescription) > 0 { + description = ":\n> " + alertDescription + } + var message string + if resolved { + message = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) + } else { + message = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) + } + body.Description = message + description + formattedConditionResults + bodyAsJSON, _ := json.Marshal(body) + return bodyAsJSON +} + +// GetDefaultAlert returns the provider's default alert configuration +func (provider *AlertProvider) GetDefaultAlert() *alert.Alert { + return provider.DefaultAlert +} + +// GetConfig returns the configuration for the provider with the overrides applied +func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) { + cfg := provider.DefaultConfig + // Handle alert overrides + if len(alert.ProviderOverride) != 0 { + overrideConfig := Config{} + if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil { + return nil, err + } + cfg.Merge(&overrideConfig) + } + // Validate the configuration (we're returning the cfg here even if there's an error mostly for testing purposes) + err := cfg.Validate() + return &cfg, err +} + +// ValidateOverrides validates the alert's provider override and, if present, the group override +func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error { + _, err := provider.GetConfig(group, alert) + return err +} diff --git a/alerting/provider/gitlab/gitlab_test.go b/alerting/provider/gitlab/gitlab_test.go new file mode 100644 index 00000000..2421284e --- /dev/null +++ b/alerting/provider/gitlab/gitlab_test.go @@ -0,0 +1,223 @@ +package gitlab + +import ( + "net/http" + "strings" + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/test" +) + +func TestAlertProvider_Validate(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + ExpectedError bool + }{ + { + Name: "invalid", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "", AuthorizationKey: ""}}, + ExpectedError: true, + }, + { + Name: "missing-webhook-url", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "", AuthorizationKey: "12345"}}, + ExpectedError: true, + }, + { + Name: "missing-authorization-key", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "https://gitlab.com/whatever/text/alerts/notify/gatus/xxxxxxxxxxxxxxxx.json", AuthorizationKey: ""}}, + ExpectedError: true, + }, + { + Name: "invalid-url", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: " http://foo.com", AuthorizationKey: "12345"}}, + ExpectedError: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + err := scenario.Provider.Validate() + if scenario.ExpectedError && err == nil { + t.Error("expected error, got none") + } + if !scenario.ExpectedError && err != nil && !strings.Contains(err.Error(), "user does not exist") && !strings.Contains(err.Error(), "no such host") { + t.Error("expected no error, got", err.Error()) + } + }) + } +} + +func TestAlertProvider_Send(t *testing.T) { + defer client.InjectHTTPClient(nil) + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + MockRoundTripper test.MockRoundTripper + ExpectedError bool + }{ + { + Name: "triggered-error", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "https://gitlab.com/hlidotbe/text/alerts/notify/gatus/xxxxxxxxxxxxxxxx.json", AuthorizationKey: "12345"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedError: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + }, + { + Name: "resolved-error", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "https://gitlab.com/hlidotbe/text/alerts/notify/gatus/xxxxxxxxxxxxxxxx.json", AuthorizationKey: "12345"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedError: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) + err := scenario.Provider.Send( + &endpoint.Endpoint{Name: "endpoint-name", Group: "endpoint-group"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if scenario.ExpectedError && err == nil { + t.Error("expected error, got none") + } + if !scenario.ExpectedError && err != nil { + t.Error("expected no error, got", err.Error()) + } + }) + } +} + +func TestAlertProvider_buildAlertBody(t *testing.T) { + firstDescription := "description-1" + scenarios := []struct { + Name string + Endpoint endpoint.Endpoint + Provider AlertProvider + Alert alert.Alert + ExpectedBody string + }{ + { + Name: "triggered", + Endpoint: endpoint.Endpoint{Name: "endpoint-name", URL: "https://example.org"}, + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "https://gitlab.com/hlidotbe/text/alerts/notify/gatus/xxxxxxxxxxxxxxxx.json", AuthorizationKey: "12345"}}, + Alert: alert.Alert{Description: &firstDescription, FailureThreshold: 3}, + ExpectedBody: "{\"title\":\"alert(gatus): endpoint-name\",\"description\":\"An alert for *endpoint-name* has been triggered due to having failed 3 time(s) in a row:\\n\\u003e description-1\\n\\n## Condition results\\n- :white_check_mark: - `[CONNECTED] == true`\\n- :x: - `[STATUS] == 200`\\n\",\"start_time\":\"0001-01-01T00:00:00Z\",\"service\":\"endpoint-name\",\"monitoring_tool\":\"gatus\",\"hosts\":\"https://example.org\",\"severity\":\"critical\"}", + }, + { + Name: "no-description", + Endpoint: endpoint.Endpoint{Name: "endpoint-name", URL: "https://example.org"}, + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "https://gitlab.com/hlidotbe/text/alerts/notify/gatus/xxxxxxxxxxxxxxxx.json", AuthorizationKey: "12345"}}, + Alert: alert.Alert{FailureThreshold: 10}, + ExpectedBody: "{\"title\":\"alert(gatus): endpoint-name\",\"description\":\"An alert for *endpoint-name* has been triggered due to having failed 10 time(s) in a row\\n\\n## Condition results\\n- :white_check_mark: - `[CONNECTED] == true`\\n- :x: - `[STATUS] == 200`\\n\",\"start_time\":\"0001-01-01T00:00:00Z\",\"service\":\"endpoint-name\",\"monitoring_tool\":\"gatus\",\"hosts\":\"https://example.org\",\"severity\":\"critical\"}", + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + cfg, err := scenario.Provider.GetConfig("", &scenario.Alert) + if err != nil { + t.Error("expected no error, got", err.Error()) + } + body := scenario.Provider.buildAlertBody( + cfg, + &scenario.Endpoint, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: true}, + {Condition: "[STATUS] == 200", Success: false}, + }, + }, + false, + ) + if strings.TrimSpace(string(body)) != strings.TrimSpace(scenario.ExpectedBody) { + t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body) + } + }) + } +} + +func TestAlertProvider_GetDefaultAlert(t *testing.T) { + if (&AlertProvider{DefaultAlert: &alert.Alert{}}).GetDefaultAlert() == nil { + t.Error("expected default alert to be not nil") + } + if (&AlertProvider{DefaultAlert: nil}).GetDefaultAlert() != nil { + t.Error("expected default alert to be nil") + } +} + +func TestAlertProvider_GetConfig(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + InputAlert alert.Alert + ExpectedOutput Config + }{ + { + Name: "provider-no-override-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "https://github.com/TwiN/test", AuthorizationKey: "12345"}, + }, + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "https://github.com/TwiN/test", AuthorizationKey: "12345", Severity: DefaultSeverity, MonitoringTool: DefaultMonitoringTool}, + }, + { + Name: "provider-with-alert-override", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "https://github.com/TwiN/test", AuthorizationKey: "12345"}, + }, + InputAlert: alert.Alert{ProviderOverride: map[string]any{"repository-url": "https://github.com/TwiN/alert-test", "authorization-key": "54321", "severity": "info", "monitoring-tool": "not-gatus", "environment-name": "prod", "service": "example"}}, + ExpectedOutput: Config{WebhookURL: "https://github.com/TwiN/test", AuthorizationKey: "54321", Severity: "info", MonitoringTool: "not-gatus", EnvironmentName: "prod", Service: "example"}, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + got, err := scenario.Provider.GetConfig("", &scenario.InputAlert) + if err != nil && !strings.Contains(err.Error(), "user does not exist") && !strings.Contains(err.Error(), "no such host") { + t.Fatalf("unexpected error: %s", err) + } + if got.WebhookURL != scenario.ExpectedOutput.WebhookURL { + t.Errorf("expected repository URL %s, got %s", scenario.ExpectedOutput.WebhookURL, got.WebhookURL) + } + if got.AuthorizationKey != scenario.ExpectedOutput.AuthorizationKey { + t.Errorf("expected AuthorizationKey %s, got %s", scenario.ExpectedOutput.AuthorizationKey, got.AuthorizationKey) + } + if got.Severity != scenario.ExpectedOutput.Severity { + t.Errorf("expected Severity %s, got %s", scenario.ExpectedOutput.Severity, got.Severity) + } + if got.MonitoringTool != scenario.ExpectedOutput.MonitoringTool { + t.Errorf("expected MonitoringTool %s, got %s", scenario.ExpectedOutput.MonitoringTool, got.MonitoringTool) + } + if got.EnvironmentName != scenario.ExpectedOutput.EnvironmentName { + t.Errorf("expected EnvironmentName %s, got %s", scenario.ExpectedOutput.EnvironmentName, got.EnvironmentName) + } + if got.Service != scenario.ExpectedOutput.Service { + t.Errorf("expected Service %s, got %s", scenario.ExpectedOutput.Service, got.Service) + } + // Test ValidateOverrides as well, since it really just calls GetConfig + if err = scenario.Provider.ValidateOverrides("", &scenario.InputAlert); err != nil { + t.Errorf("unexpected error: %s", err) + } + }) + } +} diff --git a/alerting/provider/googlechat/googlechat.go b/alerting/provider/googlechat/googlechat.go new file mode 100644 index 00000000..aeeeec93 --- /dev/null +++ b/alerting/provider/googlechat/googlechat.go @@ -0,0 +1,248 @@ +package googlechat + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "gopkg.in/yaml.v3" +) + +var ( + ErrWebhookURLNotSet = errors.New("webhook-url not set") + ErrDuplicateGroupOverride = errors.New("duplicate group override") +) + +type Config struct { + WebhookURL string `yaml:"webhook-url"` + ClientConfig *client.Config `yaml:"client,omitempty"` +} + +func (cfg *Config) Validate() error { + if len(cfg.WebhookURL) == 0 { + return ErrWebhookURLNotSet + } + return nil +} + +func (cfg *Config) Merge(override *Config) { + if override.ClientConfig != nil { + cfg.ClientConfig = override.ClientConfig + } + if len(override.WebhookURL) > 0 { + cfg.WebhookURL = override.WebhookURL + } +} + +// AlertProvider is the configuration necessary for sending an alert using Google chat +type AlertProvider struct { + DefaultConfig Config `yaml:",inline"` + + // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type + DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` + + // Overrides is a list of Override that may be prioritized over the default configuration + Overrides []Override `yaml:"overrides,omitempty"` +} + +// Override is a case under which the default integration is overridden +type Override struct { + Group string `yaml:"group"` + Config `yaml:",inline"` +} + +// Validate the provider's configuration +func (provider *AlertProvider) Validate() error { + registeredGroups := make(map[string]bool) + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if isAlreadyRegistered := registeredGroups[override.Group]; isAlreadyRegistered || override.Group == "" || len(override.WebhookURL) == 0 { + return ErrDuplicateGroupOverride + } + registeredGroups[override.Group] = true + } + } + return provider.DefaultConfig.Validate() +} + +// Send an alert using the provider +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + cfg, err := provider.GetConfig(ep.Group, alert) + if err != nil { + return err + } + buffer := bytes.NewBuffer(provider.buildRequestBody(ep, alert, result, resolved)) + request, err := http.NewRequest(http.MethodPost, cfg.WebhookURL, buffer) + if err != nil { + return err + } + request.Header.Set("Content-Type", "application/json") + response, err := client.GetHTTPClient(cfg.ClientConfig).Do(request) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode > 399 { + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body)) + } + return err +} + +type Body struct { + Cards []Cards `json:"cards"` +} + +type Cards struct { + Sections []Sections `json:"sections"` +} + +type Sections struct { + Widgets []Widgets `json:"widgets"` +} + +type Widgets struct { + KeyValue *KeyValue `json:"keyValue,omitempty"` + Buttons []Buttons `json:"buttons,omitempty"` +} + +type KeyValue struct { + TopLabel string `json:"topLabel,omitempty"` + Content string `json:"content,omitempty"` + ContentMultiline string `json:"contentMultiline,omitempty"` + BottomLabel string `json:"bottomLabel,omitempty"` + Icon string `json:"icon,omitempty"` +} + +type Buttons struct { + TextButton TextButton `json:"textButton"` +} + +type TextButton struct { + Text string `json:"text"` + OnClick OnClick `json:"onClick"` +} + +type OnClick struct { + OpenLink OpenLink `json:"openLink"` +} + +type OpenLink struct { + URL string `json:"url"` +} + +// buildRequestBody builds the request body for the provider +func (provider *AlertProvider) buildRequestBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { + var message, color string + if resolved { + color = "#36A64F" + message = fmt.Sprintf("An alert has been resolved after passing successfully %d time(s) in a row", color, alert.SuccessThreshold) + } else { + color = "#DD0000" + message = fmt.Sprintf("An alert has been triggered due to having failed %d time(s) in a row", color, alert.FailureThreshold) + } + var formattedConditionResults string + for _, conditionResult := range result.ConditionResults { + var prefix string + if conditionResult.Success { + prefix = "✅" + } else { + prefix = "❌" + } + formattedConditionResults += fmt.Sprintf("%s %s
", prefix, conditionResult.Condition) + } + var description string + if alertDescription := alert.GetDescription(); len(alertDescription) > 0 { + description = ":: " + alertDescription + } + payload := Body{ + Cards: []Cards{ + { + Sections: []Sections{ + { + Widgets: []Widgets{ + { + KeyValue: &KeyValue{ + TopLabel: ep.DisplayName(), + Content: message, + ContentMultiline: "true", + BottomLabel: description, + Icon: "BOOKMARK", + }, + }, + }, + }, + }, + }, + }, + } + if len(formattedConditionResults) > 0 { + payload.Cards[0].Sections[0].Widgets = append(payload.Cards[0].Sections[0].Widgets, Widgets{ + KeyValue: &KeyValue{ + TopLabel: "Condition results", + Content: formattedConditionResults, + ContentMultiline: "true", + Icon: "DESCRIPTION", + }, + }) + } + if ep.Type() == endpoint.TypeHTTP { + // We only include a button targeting the URL if the endpoint is an HTTP endpoint + // If the URL isn't prefixed with https://, Google Chat will just display a blank message aynways. + // See https://github.com/TwiN/gatus/issues/362 + payload.Cards[0].Sections[0].Widgets = append(payload.Cards[0].Sections[0].Widgets, Widgets{ + Buttons: []Buttons{ + { + TextButton: TextButton{ + Text: "URL", + OnClick: OnClick{OpenLink: OpenLink{URL: ep.URL}}, + }, + }, + }, + }) + } + bodyAsJSON, _ := json.Marshal(payload) + return bodyAsJSON +} + +// GetDefaultAlert returns the provider's default alert configuration +func (provider *AlertProvider) GetDefaultAlert() *alert.Alert { + return provider.DefaultAlert +} + +// GetConfig returns the configuration for the provider with the overrides applied +func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) { + cfg := provider.DefaultConfig + // Handle group overrides + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if group == override.Group { + cfg.Merge(&override.Config) + break + } + } + } + // Handle alert overrides + if len(alert.ProviderOverride) != 0 { + overrideConfig := Config{} + if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil { + return nil, err + } + cfg.Merge(&overrideConfig) + } + // Validate the configuration + err := cfg.Validate() + return &cfg, err +} + +// ValidateOverrides validates the alert's provider override and, if present, the group override +func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error { + _, err := provider.GetConfig(group, alert) + return err +} diff --git a/alerting/provider/googlechat/googlechat_test.go b/alerting/provider/googlechat/googlechat_test.go new file mode 100644 index 00000000..5dbd6cb1 --- /dev/null +++ b/alerting/provider/googlechat/googlechat_test.go @@ -0,0 +1,305 @@ +package googlechat + +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/test" +) + +func TestAlertProvider_Validate(t *testing.T) { + invalidProvider := AlertProvider{DefaultConfig: Config{WebhookURL: ""}} + if err := invalidProvider.Validate(); err == nil { + t.Error("provider shouldn't have been valid") + } + validProvider := AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}} + if err := validProvider.Validate(); err != nil { + t.Error("provider should've been valid") + } +} + +func TestAlertProvider_ValidateWithOverride(t *testing.T) { + providerWithInvalidOverrideGroup := AlertProvider{ + Overrides: []Override{ + { + Config: Config{WebhookURL: "http://example.com"}, + Group: "", + }, + }, + } + if err := providerWithInvalidOverrideGroup.Validate(); err == nil { + t.Error("provider Group shouldn't have been valid") + } + providerWithInvalidOverrideTo := AlertProvider{ + Overrides: []Override{ + { + Config: Config{WebhookURL: ""}, + Group: "group", + }, + }, + } + if err := providerWithInvalidOverrideTo.Validate(); err == nil { + t.Error("provider integration key shouldn't have been valid") + } + providerWithValidOverride := AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: []Override{ + { + Config: Config{WebhookURL: "http://example.com"}, + Group: "group", + }, + }, + } + if err := providerWithValidOverride.Validate(); err != nil { + t.Error("provider should've been valid") + } +} + +func TestAlertProvider_Send(t *testing.T) { + defer client.InjectHTTPClient(nil) + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + MockRoundTripper test.MockRoundTripper + ExpectedError bool + }{ + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "triggered-error", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + { + Name: "resolved", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "resolved-error", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) + err := scenario.Provider.Send( + &endpoint.Endpoint{Name: "endpoint-name", Group: "endpoint-group"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if scenario.ExpectedError && err == nil { + t.Error("expected error, got none") + } + if !scenario.ExpectedError && err != nil { + t.Error("expected no error, got", err.Error()) + } + }) + } +} + +func TestAlertProvider_buildRequestBody(t *testing.T) { + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Endpoint endpoint.Endpoint + Provider AlertProvider + Alert alert.Alert + Resolved bool + ExpectedBody string + }{ + { + Name: "triggered", + Endpoint: endpoint.Endpoint{Name: "endpoint-name", URL: "https://example.org"}, + Provider: AlertProvider{}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: `{"cards":[{"sections":[{"widgets":[{"keyValue":{"topLabel":"endpoint-name","content":"\u003cfont color='#DD0000'\u003eAn alert has been triggered due to having failed 3 time(s) in a row\u003c/font\u003e","contentMultiline":"true","bottomLabel":":: description-1","icon":"BOOKMARK"}},{"keyValue":{"topLabel":"Condition results","content":"❌ [CONNECTED] == true\u003cbr\u003e❌ [STATUS] == 200\u003cbr\u003e","contentMultiline":"true","icon":"DESCRIPTION"}},{"buttons":[{"textButton":{"text":"URL","onClick":{"openLink":{"url":"https://example.org"}}}}]}]}]}]}`, + }, + { + Name: "resolved", + Endpoint: endpoint.Endpoint{Name: "endpoint-name", URL: "https://example.org"}, + Provider: AlertProvider{}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: `{"cards":[{"sections":[{"widgets":[{"keyValue":{"topLabel":"endpoint-name","content":"\u003cfont color='#36A64F'\u003eAn alert has been resolved after passing successfully 5 time(s) in a row\u003c/font\u003e","contentMultiline":"true","bottomLabel":":: description-2","icon":"BOOKMARK"}},{"keyValue":{"topLabel":"Condition results","content":"✅ [CONNECTED] == true\u003cbr\u003e✅ [STATUS] == 200\u003cbr\u003e","contentMultiline":"true","icon":"DESCRIPTION"}},{"buttons":[{"textButton":{"text":"URL","onClick":{"openLink":{"url":"https://example.org"}}}}]}]}]}]}`, + }, + { + Name: "icmp-should-not-include-url", // See https://github.com/TwiN/gatus/issues/362 + Endpoint: endpoint.Endpoint{Name: "endpoint-name", URL: "icmp://example.org"}, + Provider: AlertProvider{}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: `{"cards":[{"sections":[{"widgets":[{"keyValue":{"topLabel":"endpoint-name","content":"\u003cfont color='#DD0000'\u003eAn alert has been triggered due to having failed 3 time(s) in a row\u003c/font\u003e","contentMultiline":"true","bottomLabel":":: description-1","icon":"BOOKMARK"}},{"keyValue":{"topLabel":"Condition results","content":"❌ [CONNECTED] == true\u003cbr\u003e❌ [STATUS] == 200\u003cbr\u003e","contentMultiline":"true","icon":"DESCRIPTION"}}]}]}]}`, + }, + { + Name: "tcp-should-not-include-url", // See https://github.com/TwiN/gatus/issues/362 + Endpoint: endpoint.Endpoint{Name: "endpoint-name", URL: "tcp://example.org"}, + Provider: AlertProvider{}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: `{"cards":[{"sections":[{"widgets":[{"keyValue":{"topLabel":"endpoint-name","content":"\u003cfont color='#DD0000'\u003eAn alert has been triggered due to having failed 3 time(s) in a row\u003c/font\u003e","contentMultiline":"true","bottomLabel":":: description-1","icon":"BOOKMARK"}},{"keyValue":{"topLabel":"Condition results","content":"❌ [CONNECTED] == true\u003cbr\u003e❌ [STATUS] == 200\u003cbr\u003e","contentMultiline":"true","icon":"DESCRIPTION"}}]}]}]}`, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + body := scenario.Provider.buildRequestBody( + &scenario.Endpoint, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if string(body) != scenario.ExpectedBody { + t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body) + } + out := make(map[string]interface{}) + if err := json.Unmarshal(body, &out); err != nil { + t.Error("expected body to be valid JSON, got error:", err.Error()) + } + }) + } +} + +func TestAlertProvider_GetDefaultAlert(t *testing.T) { + if (&AlertProvider{DefaultAlert: &alert.Alert{}}).GetDefaultAlert() == nil { + t.Error("expected default alert to be not nil") + } + if (&AlertProvider{DefaultAlert: nil}).GetDefaultAlert() != nil { + t.Error("expected default alert to be nil") + } +} + +func TestAlertProvider_GetConfig(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + InputGroup string + InputAlert alert.Alert + ExpectedOutput Config + }{ + { + Name: "provider-no-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: nil, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://example.com"}, + }, + { + Name: "provider-no-override-specify-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: nil, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://example.com"}, + }, + { + Name: "provider-with-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{WebhookURL: "http://example01.com"}, + }, + }, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://example.com"}, + }, + { + Name: "provider-with-override-specify-group-should-override", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{WebhookURL: "http://group-example.com"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://group-example.com"}, + }, + { + Name: "provider-with-group-override-and-alert-override--alert-override-should-take-precedence", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{WebhookURL: "http://group-example.com"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{ProviderOverride: map[string]any{"webhook-url": "http://alert-example.com"}}, + ExpectedOutput: Config{WebhookURL: "http://alert-example.com"}, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + got, err := scenario.Provider.GetConfig(scenario.InputGroup, &scenario.InputAlert) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if got.WebhookURL != scenario.ExpectedOutput.WebhookURL { + t.Errorf("expected webhook URL to be %s, got %s", scenario.ExpectedOutput.WebhookURL, got.WebhookURL) + } + // Test ValidateOverrides as well, since it really just calls GetConfig + if err = scenario.Provider.ValidateOverrides(scenario.InputGroup, &scenario.InputAlert); err != nil { + t.Errorf("unexpected error: %s", err) + } + }) + } +} diff --git a/alerting/provider/gotify/gotify.go b/alerting/provider/gotify/gotify.go new file mode 100644 index 00000000..1d492910 --- /dev/null +++ b/alerting/provider/gotify/gotify.go @@ -0,0 +1,161 @@ +package gotify + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "gopkg.in/yaml.v3" +) + +const DefaultPriority = 5 + +var ( + ErrServerURLNotSet = errors.New("server URL not set") + ErrTokenNotSet = errors.New("token not set") +) + +type Config struct { + ServerURL string `yaml:"server-url"` // URL of the Gotify server + Token string `yaml:"token"` // Token to use when sending a message to the Gotify server + Priority int `yaml:"priority,omitempty"` // Priority of the message. Defaults to DefaultPriority. + Title string `yaml:"title,omitempty"` // Title of the message that will be sent +} + +func (cfg *Config) Validate() error { + if cfg.Priority == 0 { + cfg.Priority = DefaultPriority + } + if len(cfg.ServerURL) == 0 { + return ErrServerURLNotSet + } + if len(cfg.Token) == 0 { + return ErrTokenNotSet + } + return nil +} + +func (cfg *Config) Merge(override *Config) { + if len(override.ServerURL) > 0 { + cfg.ServerURL = override.ServerURL + } + if len(override.Token) > 0 { + cfg.Token = override.Token + } + if override.Priority != 0 { + cfg.Priority = override.Priority + } + if len(override.Title) > 0 { + cfg.Title = override.Title + } +} + +// AlertProvider is the configuration necessary for sending an alert using Gotify +type AlertProvider struct { + DefaultConfig Config `yaml:",inline"` + + // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type + DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` +} + +// Validate the provider's configuration +func (provider *AlertProvider) Validate() error { + return provider.DefaultConfig.Validate() +} + +// Send an alert using the provider +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + cfg, err := provider.GetConfig(ep.Group, alert) + if err != nil { + return err + } + buffer := bytes.NewBuffer(provider.buildRequestBody(cfg, ep, alert, result, resolved)) + request, err := http.NewRequest(http.MethodPost, cfg.ServerURL+"/message?token="+cfg.Token, buffer) + if err != nil { + return err + } + request.Header.Set("Content-Type", "application/json") + response, err := client.GetHTTPClient(nil).Do(request) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode > 399 { + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("failed to send alert to Gotify: %s", string(body)) + } + return nil +} + +type Body struct { + Message string `json:"message"` + Title string `json:"title"` + Priority int `json:"priority"` +} + +// buildRequestBody builds the request body for the provider +func (provider *AlertProvider) buildRequestBody(cfg *Config, ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { + var message string + if resolved { + message = fmt.Sprintf("An alert for `%s` has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) + } else { + message = fmt.Sprintf("An alert for `%s` has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) + } + var formattedConditionResults string + for _, conditionResult := range result.ConditionResults { + var prefix string + if conditionResult.Success { + prefix = "✓" + } else { + prefix = "✕" + } + formattedConditionResults += fmt.Sprintf("\n%s - %s", prefix, conditionResult.Condition) + } + if len(alert.GetDescription()) > 0 { + message += " with the following description: " + alert.GetDescription() + } + message += formattedConditionResults + title := "Gatus: " + ep.DisplayName() + if cfg.Title != "" { + title = cfg.Title + } + bodyAsJSON, _ := json.Marshal(Body{ + Message: message, + Title: title, + Priority: cfg.Priority, + }) + return bodyAsJSON +} + +// GetDefaultAlert returns the provider's default alert configuration +func (provider *AlertProvider) GetDefaultAlert() *alert.Alert { + return provider.DefaultAlert +} + +// GetConfig returns the configuration for the provider with the overrides applied +func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) { + cfg := provider.DefaultConfig + // Handle alert overrides + if len(alert.ProviderOverride) != 0 { + overrideConfig := Config{} + if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil { + return nil, err + } + cfg.Merge(&overrideConfig) + } + // Validate the configuration + err := cfg.Validate() + return &cfg, err +} + +// ValidateOverrides validates the alert's provider override and, if present, the group override +func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error { + _, err := provider.GetConfig(group, alert) + return err +} diff --git a/alerting/provider/gotify/gotify_test.go b/alerting/provider/gotify/gotify_test.go new file mode 100644 index 00000000..9db61845 --- /dev/null +++ b/alerting/provider/gotify/gotify_test.go @@ -0,0 +1,163 @@ +package gotify + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/config/endpoint" +) + +func TestAlertProvider_Validate(t *testing.T) { + scenarios := []struct { + name string + provider AlertProvider + expected bool + }{ + { + name: "valid", + provider: AlertProvider{DefaultConfig: Config{ServerURL: "https://gotify.example.com", Token: "faketoken"}}, + expected: true, + }, + { + name: "invalid-server-url", + provider: AlertProvider{DefaultConfig: Config{ServerURL: "", Token: "faketoken"}}, + expected: false, + }, + { + name: "invalid-app-token", + provider: AlertProvider{DefaultConfig: Config{ServerURL: "https://gotify.example.com", Token: ""}}, + expected: false, + }, + { + name: "no-priority-should-use-default-value", + provider: AlertProvider{DefaultConfig: Config{ServerURL: "https://gotify.example.com", Token: "faketoken"}}, + expected: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + if err := scenario.provider.Validate(); (err == nil) != scenario.expected { + t.Errorf("expected: %t, got: %t", scenario.expected, err == nil) + } + }) + } +} + +func TestAlertProvider_buildRequestBody(t *testing.T) { + var ( + description = "custom-description" + //title = "custom-title" + endpointName = "custom-endpoint" + ) + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + ExpectedBody string + }{ + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{ServerURL: "https://gotify.example.com", Token: "faketoken"}}, + Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: fmt.Sprintf("{\"message\":\"An alert for `%s` has been triggered due to having failed 3 time(s) in a row with the following description: %s\\n✕ - [CONNECTED] == true\\n✕ - [STATUS] == 200\",\"title\":\"Gatus: custom-endpoint\",\"priority\":0}", endpointName, description), + }, + { + Name: "resolved", + Provider: AlertProvider{DefaultConfig: Config{ServerURL: "https://gotify.example.com", Token: "faketoken"}}, + Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: fmt.Sprintf("{\"message\":\"An alert for `%s` has been resolved after passing successfully 5 time(s) in a row with the following description: %s\\n✓ - [CONNECTED] == true\\n✓ - [STATUS] == 200\",\"title\":\"Gatus: custom-endpoint\",\"priority\":0}", endpointName, description), + }, + { + Name: "custom-title", + Provider: AlertProvider{DefaultConfig: Config{ServerURL: "https://gotify.example.com", Token: "faketoken", Title: "custom-title"}}, + Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: fmt.Sprintf("{\"message\":\"An alert for `%s` has been triggered due to having failed 3 time(s) in a row with the following description: %s\\n✕ - [CONNECTED] == true\\n✕ - [STATUS] == 200\",\"title\":\"custom-title\",\"priority\":0}", endpointName, description), + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + body := scenario.Provider.buildRequestBody( + &scenario.Provider.DefaultConfig, + &endpoint.Endpoint{Name: endpointName}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if string(body) != scenario.ExpectedBody { + t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body) + } + out := make(map[string]interface{}) + if err := json.Unmarshal(body, &out); err != nil { + t.Error("expected body to be valid JSON, got error:", err.Error()) + } + }) + } +} + +func TestAlertProvider_GetDefaultAlert(t *testing.T) { + provider := AlertProvider{DefaultAlert: &alert.Alert{}} + if provider.GetDefaultAlert() != provider.DefaultAlert { + t.Error("expected default alert to be returned") + } +} + +func TestAlertProvider_GetConfig(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + InputAlert alert.Alert + ExpectedOutput Config + }{ + { + Name: "provider-no-override-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{ServerURL: "https://gotify.example.com", Token: "12345"}, + }, + InputAlert: alert.Alert{}, + ExpectedOutput: Config{ServerURL: "https://gotify.example.com", Token: "12345", Priority: DefaultPriority}, + }, + { + Name: "provider-with-alert-override", + Provider: AlertProvider{ + DefaultConfig: Config{ServerURL: "https://gotify.example.com", Token: "12345"}, + }, + InputAlert: alert.Alert{ProviderOverride: map[string]any{"server-url": "https://gotify.group-example.com", "token": "54321", "title": "alert-title", "priority": 3}}, + ExpectedOutput: Config{ServerURL: "https://gotify.group-example.com", Token: "54321", Title: "alert-title", Priority: 3}, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + got, err := scenario.Provider.GetConfig("", &scenario.InputAlert) + if err != nil { + t.Error("expected no error, got:", err.Error()) + } + if got.ServerURL != scenario.ExpectedOutput.ServerURL { + t.Errorf("expected server URL to be %s, got %s", scenario.ExpectedOutput.ServerURL, got.ServerURL) + } + if got.Token != scenario.ExpectedOutput.Token { + t.Errorf("expected token to be %s, got %s", scenario.ExpectedOutput.Token, got.Token) + } + if got.Title != scenario.ExpectedOutput.Title { + t.Errorf("expected title to be %s, got %s", scenario.ExpectedOutput.Title, got.Title) + } + if got.Priority != scenario.ExpectedOutput.Priority { + t.Errorf("expected priority to be %d, got %d", scenario.ExpectedOutput.Priority, got.Priority) + } + // Test ValidateOverrides as well, since it really just calls GetConfig + if err = scenario.Provider.ValidateOverrides("", &scenario.InputAlert); err != nil { + t.Errorf("unexpected error: %s", err) + } + }) + } +} diff --git a/alerting/provider/incidentio/incidentio.go b/alerting/provider/incidentio/incidentio.go new file mode 100644 index 00000000..52039261 --- /dev/null +++ b/alerting/provider/incidentio/incidentio.go @@ -0,0 +1,210 @@ +package incidentio + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strconv" + "strings" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/logr" + "gopkg.in/yaml.v3" +) + +const ( + restAPIUrl = "https://api.incident.io/v2/alert_events/http/" +) + +var ( + ErrURLNotSet = errors.New("url not set") + ErrURLNotPrefixedWithRestAPIURL = fmt.Errorf("url must be prefixed with %s", restAPIUrl) + ErrDuplicateGroupOverride = errors.New("duplicate group override") + ErrAuthTokenNotSet = errors.New("auth-token not set") +) + +type Config struct { + URL string `yaml:"url,omitempty"` + AuthToken string `yaml:"auth-token,omitempty"` + SourceURL string `yaml:"source-url,omitempty"` + Metadata map[string]interface{} `yaml:"metadata,omitempty"` +} + +func (cfg *Config) Validate() error { + if len(cfg.URL) == 0 { + return ErrURLNotSet + } + if !strings.HasPrefix(cfg.URL, restAPIUrl) { + return ErrURLNotPrefixedWithRestAPIURL + } + if len(cfg.AuthToken) == 0 { + return ErrAuthTokenNotSet + } + return nil +} + +func (cfg *Config) Merge(override *Config) { + if len(override.URL) > 0 { + cfg.URL = override.URL + } + if len(override.AuthToken) > 0 { + cfg.AuthToken = override.AuthToken + } + if len(override.SourceURL) > 0 { + cfg.SourceURL = override.SourceURL + } + if len(override.Metadata) > 0 { + cfg.Metadata = override.Metadata + } +} + +// AlertProvider is the configuration necessary for sending an alert using incident.io +type AlertProvider struct { + DefaultConfig Config `yaml:",inline"` + + // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type + DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` + + // Overrides is a list of Override that may be prioritized over the default configuration + Overrides []Override `yaml:"overrides,omitempty"` +} + +type Override struct { + Group string `yaml:"group"` + Config `yaml:",inline"` +} + +func (provider *AlertProvider) Validate() error { + registeredGroups := make(map[string]bool) + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if isAlreadyRegistered := registeredGroups[override.Group]; isAlreadyRegistered || override.Group == "" { + return ErrDuplicateGroupOverride + } + registeredGroups[override.Group] = true + } + } + return provider.DefaultConfig.Validate() +} + +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + cfg, err := provider.GetConfig(ep.Group, alert) + if err != nil { + return err + } + buffer := bytes.NewBuffer(provider.buildRequestBody(cfg, ep, alert, result, resolved)) + req, err := http.NewRequest(http.MethodPost, cfg.URL, buffer) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+cfg.AuthToken) + response, err := client.GetHTTPClient(nil).Do(req) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode > 399 { + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body)) + } + incidentioResponse := Response{} + err = json.NewDecoder(response.Body).Decode(&incidentioResponse) + if err != nil { + // Silently fail. We don't want to create tons of alerts just because we failed to parse the body. + logr.Errorf("[incidentio.Send] Ran into error decoding pagerduty response: %s", err.Error()) + } + alert.ResolveKey = incidentioResponse.DeduplicationKey + return err +} + +type Body struct { + AlertSourceConfigID string `json:"alert_source_config_id"` + Status string `json:"status"` + Title string `json:"title"` + DeduplicationKey string `json:"deduplication_key,omitempty"` + Description string `json:"description,omitempty"` + SourceURL string `json:"source_url,omitempty"` + Metadata map[string]interface{} `json:"metadata,omitempty"` +} + +type Response struct { + DeduplicationKey string `json:"deduplication_key"` +} + +func (provider *AlertProvider) buildRequestBody(cfg *Config, ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { + var message, formattedConditionResults, status string + if resolved { + message = "An alert has been resolved after passing successfully " + strconv.Itoa(alert.SuccessThreshold) + " time(s) in a row" + status = "resolved" + } else { + message = "An alert has been triggered due to having failed " + strconv.Itoa(alert.FailureThreshold) + " time(s) in a row" + status = "firing" + } + for _, conditionResult := range result.ConditionResults { + var prefix string + if conditionResult.Success { + prefix = "🟢" + } else { + prefix = "🔴" + } + // No need for \n since incident.io trims it anyways. + formattedConditionResults += fmt.Sprintf(" %s %s ", prefix, conditionResult.Condition) + } + if len(alert.GetDescription()) > 0 { + message += " with the following description: " + alert.GetDescription() + } + message += fmt.Sprintf(" and the following conditions: %s ", formattedConditionResults) + var body []byte + alertSourceID := strings.TrimPrefix(cfg.URL, restAPIUrl) + body, _ = json.Marshal(Body{ + AlertSourceConfigID: alertSourceID, + Title: "Gatus: " + ep.DisplayName(), + Status: status, + DeduplicationKey: alert.ResolveKey, + Description: message, + SourceURL: cfg.SourceURL, + Metadata: cfg.Metadata, + }) + fmt.Printf("%v", string(body)) + return body + +} +func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) { + cfg := provider.DefaultConfig + // Handle group overrides + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if group == override.Group { + cfg.Merge(&override.Config) + break + } + } + } + // Handle alert overrides + if len(alert.ProviderOverride) != 0 { + overrideConfig := Config{} + if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil { + return nil, err + } + cfg.Merge(&overrideConfig) + } + // Validate the configuration + err := cfg.Validate() + return &cfg, err +} + +// GetDefaultAlert returns the provider's default alert configuration +func (provider *AlertProvider) GetDefaultAlert() *alert.Alert { + return provider.DefaultAlert +} + +func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error { + _, err := provider.GetConfig(group, alert) + return err +} diff --git a/alerting/provider/incidentio/incidentio_test.go b/alerting/provider/incidentio/incidentio_test.go new file mode 100644 index 00000000..bab479bf --- /dev/null +++ b/alerting/provider/incidentio/incidentio_test.go @@ -0,0 +1,390 @@ +package incidentio + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/test" +) + +func TestAlertProvider_Validate(t *testing.T) { + scenarios := []struct { + name string + provider AlertProvider + expected bool + }{ + { + name: "valid", + provider: AlertProvider{ + DefaultConfig: Config{ + URL: "https://api.incident.io/v2/alert_events/http/some-id", + AuthToken: "some-token", + }, + }, + expected: true, + }, + { + name: "invalid-url", + provider: AlertProvider{ + DefaultConfig: Config{ + URL: "id-without-rest-api-url-as-prefix", + AuthToken: "some-token", + }, + }, + expected: false, + }, + { + name: "invalid-missing-auth-token", + provider: AlertProvider{ + DefaultConfig: Config{ + URL: "some-id", + }, + }, + expected: false, + }, + { + name: "invalid-missing-alert-source-config-id", + provider: AlertProvider{ + DefaultConfig: Config{ + AuthToken: "some-token", + }, + }, + expected: false, + }, + { + name: "valid-override", + provider: AlertProvider{ + DefaultConfig: Config{ + AuthToken: "some-token", + URL: "https://api.incident.io/v2/alert_events/http/some-id", + }, + Overrides: []Override{{Group: "core", Config: Config{URL: "https://api.incident.io/v2/alert_events/http/another-id"}}}, + }, + expected: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + err := scenario.provider.Validate() + if scenario.expected && err != nil { + t.Error("expected no error, got", err.Error()) + } + if !scenario.expected && err == nil { + t.Error("expected error, got none") + } + }) + } +} + +func TestAlertProvider_Send(t *testing.T) { + defer client.InjectHTTPClient(nil) + firstDescription := "description-1" + secondDescription := "description-2" + restAPIUrl := "https://api.incident.io/v2/alert_events/http/" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + MockRoundTripper test.MockRoundTripper + ExpectedError bool + }{ + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{ + URL: restAPIUrl + "some-id", + AuthToken: "some-token", + }}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + var b bytes.Buffer + + response := Response{DeduplicationKey: "some-key"} + json.NewEncoder(&b).Encode(response) + reader := io.NopCloser(&b) + return &http.Response{StatusCode: http.StatusAccepted, Body: reader} + }), + ExpectedError: false, + }, + { + Name: "triggered-error", + Provider: AlertProvider{DefaultConfig: Config{ + URL: restAPIUrl + "some-id", + AuthToken: "some-token", + }}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + { + Name: "resolved", + Provider: AlertProvider{DefaultConfig: Config{ + URL: restAPIUrl + "some-id", + AuthToken: "some-token", + }}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + var b bytes.Buffer + response := Response{DeduplicationKey: "some-key"} + json.NewEncoder(&b).Encode(response) + reader := io.NopCloser(&b) + return &http.Response{StatusCode: http.StatusAccepted, Body: reader} + }), + ExpectedError: false, + }, + { + Name: "resolved-error", + Provider: AlertProvider{DefaultConfig: Config{}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) + err := scenario.Provider.Send( + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if scenario.ExpectedError && err == nil { + t.Error("expected error, got none") + } + if !scenario.ExpectedError && err != nil { + t.Error("expected no error, got", err.Error()) + } + }) + } +} + +func TestAlertProvider_BuildRequestBody(t *testing.T) { + firstDescription := "description-1" + secondDescription := "description-2" + restAPIUrl := "https://api.incident.io/v2/alert_events/http/" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + ExpectedBody string + }{ + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{URL: restAPIUrl + "some-id", AuthToken: "some-token"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: `{"alert_source_config_id":"some-id","status":"firing","title":"Gatus: endpoint-name","description":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1 and the following conditions: 🔴 [CONNECTED] == true 🔴 [STATUS] == 200 "}`, + }, + { + Name: "resolved", + Provider: AlertProvider{DefaultConfig: Config{URL: restAPIUrl + "some-id", AuthToken: "some-token"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: `{"alert_source_config_id":"some-id","status":"resolved","title":"Gatus: endpoint-name","description":"An alert has been resolved after passing successfully 5 time(s) in a row with the following description: description-2 and the following conditions: 🟢 [CONNECTED] == true 🟢 [STATUS] == 200 "}`, + }, + { + Name: "resolved-with-metadata-source-url", + Provider: AlertProvider{DefaultConfig: Config{URL: restAPIUrl + "some-id", AuthToken: "some-token", Metadata: map[string]interface{}{"service": "some-service", "team": "very-core"}, SourceURL: "some-source-url"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: `{"alert_source_config_id":"some-id","status":"resolved","title":"Gatus: endpoint-name","description":"An alert has been resolved after passing successfully 5 time(s) in a row with the following description: description-2 and the following conditions: 🟢 [CONNECTED] == true 🟢 [STATUS] == 200 ","source_url":"some-source-url","metadata":{"service":"some-service","team":"very-core"}}`, + }, + { + Name: "group-override", + Provider: AlertProvider{DefaultConfig: Config{URL: restAPIUrl + "some-id", AuthToken: "some-token"}, Overrides: []Override{{Group: "g", Config: Config{URL: restAPIUrl + "different-id", AuthToken: "some-token"}}}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: `{"alert_source_config_id":"different-id","status":"firing","title":"Gatus: endpoint-name","description":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1 and the following conditions: 🔴 [CONNECTED] == true 🔴 [STATUS] == 200 "}`, + }, + } + + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + cfg, err := scenario.Provider.GetConfig("g", &scenario.Alert) + if err != nil { + t.Error("expected no error, got", err.Error()) + } + body := scenario.Provider.buildRequestBody( + cfg, + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if string(body) != scenario.ExpectedBody { + t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body) + } + out := make(map[string]interface{}) + if err := json.Unmarshal(body, &out); err != nil { + t.Error("expected body to be valid JSON, got error:", err.Error()) + } + }) + } +} + +func TestAlertProvider_GetDefaultAlert(t *testing.T) { + if (&AlertProvider{DefaultAlert: &alert.Alert{}}).GetDefaultAlert() == nil { + t.Error("expected default alert to be not nil") + } + if (&AlertProvider{DefaultAlert: nil}).GetDefaultAlert() != nil { + t.Error("expected default alert to be nil") + } +} + +func TestAlertProvider_GetConfig(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + InputGroup string + InputAlert alert.Alert + ExpectedOutput Config + }{ + { + Name: "provider-no-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{URL: "https://api.incident.io/v2/alert_events/http/some-id", AuthToken: "some-token"}, + Overrides: nil, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{URL: "https://api.incident.io/v2/alert_events/http/some-id", AuthToken: "some-token"}, + }, + { + Name: "provider-no-override-specify-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{URL: "https://api.incident.io/v2/alert_events/http/some-id", AuthToken: "some-token"}, + Overrides: nil, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{URL: "https://api.incident.io/v2/alert_events/http/some-id", AuthToken: "some-token"}, + }, + { + Name: "provider-with-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{URL: "https://api.incident.io/v2/alert_events/http/some-id", AuthToken: "some-token"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{URL: "https://api.incident.io/v2/alert_events/http/diff-id"}, + }, + }, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{URL: "https://api.incident.io/v2/alert_events/http/some-id", AuthToken: "some-token"}, + }, + { + Name: "provider-with-override-specify-group-should-override", + Provider: AlertProvider{ + DefaultConfig: Config{URL: "https://api.incident.io/v2/alert_events/http/some-id", AuthToken: "some-token"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{URL: "https://api.incident.io/v2/alert_events/http/diff-id", AuthToken: "some-token"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{URL: "https://api.incident.io/v2/alert_events/http/diff-id", AuthToken: "some-token"}, + }, + { + Name: "provider-with-group-override-and-alert-override--alert-override-should-take-precedence", + Provider: AlertProvider{ + DefaultConfig: Config{URL: "https://api.incident.io/v2/alert_events/http/some-id", AuthToken: "some-token"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{URL: "https://api.incident.io/v2/alert_events/http/diff-id", AuthToken: "some-token"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{ProviderOverride: map[string]any{"url": "https://api.incident.io/v2/alert_events/http/another-id"}}, + ExpectedOutput: Config{URL: "https://api.incident.io/v2/alert_events/http/another-id", AuthToken: "some-token"}, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + got, err := scenario.Provider.GetConfig(scenario.InputGroup, &scenario.InputAlert) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if got.URL != scenario.ExpectedOutput.URL { + t.Errorf("expected alert source config to be %s, got %s", scenario.ExpectedOutput.URL, got.URL) + } + if got.AuthToken != scenario.ExpectedOutput.AuthToken { + t.Errorf("expected alert auth token to be %s, got %s", scenario.ExpectedOutput.AuthToken, got.AuthToken) + } + + // Test ValidateOverrides as well, since it really just calls GetConfig + if err = scenario.Provider.ValidateOverrides(scenario.InputGroup, &scenario.InputAlert); err != nil { + t.Errorf("unexpected error: %s", err) + } + }) + } +} + +func TestAlertProvider_ValidateWithOverride(t *testing.T) { + providerWithInvalidOverrideGroup := AlertProvider{ + Overrides: []Override{ + { + Config: Config{URL: "https://api.incident.io/v2/alert_events/http/some-id", AuthToken: "some-token"}, + Group: "", + }, + }, + } + if err := providerWithInvalidOverrideGroup.Validate(); err == nil { + t.Error("provider Group shouldn't have been valid") + } + providerWithInvalidOverrideTo := AlertProvider{ + Overrides: []Override{ + { + Config: Config{URL: "", AuthToken: "some-token"}, + Group: "group", + }, + }, + } + if err := providerWithInvalidOverrideTo.Validate(); err == nil { + t.Error("provider integration key shouldn't have been valid") + } + providerWithValidOverride := AlertProvider{ + DefaultConfig: Config{URL: "https://api.incident.io/v2/alert_events/http/nice-id", AuthToken: "some-token"}, + Overrides: []Override{ + { + Config: Config{URL: "https://api.incident.io/v2/alert_events/http/very-good-id", AuthToken: "some-token"}, + Group: "group", + }, + }, + } + if err := providerWithValidOverride.Validate(); err != nil { + t.Error("provider should've been valid") + } +} diff --git a/alerting/provider/jetbrainsspace/jetbrainsspace.go b/alerting/provider/jetbrainsspace/jetbrainsspace.go new file mode 100644 index 00000000..9e5fe7c5 --- /dev/null +++ b/alerting/provider/jetbrainsspace/jetbrainsspace.go @@ -0,0 +1,223 @@ +package jetbrainsspace + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "gopkg.in/yaml.v3" +) + +var ( + ErrProjectNotSet = errors.New("project not set") + ErrChannelIDNotSet = errors.New("channel-id not set") + ErrTokenNotSet = errors.New("token not set") + ErrDuplicateGroupOverride = errors.New("duplicate group override") +) + +type Config struct { + Project string `yaml:"project"` // Project name + ChannelID string `yaml:"channel-id"` // Chat Channel ID + Token string `yaml:"token"` // Bearer Token +} + +func (cfg *Config) Validate() error { + if len(cfg.Project) == 0 { + return ErrProjectNotSet + } + if len(cfg.ChannelID) == 0 { + return ErrChannelIDNotSet + } + if len(cfg.Token) == 0 { + return ErrTokenNotSet + } + return nil +} + +func (cfg *Config) Merge(override *Config) { + if len(override.Project) > 0 { + cfg.Project = override.Project + } + if len(override.ChannelID) > 0 { + cfg.ChannelID = override.ChannelID + } + if len(override.Token) > 0 { + cfg.Token = override.Token + } +} + +// AlertProvider is the configuration necessary for sending an alert using JetBrains Space +type AlertProvider struct { + DefaultConfig Config `yaml:",inline"` + + // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type + DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` + + // Overrides is a list of Override that may be prioritized over the default configuration + Overrides []Override `yaml:"overrides,omitempty"` +} + +// Override is a case under which the default integration is overridden +type Override struct { + Group string `yaml:"group"` + Config `yaml:",inline"` +} + +// Validate the provider's configuration +func (provider *AlertProvider) Validate() error { + registeredGroups := make(map[string]bool) + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if isAlreadyRegistered := registeredGroups[override.Group]; isAlreadyRegistered || override.Group == "" { + return ErrDuplicateGroupOverride + } + registeredGroups[override.Group] = true + } + } + return provider.DefaultConfig.Validate() +} + +// Send an alert using the provider +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + cfg, err := provider.GetConfig(ep.Group, alert) + if err != nil { + return err + } + buffer := bytes.NewBuffer(provider.buildRequestBody(cfg, ep, alert, result, resolved)) + url := fmt.Sprintf("https://%s.jetbrains.space/api/http/chats/messages/send-message", cfg.Project) + request, err := http.NewRequest(http.MethodPost, url, buffer) + if err != nil { + return err + } + request.Header.Set("Content-Type", "application/json") + request.Header.Set("Authorization", "Bearer "+cfg.Token) + response, err := client.GetHTTPClient(nil).Do(request) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode > 399 { + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body)) + } + return err +} + +type Body struct { + Channel string `json:"channel"` + Content Content `json:"content"` +} + +type Content struct { + ClassName string `json:"className"` + Style string `json:"style"` + Sections []Section `json:"sections,omitempty"` +} + +type Section struct { + ClassName string `json:"className"` + Elements []Element `json:"elements"` + Header string `json:"header"` +} + +type Element struct { + ClassName string `json:"className"` + Accessory Accessory `json:"accessory"` + Style string `json:"style"` + Size string `json:"size"` + Content string `json:"content"` +} + +type Accessory struct { + ClassName string `json:"className"` + Icon Icon `json:"icon"` + Style string `json:"style"` +} + +type Icon struct { + Icon string `json:"icon"` +} + +// buildRequestBody builds the request body for the provider +func (provider *AlertProvider) buildRequestBody(cfg *Config, ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { + body := Body{ + Channel: "id:" + cfg.ChannelID, + Content: Content{ + ClassName: "ChatMessage.Block", + Sections: []Section{{ + ClassName: "MessageSection", + Elements: []Element{}, + }}, + }, + } + if resolved { + body.Content.Style = "SUCCESS" + body.Content.Sections[0].Header = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) + } else { + body.Content.Style = "WARNING" + body.Content.Sections[0].Header = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) + } + for _, conditionResult := range result.ConditionResults { + icon := "warning" + style := "WARNING" + if conditionResult.Success { + icon = "success" + style = "SUCCESS" + } + body.Content.Sections[0].Elements = append(body.Content.Sections[0].Elements, Element{ + ClassName: "MessageText", + Accessory: Accessory{ + ClassName: "MessageIcon", + Icon: Icon{Icon: icon}, + Style: style, + }, + Style: style, + Size: "REGULAR", + Content: conditionResult.Condition, + }) + } + bodyAsJSON, _ := json.Marshal(body) + return bodyAsJSON +} + +// GetDefaultAlert returns the provider's default alert configuration +func (provider *AlertProvider) GetDefaultAlert() *alert.Alert { + return provider.DefaultAlert +} + +// GetConfig returns the configuration for the provider with the overrides applied +func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) { + cfg := provider.DefaultConfig + // Handle group overrides + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if group == override.Group { + cfg.Merge(&override.Config) + break + } + } + } + // Handle alert overrides + if len(alert.ProviderOverride) != 0 { + overrideConfig := Config{} + if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil { + return nil, err + } + cfg.Merge(&overrideConfig) + } + // Validate the configuration + err := cfg.Validate() + return &cfg, err +} + +// ValidateOverrides validates the alert's provider override and, if present, the group override +func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error { + _, err := provider.GetConfig(group, alert) + return err +} diff --git a/alerting/provider/jetbrainsspace/jetbrainsspace_test.go b/alerting/provider/jetbrainsspace/jetbrainsspace_test.go new file mode 100644 index 00000000..57f834f6 --- /dev/null +++ b/alerting/provider/jetbrainsspace/jetbrainsspace_test.go @@ -0,0 +1,318 @@ +package jetbrainsspace + +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/test" +) + +func TestAlertProvider_Validate(t *testing.T) { + invalidProvider := AlertProvider{DefaultConfig: Config{Project: ""}} + if err := invalidProvider.Validate(); err == nil { + t.Error("provider shouldn't have been valid") + } + validProvider := AlertProvider{DefaultConfig: Config{Project: "foo", ChannelID: "bar", Token: "baz"}} + if err := validProvider.Validate(); err != nil { + t.Error("provider should've been valid") + } +} + +func TestAlertProvider_ValidateWithOverride(t *testing.T) { + providerWithInvalidOverrideGroup := AlertProvider{ + DefaultConfig: Config{Project: "foobar"}, + Overrides: []Override{ + { + Config: Config{ChannelID: "http://example.com"}, + Group: "", + }, + }, + } + if err := providerWithInvalidOverrideGroup.Validate(); err == nil { + t.Error("provider Group shouldn't have been valid") + } + providerWithInvalidOverrideTo := AlertProvider{ + DefaultConfig: Config{Project: "foobar"}, + Overrides: []Override{ + { + Config: Config{ChannelID: ""}, + Group: "group", + }, + }, + } + if err := providerWithInvalidOverrideTo.Validate(); err == nil { + t.Error("provider integration key shouldn't have been valid") + } + providerWithValidOverride := AlertProvider{ + DefaultConfig: Config{ + Project: "foo", + ChannelID: "bar", + Token: "baz", + }, + Overrides: []Override{ + { + Config: Config{ChannelID: "foobar"}, + Group: "group", + }, + }, + } + if err := providerWithValidOverride.Validate(); err != nil { + t.Error("provider should've been valid") + } +} + +func TestAlertProvider_Send(t *testing.T) { + defer client.InjectHTTPClient(nil) + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + MockRoundTripper test.MockRoundTripper + ExpectedError bool + }{ + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{ChannelID: "1", Project: "project", Token: "token"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "triggered-error", + Provider: AlertProvider{DefaultConfig: Config{ChannelID: "1", Project: "project", Token: "token"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + { + Name: "resolved", + Provider: AlertProvider{DefaultConfig: Config{ChannelID: "1", Project: "project", Token: "token"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "resolved-error", + Provider: AlertProvider{DefaultConfig: Config{ChannelID: "1", Project: "project", Token: "token"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) + err := scenario.Provider.Send( + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if scenario.ExpectedError && err == nil { + t.Error("expected error, got none") + } + if !scenario.ExpectedError && err != nil { + t.Error("expected no error, got", err.Error()) + } + }) + } +} + +func TestAlertProvider_buildRequestBody(t *testing.T) { + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Endpoint endpoint.Endpoint + Alert alert.Alert + Resolved bool + ExpectedBody string + }{ + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{ChannelID: "1", Project: "project"}}, + Endpoint: endpoint.Endpoint{Name: "name"}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: `{"channel":"id:1","content":{"className":"ChatMessage.Block","style":"WARNING","sections":[{"className":"MessageSection","elements":[{"className":"MessageText","accessory":{"className":"MessageIcon","icon":{"icon":"warning"},"style":"WARNING"},"style":"WARNING","size":"REGULAR","content":"[CONNECTED] == true"},{"className":"MessageText","accessory":{"className":"MessageIcon","icon":{"icon":"warning"},"style":"WARNING"},"style":"WARNING","size":"REGULAR","content":"[STATUS] == 200"}],"header":"An alert for *name* has been triggered due to having failed 3 time(s) in a row"}]}}`, + }, + { + Name: "triggered-with-group", + Provider: AlertProvider{DefaultConfig: Config{ChannelID: "1", Project: "project"}}, + Endpoint: endpoint.Endpoint{Name: "name", Group: "group"}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: `{"channel":"id:1","content":{"className":"ChatMessage.Block","style":"WARNING","sections":[{"className":"MessageSection","elements":[{"className":"MessageText","accessory":{"className":"MessageIcon","icon":{"icon":"warning"},"style":"WARNING"},"style":"WARNING","size":"REGULAR","content":"[CONNECTED] == true"},{"className":"MessageText","accessory":{"className":"MessageIcon","icon":{"icon":"warning"},"style":"WARNING"},"style":"WARNING","size":"REGULAR","content":"[STATUS] == 200"}],"header":"An alert for *group/name* has been triggered due to having failed 3 time(s) in a row"}]}}`, + }, + { + Name: "resolved", + Provider: AlertProvider{DefaultConfig: Config{ChannelID: "1", Project: "project"}}, + Endpoint: endpoint.Endpoint{Name: "name"}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: `{"channel":"id:1","content":{"className":"ChatMessage.Block","style":"SUCCESS","sections":[{"className":"MessageSection","elements":[{"className":"MessageText","accessory":{"className":"MessageIcon","icon":{"icon":"success"},"style":"SUCCESS"},"style":"SUCCESS","size":"REGULAR","content":"[CONNECTED] == true"},{"className":"MessageText","accessory":{"className":"MessageIcon","icon":{"icon":"success"},"style":"SUCCESS"},"style":"SUCCESS","size":"REGULAR","content":"[STATUS] == 200"}],"header":"An alert for *name* has been resolved after passing successfully 5 time(s) in a row"}]}}`, + }, + { + Name: "resolved-with-group", + Provider: AlertProvider{DefaultConfig: Config{ChannelID: "1", Project: "project"}}, + Endpoint: endpoint.Endpoint{Name: "name", Group: "group"}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: `{"channel":"id:1","content":{"className":"ChatMessage.Block","style":"SUCCESS","sections":[{"className":"MessageSection","elements":[{"className":"MessageText","accessory":{"className":"MessageIcon","icon":{"icon":"success"},"style":"SUCCESS"},"style":"SUCCESS","size":"REGULAR","content":"[CONNECTED] == true"},{"className":"MessageText","accessory":{"className":"MessageIcon","icon":{"icon":"success"},"style":"SUCCESS"},"style":"SUCCESS","size":"REGULAR","content":"[STATUS] == 200"}],"header":"An alert for *group/name* has been resolved after passing successfully 5 time(s) in a row"}]}}`, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + body := scenario.Provider.buildRequestBody( + &scenario.Provider.DefaultConfig, + &scenario.Endpoint, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if string(body) != scenario.ExpectedBody { + t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body) + } + out := make(map[string]interface{}) + if err := json.Unmarshal(body, &out); err != nil { + t.Error("expected body to be valid JSON, got error:", err.Error()) + } + }) + } +} + +func TestAlertProvider_GetDefaultAlert(t *testing.T) { + if (&AlertProvider{DefaultAlert: &alert.Alert{}}).GetDefaultAlert() == nil { + t.Error("expected default alert to be not nil") + } + if (&AlertProvider{DefaultAlert: nil}).GetDefaultAlert() != nil { + t.Error("expected default alert to be nil") + } +} + +func TestAlertProvider_GetConfig(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + InputGroup string + InputAlert alert.Alert + ExpectedOutput Config + }{ + { + Name: "provider-no-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{ChannelID: "default", Project: "project", Token: "token"}, + Overrides: nil, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{ChannelID: "default", Project: "project", Token: "token"}, + }, + { + Name: "provider-no-override-specify-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{ChannelID: "default", Project: "project", Token: "token"}, + Overrides: nil, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{ChannelID: "default", Project: "project", Token: "token"}, + }, + { + Name: "provider-with-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{ChannelID: "default", Project: "project", Token: "token"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{ChannelID: "group-channel"}, + }, + }, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{ChannelID: "default", Project: "project", Token: "token"}, + }, + { + Name: "provider-with-override-specify-group-should-override", + Provider: AlertProvider{ + DefaultConfig: Config{ChannelID: "default", Project: "project", Token: "token"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{ChannelID: "group-channel"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{ChannelID: "group-channel", Project: "project", Token: "token"}, + }, + { + Name: "provider-with-group-override-and-alert-override--alert-override-should-take-precedence", + Provider: AlertProvider{ + DefaultConfig: Config{ChannelID: "default", Project: "project", Token: "token"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{ChannelID: "group-channel"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{ProviderOverride: map[string]any{"channel-id": "alert-channel", "project": "alert-project", "token": "alert-token"}}, + ExpectedOutput: Config{ChannelID: "alert-channel", Project: "alert-project", Token: "alert-token"}, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + got, err := scenario.Provider.GetConfig(scenario.InputGroup, &scenario.InputAlert) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if got.ChannelID != scenario.ExpectedOutput.ChannelID { + t.Errorf("expected %s, got %s", scenario.ExpectedOutput.ChannelID, got.ChannelID) + } + if got.Project != scenario.ExpectedOutput.Project { + t.Errorf("expected %s, got %s", scenario.ExpectedOutput.Project, got.Project) + } + if got.Token != scenario.ExpectedOutput.Token { + t.Errorf("expected %s, got %s", scenario.ExpectedOutput.Token, got.Token) + } + // Test ValidateOverrides as well, since it really just calls GetConfig + if err = scenario.Provider.ValidateOverrides(scenario.InputGroup, &scenario.InputAlert); err != nil { + t.Errorf("unexpected error: %s", err) + } + }) + } +} diff --git a/alerting/provider/matrix/matrix.go b/alerting/provider/matrix/matrix.go new file mode 100644 index 00000000..e735d148 --- /dev/null +++ b/alerting/provider/matrix/matrix.go @@ -0,0 +1,247 @@ +package matrix + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "math/rand" + "net/http" + "net/url" + "time" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "gopkg.in/yaml.v3" +) + +const defaultServerURL = "https://matrix-client.matrix.org" + +var ( + ErrAccessTokenNotSet = errors.New("access-token not set") + ErrInternalRoomID = errors.New("internal-room-id not set") + ErrDuplicateGroupOverride = errors.New("duplicate group override") +) + +type Config struct { + // ServerURL is the custom homeserver to use (optional) + ServerURL string `yaml:"server-url"` + + // AccessToken is the bot user's access token to send messages + AccessToken string `yaml:"access-token"` + + // InternalRoomID is the room that the bot user has permissions to send messages to + InternalRoomID string `yaml:"internal-room-id"` +} + +func (cfg *Config) Validate() error { + if len(cfg.ServerURL) == 0 { + cfg.ServerURL = defaultServerURL + } + if len(cfg.AccessToken) == 0 { + return ErrAccessTokenNotSet + } + if len(cfg.InternalRoomID) == 0 { + return ErrInternalRoomID + } + return nil +} + +func (cfg *Config) Merge(override *Config) { + if len(override.ServerURL) > 0 { + cfg.ServerURL = override.ServerURL + } + if len(override.AccessToken) > 0 { + cfg.AccessToken = override.AccessToken + } + if len(override.InternalRoomID) > 0 { + cfg.InternalRoomID = override.InternalRoomID + } +} + +// AlertProvider is the configuration necessary for sending an alert using Matrix +type AlertProvider struct { + DefaultConfig Config `yaml:",inline"` + + // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type + DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` + + // Overrides is a list of Override that may be prioritized over the default configuration + Overrides []Override `yaml:"overrides,omitempty"` +} + +// Override is a case under which the default integration is overridden +type Override struct { + Group string `yaml:"group"` + Config `yaml:",inline"` +} + +// Validate the provider's configuration +func (provider *AlertProvider) Validate() error { + registeredGroups := make(map[string]bool) + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if isAlreadyRegistered := registeredGroups[override.Group]; isAlreadyRegistered || override.Group == "" || len(override.AccessToken) == 0 || len(override.InternalRoomID) == 0 { + return ErrDuplicateGroupOverride + } + registeredGroups[override.Group] = true + } + } + return provider.DefaultConfig.Validate() +} + +// Send an alert using the provider +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + cfg, err := provider.GetConfig(ep.Group, alert) + if err != nil { + return err + } + buffer := bytes.NewBuffer(provider.buildRequestBody(ep, alert, result, resolved)) + // The Matrix endpoint requires a unique transaction ID for each event sent + txnId := randStringBytes(24) + request, err := http.NewRequest( + http.MethodPut, + fmt.Sprintf("%s/_matrix/client/v3/rooms/%s/send/m.room.message/%s?access_token=%s", + cfg.ServerURL, + url.PathEscape(cfg.InternalRoomID), + txnId, + url.QueryEscape(cfg.AccessToken), + ), + buffer, + ) + if err != nil { + return err + } + request.Header.Set("Content-Type", "application/json") + response, err := client.GetHTTPClient(nil).Do(request) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode > 399 { + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body)) + } + return err +} + +type Body struct { + MsgType string `json:"msgtype"` + Format string `json:"format"` + Body string `json:"body"` + FormattedBody string `json:"formatted_body"` +} + +// buildRequestBody builds the request body for the provider +func (provider *AlertProvider) buildRequestBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { + body, _ := json.Marshal(Body{ + MsgType: "m.text", + Format: "org.matrix.custom.html", + Body: buildPlaintextMessageBody(ep, alert, result, resolved), + FormattedBody: buildHTMLMessageBody(ep, alert, result, resolved), + }) + return body +} + +// buildPlaintextMessageBody builds the message body in plaintext to include in request +func buildPlaintextMessageBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) string { + var message string + if resolved { + message = fmt.Sprintf("An alert for `%s` has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) + } else { + message = fmt.Sprintf("An alert for `%s` has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) + } + var formattedConditionResults string + for _, conditionResult := range result.ConditionResults { + var prefix string + if conditionResult.Success { + prefix = "✓" + } else { + prefix = "✕" + } + formattedConditionResults += fmt.Sprintf("\n%s - %s", prefix, conditionResult.Condition) + } + var description string + if alertDescription := alert.GetDescription(); len(alertDescription) > 0 { + description = "\n" + alertDescription + } + return fmt.Sprintf("%s%s\n%s", message, description, formattedConditionResults) +} + +// buildHTMLMessageBody builds the message body in HTML to include in request +func buildHTMLMessageBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) string { + var message string + if resolved { + message = fmt.Sprintf("An alert for %s has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) + } else { + message = fmt.Sprintf("An alert for %s has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) + } + var formattedConditionResults string + if len(result.ConditionResults) > 0 { + formattedConditionResults = "\n
Condition results
    " + for _, conditionResult := range result.ConditionResults { + var prefix string + if conditionResult.Success { + prefix = "✅" + } else { + prefix = "❌" + } + formattedConditionResults += fmt.Sprintf("
  • %s - %s
  • ", prefix, conditionResult.Condition) + } + formattedConditionResults += "
" + } + var description string + if alertDescription := alert.GetDescription(); len(alertDescription) > 0 { + description = fmt.Sprintf("\n
%s
", alertDescription) + } + return fmt.Sprintf("

%s

%s%s", message, description, formattedConditionResults) +} + +func randStringBytes(n int) string { + // All the compatible characters to use in a transaction ID + const availableCharacterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + b := make([]byte, n) + rand.Seed(time.Now().UnixNano()) + for i := range b { + b[i] = availableCharacterBytes[rand.Intn(len(availableCharacterBytes))] + } + return string(b) +} + +// GetDefaultAlert returns the provider's default alert configuration +func (provider *AlertProvider) GetDefaultAlert() *alert.Alert { + return provider.DefaultAlert +} + +// GetConfig returns the configuration for the provider with the overrides applied +func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) { + cfg := provider.DefaultConfig + // Handle group overrides + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if group == override.Group { + cfg.Merge(&override.Config) + break + } + } + } + // Handle alert overrides + if len(alert.ProviderOverride) != 0 { + overrideConfig := Config{} + if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil { + return nil, err + } + cfg.Merge(&overrideConfig) + } + // Validate the configuration + err := cfg.Validate() + return &cfg, err +} + +// ValidateOverrides validates the alert's provider override and, if present, the group override +func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error { + _, err := provider.GetConfig(group, alert) + return err +} diff --git a/alerting/provider/matrix/matrix_test.go b/alerting/provider/matrix/matrix_test.go new file mode 100644 index 00000000..561ae16c --- /dev/null +++ b/alerting/provider/matrix/matrix_test.go @@ -0,0 +1,387 @@ +package matrix + +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/test" +) + +func TestAlertProvider_Validate(t *testing.T) { + invalidProvider := AlertProvider{ + DefaultConfig: Config{ + AccessToken: "", + InternalRoomID: "", + }, + } + if err := invalidProvider.Validate(); err == nil { + t.Error("provider shouldn't have been valid") + } + validProvider := AlertProvider{ + DefaultConfig: Config{ + AccessToken: "1", + InternalRoomID: "!a:example.com", + }, + } + if err := validProvider.Validate(); err != nil { + t.Error("provider should've been valid") + } + validProviderWithHomeserver := AlertProvider{ + DefaultConfig: Config{ + ServerURL: "https://example.com", + AccessToken: "1", + InternalRoomID: "!a:example.com", + }, + } + if err := validProviderWithHomeserver.Validate(); err != nil { + t.Error("provider with homeserver should've been valid") + } +} + +func TestAlertProvider_ValidateWithOverride(t *testing.T) { + providerWithInvalidOverrideGroup := AlertProvider{ + Overrides: []Override{ + { + Group: "", + Config: Config{ + AccessToken: "", + InternalRoomID: "", + }, + }, + }, + } + if err := providerWithInvalidOverrideGroup.Validate(); err == nil { + t.Error("provider Group shouldn't have been valid") + } + providerWithInvalidOverrideTo := AlertProvider{ + Overrides: []Override{ + { + Group: "group", + Config: Config{ + AccessToken: "", + InternalRoomID: "", + }, + }, + }, + } + if err := providerWithInvalidOverrideTo.Validate(); err == nil { + t.Error("provider integration key shouldn't have been valid") + } + providerWithValidOverride := AlertProvider{ + DefaultConfig: Config{ + AccessToken: "1", + InternalRoomID: "!a:example.com", + }, + Overrides: []Override{ + { + Group: "group", + Config: Config{ + ServerURL: "https://example.com", + AccessToken: "1", + InternalRoomID: "!a:example.com", + }, + }, + }, + } + if err := providerWithValidOverride.Validate(); err != nil { + t.Error("provider should've been valid") + } +} + +func TestAlertProvider_Send(t *testing.T) { + defer client.InjectHTTPClient(nil) + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + MockRoundTripper test.MockRoundTripper + ExpectedError bool + }{ + { + Name: "triggered-with-bad-config", + Provider: AlertProvider{}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: true, + }, + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{AccessToken: "1", InternalRoomID: "!a:example.com"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "triggered-error", + Provider: AlertProvider{DefaultConfig: Config{AccessToken: "1", InternalRoomID: "!a:example.com"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + { + Name: "resolved", + Provider: AlertProvider{DefaultConfig: Config{AccessToken: "1", InternalRoomID: "!a:example.com"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "resolved-error", + Provider: AlertProvider{DefaultConfig: Config{AccessToken: "1", InternalRoomID: "!a:example.com"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) + err := scenario.Provider.Send( + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if scenario.ExpectedError && err == nil { + t.Error("expected error, got none") + } + if !scenario.ExpectedError && err != nil { + t.Error("expected no error, got", err.Error()) + } + }) + } +} + +func TestAlertProvider_buildRequestBody(t *testing.T) { + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + ExpectedBody string + }{ + { + Name: "triggered", + Provider: AlertProvider{}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: "{\"msgtype\":\"m.text\",\"format\":\"org.matrix.custom.html\",\"body\":\"An alert for `endpoint-name` has been triggered due to having failed 3 time(s) in a row\\ndescription-1\\n\\n✕ - [CONNECTED] == true\\n✕ - [STATUS] == 200\",\"formatted_body\":\"\\u003ch3\\u003eAn alert for \\u003ccode\\u003eendpoint-name\\u003c/code\\u003e has been triggered due to having failed 3 time(s) in a row\\u003c/h3\\u003e\\n\\u003cblockquote\\u003edescription-1\\u003c/blockquote\\u003e\\n\\u003ch5\\u003eCondition results\\u003c/h5\\u003e\\u003cul\\u003e\\u003cli\\u003e❌ - \\u003ccode\\u003e[CONNECTED] == true\\u003c/code\\u003e\\u003c/li\\u003e\\u003cli\\u003e❌ - \\u003ccode\\u003e[STATUS] == 200\\u003c/code\\u003e\\u003c/li\\u003e\\u003c/ul\\u003e\"}", + }, + { + Name: "resolved", + Provider: AlertProvider{}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: "{\"msgtype\":\"m.text\",\"format\":\"org.matrix.custom.html\",\"body\":\"An alert for `endpoint-name` has been resolved after passing successfully 5 time(s) in a row\\ndescription-2\\n\\n✓ - [CONNECTED] == true\\n✓ - [STATUS] == 200\",\"formatted_body\":\"\\u003ch3\\u003eAn alert for \\u003ccode\\u003eendpoint-name\\u003c/code\\u003e has been resolved after passing successfully 5 time(s) in a row\\u003c/h3\\u003e\\n\\u003cblockquote\\u003edescription-2\\u003c/blockquote\\u003e\\n\\u003ch5\\u003eCondition results\\u003c/h5\\u003e\\u003cul\\u003e\\u003cli\\u003e✅ - \\u003ccode\\u003e[CONNECTED] == true\\u003c/code\\u003e\\u003c/li\\u003e\\u003cli\\u003e✅ - \\u003ccode\\u003e[STATUS] == 200\\u003c/code\\u003e\\u003c/li\\u003e\\u003c/ul\\u003e\"}", + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + body := scenario.Provider.buildRequestBody( + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if string(body) != scenario.ExpectedBody { + t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body) + } + out := make(map[string]interface{}) + if err := json.Unmarshal(body, &out); err != nil { + t.Error("expected body to be valid JSON, got error:", err.Error()) + } + }) + } +} + +func TestAlertProvider_GetDefaultAlert(t *testing.T) { + if (&AlertProvider{DefaultAlert: &alert.Alert{}}).GetDefaultAlert() == nil { + t.Error("expected default alert to be not nil") + } + if (&AlertProvider{DefaultAlert: nil}).GetDefaultAlert() != nil { + t.Error("expected default alert to be nil") + } +} + +func TestAlertProvider_GetConfig(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + InputGroup string + InputAlert alert.Alert + ExpectedOutput Config + }{ + { + Name: "provider-no-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{ + ServerURL: "https://example.com", + AccessToken: "1", + InternalRoomID: "!a:example.com", + }, + Overrides: nil, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{ + ServerURL: "https://example.com", + AccessToken: "1", + InternalRoomID: "!a:example.com", + }, + }, + { + Name: "provider-no-override-specify-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{ + ServerURL: "https://example.com", + AccessToken: "1", + InternalRoomID: "!a:example.com", + }, + Overrides: nil, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{ + ServerURL: "https://example.com", + AccessToken: "1", + InternalRoomID: "!a:example.com", + }, + }, + { + Name: "provider-with-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{ + ServerURL: "https://example.com", + AccessToken: "1", + InternalRoomID: "!a:example.com", + }, + Overrides: []Override{ + { + Group: "group", + Config: Config{ + ServerURL: "https://group-example.com", + AccessToken: "12", + InternalRoomID: "!a:group-example.com", + }, + }, + }, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{ + ServerURL: "https://example.com", + AccessToken: "1", + InternalRoomID: "!a:example.com", + }, + }, + { + Name: "provider-with-override-specify-group-should-override", + Provider: AlertProvider{ + DefaultConfig: Config{ + ServerURL: "https://example.com", + AccessToken: "1", + InternalRoomID: "!a:example.com", + }, + Overrides: []Override{ + { + Group: "group", + Config: Config{ + ServerURL: "https://group-example.com", + AccessToken: "12", + InternalRoomID: "!a:group-example.com", + }, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{ + ServerURL: "https://group-example.com", + AccessToken: "12", + InternalRoomID: "!a:group-example.com", + }, + }, + { + Name: "provider-with-group-override-and-alert-override--alert-override-should-take-precedence", + Provider: AlertProvider{ + DefaultConfig: Config{ + ServerURL: "https://example.com", + AccessToken: "1", + InternalRoomID: "!a:example.com", + }, + Overrides: []Override{ + { + Group: "group", + Config: Config{ + ServerURL: "https://group-example.com", + AccessToken: "12", + InternalRoomID: "!a:example01.com", + }, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{ProviderOverride: map[string]any{"server-url": "https://alert-example.com", "access-token": "123", "internal-room-id": "!a:alert-example.com"}}, + ExpectedOutput: Config{ + ServerURL: "https://alert-example.com", + AccessToken: "123", + InternalRoomID: "!a:alert-example.com", + }, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + outputConfig, err := scenario.Provider.GetConfig(scenario.InputGroup, &scenario.InputAlert) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + if outputConfig.ServerURL != scenario.ExpectedOutput.ServerURL { + t.Errorf("expected ServerURL to be %s, got %s", scenario.ExpectedOutput.ServerURL, outputConfig.ServerURL) + } + if outputConfig.AccessToken != scenario.ExpectedOutput.AccessToken { + t.Errorf("expected AccessToken to be %s, got %s", scenario.ExpectedOutput.AccessToken, outputConfig.AccessToken) + } + if outputConfig.InternalRoomID != scenario.ExpectedOutput.InternalRoomID { + t.Errorf("expected InternalRoomID to be %s, got %s", scenario.ExpectedOutput.InternalRoomID, outputConfig.InternalRoomID) + } + // Test ValidateOverrides as well, since it really just calls GetConfig + if err = scenario.Provider.ValidateOverrides(scenario.InputGroup, &scenario.InputAlert); err != nil { + t.Errorf("unexpected error: %s", err) + } + }) + } +} diff --git a/alerting/provider/mattermost/mattermost.go b/alerting/provider/mattermost/mattermost.go new file mode 100644 index 00000000..564eb990 --- /dev/null +++ b/alerting/provider/mattermost/mattermost.go @@ -0,0 +1,211 @@ +package mattermost + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "gopkg.in/yaml.v3" +) + +var ( + ErrWebhookURLNotSet = errors.New("webhook URL not set") + ErrDuplicateGroupOverride = errors.New("duplicate group override") +) + +type Config struct { + WebhookURL string `yaml:"webhook-url"` + Channel string `yaml:"channel,omitempty"` + ClientConfig *client.Config `yaml:"client,omitempty"` +} + +func (cfg *Config) Validate() error { + if len(cfg.WebhookURL) == 0 { + return ErrWebhookURLNotSet + } + return nil +} + +func (cfg *Config) Merge(override *Config) { + if override.ClientConfig != nil { + cfg.ClientConfig = override.ClientConfig + } + if len(override.WebhookURL) > 0 { + cfg.WebhookURL = override.WebhookURL + } + if len(override.Channel) > 0 { + cfg.Channel = override.Channel + } +} + +// AlertProvider is the configuration necessary for sending an alert using Mattermost +type AlertProvider struct { + DefaultConfig Config `yaml:",inline"` + + // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type + DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` + + // Overrides is a list of Override that may be prioritized over the default configuration + Overrides []Override `yaml:"overrides,omitempty"` +} + +// Override is a case under which the default integration is overridden +type Override struct { + Group string `yaml:"group"` + Config `yaml:",inline"` +} + +// Validate the provider's configuration +func (provider *AlertProvider) Validate() error { + if provider.Overrides != nil { + registeredGroups := make(map[string]bool) + for _, override := range provider.Overrides { + if isAlreadyRegistered := registeredGroups[override.Group]; isAlreadyRegistered || override.Group == "" || len(override.WebhookURL) == 0 { + return ErrDuplicateGroupOverride + } + registeredGroups[override.Group] = true + } + } + return provider.DefaultConfig.Validate() +} + +// Send an alert using the provider +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + cfg, err := provider.GetConfig(ep.Group, alert) + if err != nil { + return err + } + buffer := bytes.NewBuffer([]byte(provider.buildRequestBody(cfg, ep, alert, result, resolved))) + request, err := http.NewRequest(http.MethodPost, cfg.WebhookURL, buffer) + if err != nil { + return err + } + request.Header.Set("Content-Type", "application/json") + response, err := client.GetHTTPClient(cfg.ClientConfig).Do(request) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode > 399 { + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body)) + } + return err +} + +type Body struct { + Channel string `json:"channel,omitempty"` // Optional channel override + Text string `json:"text"` + Username string `json:"username"` + IconURL string `json:"icon_url"` + Attachments []Attachment `json:"attachments"` +} + +type Attachment struct { + Title string `json:"title"` + Fallback string `json:"fallback"` + Text string `json:"text"` + Short bool `json:"short"` + Color string `json:"color"` + Fields []Field `json:"fields"` +} + +type Field struct { + Title string `json:"title"` + Value string `json:"value"` + Short bool `json:"short"` +} + +// buildRequestBody builds the request body for the provider +func (provider *AlertProvider) buildRequestBody(cfg *Config, ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { + var message, color string + if resolved { + message = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) + color = "#36A64F" + } else { + message = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) + color = "#DD0000" + } + var formattedConditionResults string + if len(result.ConditionResults) > 0 { + for _, conditionResult := range result.ConditionResults { + var prefix string + if conditionResult.Success { + prefix = ":white_check_mark:" + } else { + prefix = ":x:" + } + formattedConditionResults += fmt.Sprintf("%s - `%s`\n", prefix, conditionResult.Condition) + } + } + var description string + if alertDescription := alert.GetDescription(); len(alertDescription) > 0 { + description = ":\n> " + alertDescription + } + body := Body{ + Channel: cfg.Channel, + Text: "", + Username: "gatus", + IconURL: "https://raw.githubusercontent.com/TwiN/gatus/master/.github/assets/logo.png", + Attachments: []Attachment{ + { + Title: ":helmet_with_white_cross: Gatus", + Fallback: "Gatus - " + message, + Text: message + description, + Short: false, + Color: color, + }, + }, + } + if len(formattedConditionResults) > 0 { + body.Attachments[0].Fields = append(body.Attachments[0].Fields, Field{ + Title: "Condition results", + Value: formattedConditionResults, + Short: false, + }) + } + bodyAsJSON, _ := json.Marshal(body) + return bodyAsJSON +} + +// GetDefaultAlert returns the provider's default alert configuration +func (provider *AlertProvider) GetDefaultAlert() *alert.Alert { + return provider.DefaultAlert +} + +// GetConfig returns the configuration for the provider with the overrides applied +func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) { + cfg := provider.DefaultConfig + // Handle group overrides + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if group == override.Group { + cfg.Merge(&override.Config) + break + } + } + } + // Handle alert overrides + if len(alert.ProviderOverride) != 0 { + overrideConfig := Config{} + if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil { + return nil, err + } + cfg.Merge(&overrideConfig) + } + // Validate the configuration + err := cfg.Validate() + return &cfg, err +} + +// ValidateOverrides validates the alert's provider override and, if present, the group override +func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error { + _, err := provider.GetConfig(group, alert) + return err +} diff --git a/alerting/provider/mattermost/mattermost_test.go b/alerting/provider/mattermost/mattermost_test.go new file mode 100644 index 00000000..0ce253ab --- /dev/null +++ b/alerting/provider/mattermost/mattermost_test.go @@ -0,0 +1,287 @@ +package mattermost + +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/test" +) + +func TestAlertProvider_Validate(t *testing.T) { + invalidProvider := AlertProvider{DefaultConfig: Config{WebhookURL: ""}} + if err := invalidProvider.Validate(); err == nil { + t.Error("provider shouldn't have been valid") + } + validProvider := AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}} + if err := validProvider.Validate(); err != nil { + t.Error("provider should've been valid") + } +} + +func TestAlertProvider_ValidateWithOverride(t *testing.T) { + providerWithInvalidOverrideGroup := AlertProvider{ + Overrides: []Override{ + { + Config: Config{WebhookURL: "http://example.com"}, + Group: "", + }, + }, + } + if err := providerWithInvalidOverrideGroup.Validate(); err == nil { + t.Error("provider Group shouldn't have been valid") + } + providerWithInvalidOverrideWebHookUrl := AlertProvider{ + Overrides: []Override{ + { + Config: Config{WebhookURL: ""}, + Group: "group", + }, + }, + } + if err := providerWithInvalidOverrideWebHookUrl.Validate(); err == nil { + t.Error("provider WebHookURL shouldn't have been valid") + } + providerWithValidOverride := AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: []Override{ + { + Config: Config{WebhookURL: "http://example.com"}, + Group: "group", + }, + }, + } + if err := providerWithValidOverride.Validate(); err != nil { + t.Error("provider should've been valid") + } +} + +func TestAlertProvider_Send(t *testing.T) { + defer client.InjectHTTPClient(nil) + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + MockRoundTripper test.MockRoundTripper + ExpectedError bool + }{ + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "triggered-error", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + { + Name: "resolved", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "resolved-error", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) + err := scenario.Provider.Send( + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if scenario.ExpectedError && err == nil { + t.Error("expected error, got none") + } + if !scenario.ExpectedError && err != nil { + t.Error("expected no error, got", err.Error()) + } + }) + } +} + +func TestAlertProvider_buildRequestBody(t *testing.T) { + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + ExpectedBody string + }{ + { + Name: "triggered", + Provider: AlertProvider{}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: "{\"text\":\"\",\"username\":\"gatus\",\"icon_url\":\"https://raw.githubusercontent.com/TwiN/gatus/master/.github/assets/logo.png\",\"attachments\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"fallback\":\"Gatus - An alert for *endpoint-name* has been triggered due to having failed 3 time(s) in a row\",\"text\":\"An alert for *endpoint-name* has been triggered due to having failed 3 time(s) in a row:\\n\\u003e description-1\",\"short\":false,\"color\":\"#DD0000\",\"fields\":[{\"title\":\"Condition results\",\"value\":\":x: - `[CONNECTED] == true`\\n:x: - `[STATUS] == 200`\\n\",\"short\":false}]}]}", + }, + { + Name: "resolved", + Provider: AlertProvider{}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: "{\"text\":\"\",\"username\":\"gatus\",\"icon_url\":\"https://raw.githubusercontent.com/TwiN/gatus/master/.github/assets/logo.png\",\"attachments\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"fallback\":\"Gatus - An alert for *endpoint-name* has been resolved after passing successfully 5 time(s) in a row\",\"text\":\"An alert for *endpoint-name* has been resolved after passing successfully 5 time(s) in a row:\\n\\u003e description-2\",\"short\":false,\"color\":\"#36A64F\",\"fields\":[{\"title\":\"Condition results\",\"value\":\":white_check_mark: - `[CONNECTED] == true`\\n:white_check_mark: - `[STATUS] == 200`\\n\",\"short\":false}]}]}", + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + body := scenario.Provider.buildRequestBody( + &scenario.Provider.DefaultConfig, + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if string(body) != scenario.ExpectedBody { + t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body) + } + out := make(map[string]interface{}) + if err := json.Unmarshal(body, &out); err != nil { + t.Error("expected body to be valid JSON, got error:", err.Error()) + } + }) + } +} + +func TestAlertProvider_GetDefaultAlert(t *testing.T) { + if (&AlertProvider{DefaultAlert: &alert.Alert{}}).GetDefaultAlert() == nil { + t.Error("expected default alert to be not nil") + } + if (&AlertProvider{DefaultAlert: nil}).GetDefaultAlert() != nil { + t.Error("expected default alert to be nil") + } +} + +func TestAlertProvider_GetConfig(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + InputGroup string + InputAlert alert.Alert + ExpectedOutput Config + }{ + { + Name: "provider-no-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: nil, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://example.com"}, + }, + { + Name: "provider-no-override-specify-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: nil, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://example.com"}, + }, + { + Name: "provider-with-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{WebhookURL: "http://example01.com"}, + }, + }, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://example.com"}, + }, + { + Name: "provider-with-override-specify-group-should-override", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{WebhookURL: "http://group-example.com"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://group-example.com"}, + }, + { + Name: "provider-with-group-override-and-alert-override--alert-override-should-take-precedence", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{WebhookURL: "http://group-example.com"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{ProviderOverride: map[string]any{"webhook-url": "http://alert-example.com"}}, + ExpectedOutput: Config{WebhookURL: "http://alert-example.com"}, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + got, err := scenario.Provider.GetConfig(scenario.InputGroup, &scenario.InputAlert) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if got.WebhookURL != scenario.ExpectedOutput.WebhookURL { + t.Errorf("expected webhook URL to be %s, got %s", scenario.ExpectedOutput.WebhookURL, got.WebhookURL) + } + // Test ValidateOverrides as well, since it really just calls GetConfig + if err = scenario.Provider.ValidateOverrides(scenario.InputGroup, &scenario.InputAlert); err != nil { + t.Errorf("unexpected error: %s", err) + } + }) + } +} diff --git a/alerting/provider/messagebird/messagebird.go b/alerting/provider/messagebird/messagebird.go new file mode 100644 index 00000000..fe02d394 --- /dev/null +++ b/alerting/provider/messagebird/messagebird.go @@ -0,0 +1,142 @@ +package messagebird + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "gopkg.in/yaml.v3" +) + +const restAPIURL = "https://rest.messagebird.com/messages" + +var ( + ErrorAccessKeyNotSet = errors.New("access-key not set") + ErrorOriginatorNotSet = errors.New("originator not set") + ErrorRecipientsNotSet = errors.New("recipients not set") +) + +type Config struct { + AccessKey string `yaml:"access-key"` + Originator string `yaml:"originator"` + Recipients string `yaml:"recipients"` +} + +func (cfg *Config) Validate() error { + if len(cfg.AccessKey) == 0 { + return ErrorAccessKeyNotSet + } + if len(cfg.Originator) == 0 { + return ErrorOriginatorNotSet + } + if len(cfg.Recipients) == 0 { + return ErrorRecipientsNotSet + } + return nil +} + +func (cfg *Config) Merge(override *Config) { + if len(override.AccessKey) > 0 { + cfg.AccessKey = override.AccessKey + } + if len(override.Originator) > 0 { + cfg.Originator = override.Originator + } + if len(override.Recipients) > 0 { + cfg.Recipients = override.Recipients + } +} + +// AlertProvider is the configuration necessary for sending an alert using Messagebird +type AlertProvider struct { + DefaultConfig Config `yaml:",inline"` + + // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type + DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` +} + +// Validate the provider's configuration +func (provider *AlertProvider) Validate() error { + return provider.DefaultConfig.Validate() +} + +// Send an alert using the provider +// Reference doc for messagebird: https://developers.messagebird.com/api/sms-messaging/#send-outbound-sms +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + cfg, err := provider.GetConfig(ep.Group, alert) + if err != nil { + return err + } + buffer := bytes.NewBuffer(provider.buildRequestBody(cfg, ep, alert, result, resolved)) + request, err := http.NewRequest(http.MethodPost, restAPIURL, buffer) + if err != nil { + return err + } + request.Header.Set("Content-Type", "application/json") + request.Header.Set("Authorization", fmt.Sprintf("AccessKey %s", cfg.AccessKey)) + response, err := client.GetHTTPClient(nil).Do(request) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode > 399 { + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body)) + } + return err +} + +type Body struct { + Originator string `json:"originator"` + Recipients string `json:"recipients"` + Body string `json:"body"` +} + +// buildRequestBody builds the request body for the provider +func (provider *AlertProvider) buildRequestBody(cfg *Config, ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { + var message string + if resolved { + message = fmt.Sprintf("RESOLVED: %s - %s", ep.DisplayName(), alert.GetDescription()) + } else { + message = fmt.Sprintf("TRIGGERED: %s - %s", ep.DisplayName(), alert.GetDescription()) + } + body, _ := json.Marshal(Body{ + Originator: cfg.Originator, + Recipients: cfg.Recipients, + Body: message, + }) + return body +} + +// GetDefaultAlert returns the provider's default alert configuration +func (provider *AlertProvider) GetDefaultAlert() *alert.Alert { + return provider.DefaultAlert +} + +// GetConfig returns the configuration for the provider with the overrides applied +func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) { + cfg := provider.DefaultConfig + // Handle alert overrides + if len(alert.ProviderOverride) != 0 { + overrideConfig := Config{} + if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil { + return nil, err + } + cfg.Merge(&overrideConfig) + } + // Validate the configuration + err := cfg.Validate() + return &cfg, err +} + +// ValidateOverrides validates the alert's provider override and, if present, the group override +func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error { + _, err := provider.GetConfig(group, alert) + return err +} diff --git a/alerting/provider/messagebird/messagebird_test.go b/alerting/provider/messagebird/messagebird_test.go new file mode 100644 index 00000000..29b5191e --- /dev/null +++ b/alerting/provider/messagebird/messagebird_test.go @@ -0,0 +1,212 @@ +package messagebird + +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/test" +) + +func TestMessagebirdAlertProvider_IsValid(t *testing.T) { + invalidProvider := AlertProvider{} + if err := invalidProvider.Validate(); err == nil { + t.Error("provider shouldn't have been valid") + } + validProvider := AlertProvider{ + DefaultConfig: Config{ + AccessKey: "1", + Originator: "1", + Recipients: "1", + }, + } + if err := validProvider.Validate(); err != nil { + t.Error("provider should've been valid") + } +} + +func TestAlertProvider_Send(t *testing.T) { + defer client.InjectHTTPClient(nil) + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + MockRoundTripper test.MockRoundTripper + ExpectedError bool + }{ + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{AccessKey: "1", Originator: "2", Recipients: "3"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "triggered-error", + Provider: AlertProvider{DefaultConfig: Config{AccessKey: "1", Originator: "2", Recipients: "3"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + { + Name: "resolved", + Provider: AlertProvider{DefaultConfig: Config{AccessKey: "1", Originator: "2", Recipients: "3"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "resolved-error", + Provider: AlertProvider{DefaultConfig: Config{AccessKey: "1", Originator: "2", Recipients: "3"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) + err := scenario.Provider.Send( + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if scenario.ExpectedError && err == nil { + t.Error("expected error, got none") + } + if !scenario.ExpectedError && err != nil { + t.Error("expected no error, got", err.Error()) + } + }) + } +} + +func TestAlertProvider_buildRequestBody(t *testing.T) { + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + ExpectedBody string + }{ + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{AccessKey: "1", Originator: "2", Recipients: "3"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: "{\"originator\":\"2\",\"recipients\":\"3\",\"body\":\"TRIGGERED: endpoint-name - description-1\"}", + }, + { + Name: "resolved", + Provider: AlertProvider{DefaultConfig: Config{AccessKey: "4", Originator: "5", Recipients: "6"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: "{\"originator\":\"5\",\"recipients\":\"6\",\"body\":\"RESOLVED: endpoint-name - description-2\"}", + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + body := scenario.Provider.buildRequestBody( + &scenario.Provider.DefaultConfig, + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if string(body) != scenario.ExpectedBody { + t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body) + } + out := make(map[string]interface{}) + if err := json.Unmarshal(body, &out); err != nil { + t.Error("expected body to be valid JSON, got error:", err.Error()) + } + }) + } +} + +func TestAlertProvider_GetDefaultAlert(t *testing.T) { + if (&AlertProvider{DefaultAlert: &alert.Alert{}}).GetDefaultAlert() == nil { + t.Error("expected default alert to be not nil") + } + if (&AlertProvider{DefaultAlert: nil}).GetDefaultAlert() != nil { + t.Error("expected default alert to be nil") + } +} + +func TestAlertProvider_GetConfig(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + InputAlert alert.Alert + ExpectedOutput Config + }{ + { + Name: "provider-no-override-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{AccessKey: "1", Originator: "2", Recipients: "3"}, + }, + InputAlert: alert.Alert{}, + ExpectedOutput: Config{AccessKey: "1", Originator: "2", Recipients: "3"}, + }, + { + Name: "provider-with-alert-override", + Provider: AlertProvider{ + DefaultConfig: Config{AccessKey: "1", Originator: "2", Recipients: "3"}, + }, + InputAlert: alert.Alert{ProviderOverride: map[string]any{"access-key": "4", "originator": "5", "recipients": "6"}}, + ExpectedOutput: Config{AccessKey: "4", Originator: "5", Recipients: "6"}, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + got, err := scenario.Provider.GetConfig("", &scenario.InputAlert) + if err != nil { + t.Error("expected no error, got:", err.Error()) + } + if got.AccessKey != scenario.ExpectedOutput.AccessKey { + t.Errorf("expected access key to be %s, got %s", scenario.ExpectedOutput.AccessKey, got.AccessKey) + } + if got.Originator != scenario.ExpectedOutput.Originator { + t.Errorf("expected originator to be %s, got %s", scenario.ExpectedOutput.Originator, got.Originator) + } + if got.Recipients != scenario.ExpectedOutput.Recipients { + t.Errorf("expected recipients to be %s, got %s", scenario.ExpectedOutput.Recipients, got.Recipients) + } + // Test ValidateOverrides as well, since it really just calls GetConfig + if err = scenario.Provider.ValidateOverrides("", &scenario.InputAlert); err != nil { + t.Errorf("unexpected error: %s", err) + } + }) + } +} diff --git a/alerting/provider/ntfy/ntfy.go b/alerting/provider/ntfy/ntfy.go new file mode 100644 index 00000000..9f875129 --- /dev/null +++ b/alerting/provider/ntfy/ntfy.go @@ -0,0 +1,243 @@ +package ntfy + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strconv" + "strings" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "gopkg.in/yaml.v3" +) + +const ( + DefaultURL = "https://ntfy.sh" + DefaultPriority = 3 + TokenPrefix = "tk_" +) + +var ( + ErrInvalidToken = errors.New("invalid token") + ErrTopicNotSet = errors.New("topic not set") + ErrInvalidPriority = errors.New("priority must between 1 and 5 inclusively") + ErrDuplicateGroupOverride = errors.New("duplicate group override") +) + +type Config struct { + Topic string `yaml:"topic"` + URL string `yaml:"url,omitempty"` // Defaults to DefaultURL + Priority int `yaml:"priority,omitempty"` // Defaults to DefaultPriority + Token string `yaml:"token,omitempty"` // Defaults to "" + Email string `yaml:"email,omitempty"` // Defaults to "" + Click string `yaml:"click,omitempty"` // Defaults to "" + DisableFirebase bool `yaml:"disable-firebase,omitempty"` // Defaults to false + DisableCache bool `yaml:"disable-cache,omitempty"` // Defaults to false +} + +func (cfg *Config) Validate() error { + if len(cfg.URL) == 0 { + cfg.URL = DefaultURL + } + if cfg.Priority == 0 { + cfg.Priority = DefaultPriority + } + if len(cfg.Token) > 0 && !strings.HasPrefix(cfg.Token, TokenPrefix) { + return ErrInvalidToken + } + if len(cfg.Topic) == 0 { + return ErrTopicNotSet + } + if cfg.Priority < 1 || cfg.Priority > 5 { + return ErrInvalidPriority + } + return nil +} + +func (cfg *Config) Merge(override *Config) { + if len(override.Topic) > 0 { + cfg.Topic = override.Topic + } + if len(override.URL) > 0 { + cfg.URL = override.URL + } + if override.Priority > 0 { + cfg.Priority = override.Priority + } + if len(override.Token) > 0 { + cfg.Token = override.Token + } + if len(override.Email) > 0 { + cfg.Email = override.Email + } + if len(override.Click) > 0 { + cfg.Click = override.Click + } + if override.DisableFirebase { + cfg.DisableFirebase = true + } + if override.DisableCache { + cfg.DisableCache = true + } +} + +// AlertProvider is the configuration necessary for sending an alert using Slack +type AlertProvider struct { + DefaultConfig Config `yaml:",inline"` + + // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type + DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` + + // Overrides is a list of Override that may be prioritized over the default configuration + Overrides []Override `yaml:"overrides,omitempty"` +} + +// Override is a case under which the default integration is overridden +type Override struct { + Group string `yaml:"group"` + Config `yaml:",inline"` +} + +// Validate the provider's configuration +func (provider *AlertProvider) Validate() error { + registeredGroups := make(map[string]bool) + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if len(override.Group) == 0 { + return ErrDuplicateGroupOverride + } + if _, ok := registeredGroups[override.Group]; ok { + return ErrDuplicateGroupOverride + } + if len(override.Token) > 0 && !strings.HasPrefix(override.Token, TokenPrefix) { + return ErrDuplicateGroupOverride + } + if override.Priority < 0 || override.Priority >= 6 { + return ErrDuplicateGroupOverride + } + registeredGroups[override.Group] = true + } + } + + return provider.DefaultConfig.Validate() +} + +// Send an alert using the provider +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + cfg, err := provider.GetConfig(ep.Group, alert) + if err != nil { + return err + } + buffer := bytes.NewBuffer(provider.buildRequestBody(cfg, ep, alert, result, resolved)) + url := cfg.URL + request, err := http.NewRequest(http.MethodPost, url, buffer) + if err != nil { + return err + } + request.Header.Set("Content-Type", "application/json") + if token := cfg.Token; len(token) > 0 { + request.Header.Set("Authorization", "Bearer "+token) + } + if cfg.DisableFirebase { + request.Header.Set("Firebase", "no") + } + if cfg.DisableCache { + request.Header.Set("Cache", "no") + } + response, err := client.GetHTTPClient(nil).Do(request) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode > 399 { + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body)) + } + return err +} + +type Body struct { + Topic string `json:"topic"` + Title string `json:"title"` + Message string `json:"message"` + Tags []string `json:"tags"` + Priority int `json:"priority"` + Email string `json:"email,omitempty"` + Click string `json:"click,omitempty"` +} + +// buildRequestBody builds the request body for the provider +func (provider *AlertProvider) buildRequestBody(cfg *Config, ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { + var message, formattedConditionResults, tag string + if resolved { + tag = "white_check_mark" + message = "An alert has been resolved after passing successfully " + strconv.Itoa(alert.SuccessThreshold) + " time(s) in a row" + } else { + tag = "rotating_light" + message = "An alert has been triggered due to having failed " + strconv.Itoa(alert.FailureThreshold) + " time(s) in a row" + } + for _, conditionResult := range result.ConditionResults { + var prefix string + if conditionResult.Success { + prefix = "🟢" + } else { + prefix = "🔴" + } + formattedConditionResults += fmt.Sprintf("\n%s %s", prefix, conditionResult.Condition) + } + if len(alert.GetDescription()) > 0 { + message += " with the following description: " + alert.GetDescription() + } + message += formattedConditionResults + body, _ := json.Marshal(Body{ + Topic: cfg.Topic, + Title: "Gatus: " + ep.DisplayName(), + Message: message, + Tags: []string{tag}, + Priority: cfg.Priority, + Email: cfg.Email, + Click: cfg.Click, + }) + return body +} + +// GetDefaultAlert returns the provider's default alert configuration +func (provider *AlertProvider) GetDefaultAlert() *alert.Alert { + return provider.DefaultAlert +} + +// GetConfig returns the configuration for the provider with the overrides applied +func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) { + cfg := provider.DefaultConfig + // Handle group overrides + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if group == override.Group { + cfg.Merge(&override.Config) + break + } + } + } + // Handle alert overrides + if len(alert.ProviderOverride) != 0 { + overrideConfig := Config{} + if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil { + return nil, err + } + cfg.Merge(&overrideConfig) + } + // Validate the configuration + err := cfg.Validate() + return &cfg, err +} + +// ValidateOverrides validates the alert's provider override and, if present, the group override +func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error { + _, err := provider.GetConfig(group, alert) + return err +} diff --git a/alerting/provider/ntfy/ntfy_test.go b/alerting/provider/ntfy/ntfy_test.go new file mode 100644 index 00000000..16ef6cb3 --- /dev/null +++ b/alerting/provider/ntfy/ntfy_test.go @@ -0,0 +1,414 @@ +package ntfy + +import ( + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/config/endpoint" +) + +func TestAlertProvider_Validate(t *testing.T) { + scenarios := []struct { + name string + provider AlertProvider + expected bool + }{ + { + name: "valid", + provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1}}, + expected: true, + }, + { + name: "no-url-should-use-default-value", + provider: AlertProvider{DefaultConfig: Config{Topic: "example", Priority: 1}}, + expected: true, + }, + { + name: "valid-with-token", + provider: AlertProvider{DefaultConfig: Config{Topic: "example", Priority: 1, Token: "tk_faketoken"}}, + expected: true, + }, + { + name: "invalid-token", + provider: AlertProvider{DefaultConfig: Config{Topic: "example", Priority: 1, Token: "xx_faketoken"}}, + expected: false, + }, + { + name: "invalid-topic", + provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "", Priority: 1}}, + expected: false, + }, + { + name: "invalid-priority-too-high", + provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 6}}, + expected: false, + }, + { + name: "invalid-priority-too-low", + provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: -1}}, + expected: false, + }, + { + name: "no-priority-should-use-default-value", + provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example"}}, + expected: true, + }, + { + name: "invalid-override-token", + provider: AlertProvider{DefaultConfig: Config{Topic: "example"}, Overrides: []Override{{Group: "g", Config: Config{Token: "xx_faketoken"}}}}, + expected: false, + }, + { + name: "invalid-override-priority", + provider: AlertProvider{DefaultConfig: Config{Topic: "example"}, Overrides: []Override{{Group: "g", Config: Config{Priority: 8}}}}, + expected: false, + }, + { + name: "no-override-group-name", + provider: AlertProvider{DefaultConfig: Config{Topic: "example"}, Overrides: []Override{{}}}, + expected: false, + }, + { + name: "duplicate-override-group-names", + provider: AlertProvider{DefaultConfig: Config{Topic: "example"}, Overrides: []Override{{Group: "g"}, {Group: "g"}}}, + expected: false, + }, + { + name: "valid-override", + provider: AlertProvider{DefaultConfig: Config{Topic: "example"}, Overrides: []Override{{Group: "g1", Config: Config{Priority: 4, Click: "https://example.com"}}, {Group: "g2", Config: Config{Topic: "Example", Token: "tk_faketoken"}}}}, + expected: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + err := scenario.provider.Validate() + if scenario.expected && err != nil { + t.Error("expected no error, got", err.Error()) + } + if !scenario.expected && err == nil { + t.Error("expected error, got none") + } + }) + } +} + +func TestAlertProvider_buildRequestBody(t *testing.T) { + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + ExpectedBody string + }{ + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":1}`, + }, + { + Name: "resolved", + Provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 2}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been resolved after passing successfully 5 time(s) in a row with the following description: description-2\n🟢 [CONNECTED] == true\n🟢 [STATUS] == 200","tags":["white_check_mark"],"priority":2}`, + }, + { + Name: "triggered-email", + Provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1, Email: "test@example.com", Click: "example.com"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":1,"email":"test@example.com","click":"example.com"}`, + }, + { + Name: "resolved-email", + Provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 2, Email: "test@example.com", Click: "example.com"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been resolved after passing successfully 5 time(s) in a row with the following description: description-2\n🟢 [CONNECTED] == true\n🟢 [STATUS] == 200","tags":["white_check_mark"],"priority":2,"email":"test@example.com","click":"example.com"}`, + }, + { + Name: "group-override", + Provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 5, Email: "test@example.com", Click: "example.com"}, Overrides: []Override{{Group: "g", Config: Config{Topic: "group-topic", Priority: 4, Email: "override@test.com", Click: "test.com"}}}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: `{"topic":"group-topic","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":4,"email":"override@test.com","click":"test.com"}`, + }, + { + Name: "alert-override", + Provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 5, Email: "test@example.com", Click: "example.com"}, Overrides: []Override{{Group: "g", Config: Config{Topic: "group-topic", Priority: 4, Email: "override@test.com", Click: "test.com"}}}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3, ProviderOverride: map[string]any{"topic": "alert-topic"}}, + Resolved: false, + ExpectedBody: `{"topic":"alert-topic","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":4,"email":"override@test.com","click":"test.com"}`, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + cfg, err := scenario.Provider.GetConfig("g", &scenario.Alert) + if err != nil { + t.Error("expected no error, got", err.Error()) + } + body := scenario.Provider.buildRequestBody( + cfg, + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if string(body) != scenario.ExpectedBody { + t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body) + } + out := make(map[string]interface{}) + if err := json.Unmarshal(body, &out); err != nil { + t.Error("expected body to be valid JSON, got error:", err.Error()) + } + }) + } +} + +func TestAlertProvider_Send(t *testing.T) { + description := "description-1" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + Group string + ExpectedBody string + ExpectedHeaders map[string]string + }{ + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1, Email: "test@example.com", Click: "example.com"}}, + Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + Group: "", + ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":1,"email":"test@example.com","click":"example.com"}`, + ExpectedHeaders: map[string]string{ + "Content-Type": "application/json", + }, + }, + { + Name: "token", + Provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1, Email: "test@example.com", Click: "example.com", Token: "tk_mytoken"}}, + Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + Group: "", + ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":1,"email":"test@example.com","click":"example.com"}`, + ExpectedHeaders: map[string]string{ + "Content-Type": "application/json", + "Authorization": "Bearer tk_mytoken", + }, + }, + { + Name: "no firebase", + Provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1, Email: "test@example.com", Click: "example.com", DisableFirebase: true}}, + Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + Group: "", + ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":1,"email":"test@example.com","click":"example.com"}`, + ExpectedHeaders: map[string]string{ + "Content-Type": "application/json", + "Firebase": "no", + }, + }, + { + Name: "no cache", + Provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1, Email: "test@example.com", Click: "example.com", DisableCache: true}}, + Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + Group: "", + ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":1,"email":"test@example.com","click":"example.com"}`, + ExpectedHeaders: map[string]string{ + "Content-Type": "application/json", + "Cache": "no", + }, + }, + { + Name: "neither firebase & cache", + Provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1, Email: "test@example.com", Click: "example.com", DisableFirebase: true, DisableCache: true}}, + Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + Group: "", + ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":1,"email":"test@example.com","click":"example.com"}`, + ExpectedHeaders: map[string]string{ + "Content-Type": "application/json", + "Firebase": "no", + "Cache": "no", + }, + }, + { + Name: "overrides", + Provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1, Email: "test@example.com", Click: "example.com", Token: "tk_mytoken"}, Overrides: []Override{Override{Group: "other-group", Config: Config{URL: "https://example.com", Token: "tk_othertoken"}}, Override{Group: "test-group", Config: Config{Token: "tk_test_token"}}}}, + Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + Group: "test-group", + ExpectedBody: `{"topic":"example","title":"Gatus: test-group/endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":1,"email":"test@example.com","click":"example.com"}`, + ExpectedHeaders: map[string]string{ + "Content-Type": "application/json", + "Authorization": "Bearer tk_test_token", + }, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + // Start a local HTTP server + server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + // Test request parameters + for header, value := range scenario.ExpectedHeaders { + if value != req.Header.Get(header) { + t.Errorf("expected: %s, got: %s", value, req.Header.Get(header)) + } + } + body, _ := io.ReadAll(req.Body) + if string(body) != scenario.ExpectedBody { + t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body) + } + // Send response to be tested + rw.Write([]byte(`OK`)) + })) + // Close the server when test finishes + defer server.Close() + + scenario.Provider.DefaultConfig.URL = server.URL + err := scenario.Provider.Send( + &endpoint.Endpoint{Name: "endpoint-name", Group: scenario.Group}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if err != nil { + t.Error("Encountered an error on Send: ", err) + } + }) + } +} + +func TestAlertProvider_GetConfig(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + InputGroup string + InputAlert alert.Alert + ExpectedOutput Config + }{ + { + Name: "provider-no-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1}, + Overrides: nil, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1}, + }, + { + Name: "provider-no-override-specify-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1}, + Overrides: nil, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1}, + }, + { + Name: "provider-with-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1}, + Overrides: []Override{ + { + Group: "group", + Config: Config{URL: "https://group-example.com", Topic: "group-topic", Priority: 2}, + }, + }, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1}, + }, + { + Name: "provider-with-override-specify-group-should-override", + Provider: AlertProvider{ + DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1}, + Overrides: []Override{ + { + Group: "group", + Config: Config{URL: "https://group-example.com", Topic: "group-topic", Priority: 2}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{URL: "https://group-example.com", Topic: "group-topic", Priority: 2}, + }, + { + Name: "provider-with-group-override-and-alert-override--alert-override-should-take-precedence", + Provider: AlertProvider{ + DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1}, + Overrides: []Override{ + { + Group: "group", + Config: Config{URL: "https://group-example.com", Topic: "group-topic", Priority: 2}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{ProviderOverride: map[string]any{"url": "http://alert-example.com", "topic": "alert-topic", "priority": 3}}, + ExpectedOutput: Config{URL: "http://alert-example.com", Topic: "alert-topic", Priority: 3}, + }, + { + Name: "provider-with-partial-overrides", + Provider: AlertProvider{ + DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1}, + Overrides: []Override{ + { + Group: "group", + Config: Config{Topic: "group-topic"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{ProviderOverride: map[string]any{"priority": 3}}, + ExpectedOutput: Config{URL: "https://ntfy.sh", Topic: "group-topic", Priority: 3}, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + got, err := scenario.Provider.GetConfig(scenario.InputGroup, &scenario.InputAlert) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if got.URL != scenario.ExpectedOutput.URL { + t.Errorf("expected url %s, got %s", scenario.ExpectedOutput.URL, got.URL) + } + if got.Topic != scenario.ExpectedOutput.Topic { + t.Errorf("expected topic %s, got %s", scenario.ExpectedOutput.Topic, got.Topic) + } + if got.Priority != scenario.ExpectedOutput.Priority { + t.Errorf("expected priority %d, got %d", scenario.ExpectedOutput.Priority, got.Priority) + } + // Test ValidateOverrides as well, since it really just calls GetConfig + if err = scenario.Provider.ValidateOverrides(scenario.InputGroup, &scenario.InputAlert); err != nil { + t.Errorf("unexpected error: %s", err) + } + }) + } +} diff --git a/alerting/provider/opsgenie/opsgenie.go b/alerting/provider/opsgenie/opsgenie.go new file mode 100644 index 00000000..bd968427 --- /dev/null +++ b/alerting/provider/opsgenie/opsgenie.go @@ -0,0 +1,284 @@ +package opsgenie + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strconv" + "strings" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "gopkg.in/yaml.v3" +) + +const ( + restAPI = "https://api.opsgenie.com/v2/alerts" +) + +var ( + ErrAPIKeyNotSet = errors.New("api-key not set") +) + +type Config struct { + // APIKey to use for + APIKey string `yaml:"api-key"` + + // Priority to be used in Opsgenie alert payload + // + // default: P1 + Priority string `yaml:"priority"` + + // Source define source to be used in Opsgenie alert payload + // + // default: gatus + Source string `yaml:"source"` + + // EntityPrefix is a prefix to be used in entity argument in Opsgenie alert payload + // + // default: gatus- + EntityPrefix string `yaml:"entity-prefix"` + + //AliasPrefix is a prefix to be used in alias argument in Opsgenie alert payload + // + // default: gatus-healthcheck- + AliasPrefix string `yaml:"alias-prefix"` + + // Tags to be used in Opsgenie alert payload + // + // default: [] + Tags []string `yaml:"tags"` +} + +func (cfg *Config) Validate() error { + if len(cfg.APIKey) == 0 { + return ErrAPIKeyNotSet + } + if len(cfg.Source) == 0 { + cfg.Source = "gatus" + } + if len(cfg.EntityPrefix) == 0 { + cfg.EntityPrefix = "gatus-" + } + if len(cfg.AliasPrefix) == 0 { + cfg.AliasPrefix = "gatus-healthcheck-" + } + if len(cfg.Priority) == 0 { + cfg.Priority = "P1" + } + return nil +} + +func (cfg *Config) Merge(override *Config) { + if len(override.APIKey) > 0 { + cfg.APIKey = override.APIKey + } + if len(override.Priority) > 0 { + cfg.Priority = override.Priority + } + if len(override.Source) > 0 { + cfg.Source = override.Source + } + if len(override.EntityPrefix) > 0 { + cfg.EntityPrefix = override.EntityPrefix + } + if len(override.AliasPrefix) > 0 { + cfg.AliasPrefix = override.AliasPrefix + } + if len(override.Tags) > 0 { + cfg.Tags = override.Tags + } +} + +type AlertProvider struct { + DefaultConfig Config `yaml:",inline"` + + // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type + DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` +} + +// Validate the provider's configuration +func (provider *AlertProvider) Validate() error { + return provider.DefaultConfig.Validate() +} + +// Send an alert using the provider +// +// Relevant: https://docs.opsgenie.com/docs/alert-api +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + cfg, err := provider.GetConfig(ep.Group, alert) + if err != nil { + return err + } + err = provider.sendAlertRequest(cfg, ep, alert, result, resolved) + if err != nil { + return err + } + if resolved { + err = provider.closeAlert(cfg, ep, alert) + if err != nil { + return err + } + } + if alert.IsSendingOnResolved() { + if resolved { + // The alert has been resolved and there's no error, so we can clear the alert's ResolveKey + alert.ResolveKey = "" + } else { + alert.ResolveKey = cfg.AliasPrefix + buildKey(ep) + } + } + return nil +} + +func (provider *AlertProvider) sendAlertRequest(cfg *Config, ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + payload := provider.buildCreateRequestBody(cfg, ep, alert, result, resolved) + return provider.sendRequest(cfg, restAPI, http.MethodPost, payload) +} + +func (provider *AlertProvider) closeAlert(cfg *Config, ep *endpoint.Endpoint, alert *alert.Alert) error { + payload := provider.buildCloseRequestBody(ep, alert) + url := restAPI + "/" + cfg.AliasPrefix + buildKey(ep) + "/close?identifierType=alias" + return provider.sendRequest(cfg, url, http.MethodPost, payload) +} + +func (provider *AlertProvider) sendRequest(cfg *Config, url, method string, payload interface{}) error { + body, err := json.Marshal(payload) + if err != nil { + return fmt.Errorf("error build alert with payload %v: %w", payload, err) + } + request, err := http.NewRequest(method, url, bytes.NewBuffer(body)) + if err != nil { + return err + } + request.Header.Set("Content-Type", "application/json") + request.Header.Set("Authorization", "GenieKey "+cfg.APIKey) + response, err := client.GetHTTPClient(nil).Do(request) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode > 399 { + rBody, _ := io.ReadAll(response.Body) + return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(rBody)) + } + return nil +} + +func (provider *AlertProvider) buildCreateRequestBody(cfg *Config, ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) alertCreateRequest { + var message, description string + if resolved { + message = fmt.Sprintf("RESOLVED: %s - %s", ep.Name, alert.GetDescription()) + description = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) + } else { + message = fmt.Sprintf("%s - %s", ep.Name, alert.GetDescription()) + description = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) + } + if ep.Group != "" { + message = fmt.Sprintf("[%s] %s", ep.Group, message) + } + var formattedConditionResults string + for _, conditionResult := range result.ConditionResults { + var prefix string + if conditionResult.Success { + prefix = "▣" + } else { + prefix = "▢" + } + formattedConditionResults += fmt.Sprintf("%s - `%s`\n", prefix, conditionResult.Condition) + } + description = description + "\n" + formattedConditionResults + key := buildKey(ep) + details := map[string]string{ + "endpoint:url": ep.URL, + "endpoint:group": ep.Group, + "result:hostname": result.Hostname, + "result:ip": result.IP, + "result:dns_code": result.DNSRCode, + "result:errors": strings.Join(result.Errors, ","), + } + for k, v := range details { + if v == "" { + delete(details, k) + } + } + if result.HTTPStatus > 0 { + details["result:http_status"] = strconv.Itoa(result.HTTPStatus) + } + return alertCreateRequest{ + Message: message, + Description: description, + Source: cfg.Source, + Priority: cfg.Priority, + Alias: cfg.AliasPrefix + key, + Entity: cfg.EntityPrefix + key, + Tags: cfg.Tags, + Details: details, + } +} + +func (provider *AlertProvider) buildCloseRequestBody(ep *endpoint.Endpoint, alert *alert.Alert) alertCloseRequest { + return alertCloseRequest{ + Source: buildKey(ep), + Note: fmt.Sprintf("RESOLVED: %s - %s", ep.Name, alert.GetDescription()), + } +} + +// GetDefaultAlert returns the provider's default alert configuration +func (provider *AlertProvider) GetDefaultAlert() *alert.Alert { + return provider.DefaultAlert +} + +// GetConfig returns the configuration for the provider with the overrides applied +func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) { + cfg := provider.DefaultConfig + // Handle alert overrides + if len(alert.ProviderOverride) != 0 { + overrideConfig := Config{} + if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil { + return nil, err + } + cfg.Merge(&overrideConfig) + } + // Validate the configuration + err := cfg.Validate() + return &cfg, err +} + +// ValidateOverrides validates the alert's provider override and, if present, the group override +func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error { + _, err := provider.GetConfig(group, alert) + return err +} + +func buildKey(ep *endpoint.Endpoint) string { + name := toKebabCase(ep.Name) + if ep.Group == "" { + return name + } + return toKebabCase(ep.Group) + "-" + name +} + +func toKebabCase(val string) string { + return strings.ToLower(strings.ReplaceAll(val, " ", "-")) +} + +type alertCreateRequest struct { + Message string `json:"message"` + Priority string `json:"priority"` + Source string `json:"source"` + Entity string `json:"entity"` + Alias string `json:"alias"` + Description string `json:"description"` + Tags []string `json:"tags,omitempty"` + Details map[string]string `json:"details"` +} + +type alertCloseRequest struct { + Source string `json:"source"` + Note string `json:"note"` +} diff --git a/alerting/provider/opsgenie/opsgenie_test.go b/alerting/provider/opsgenie/opsgenie_test.go new file mode 100644 index 00000000..11064c5c --- /dev/null +++ b/alerting/provider/opsgenie/opsgenie_test.go @@ -0,0 +1,361 @@ +package opsgenie + +import ( + "net/http" + "reflect" + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/test" +) + +func TestAlertProvider_Validate(t *testing.T) { + invalidProvider := AlertProvider{DefaultConfig: Config{APIKey: ""}} + if err := invalidProvider.Validate(); err == nil { + t.Error("provider shouldn't have been valid") + } + validProvider := AlertProvider{DefaultConfig: Config{APIKey: "00000000-0000-0000-0000-000000000000"}} + if err := validProvider.Validate(); err != nil { + t.Error("provider should've been valid") + } +} + +func TestAlertProvider_Send(t *testing.T) { + defer client.InjectHTTPClient(nil) + description := "my bad alert description" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + MockRoundTripper test.MockRoundTripper + ExpectedError bool + }{ + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{APIKey: "00000000-0000-0000-0000-000000000000"}}, + Alert: alert.Alert{Description: &description, SuccessThreshold: 1, FailureThreshold: 1}, + Resolved: false, + ExpectedError: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + }, + { + Name: "triggered-error", + Provider: AlertProvider{DefaultConfig: Config{APIKey: "00000000-0000-0000-0000-000000000000"}}, + Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedError: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + }, + { + Name: "resolved", + Provider: AlertProvider{DefaultConfig: Config{APIKey: "00000000-0000-0000-0000-000000000000"}}, + Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedError: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + }, + { + Name: "resolved-error", + Provider: AlertProvider{DefaultConfig: Config{APIKey: "00000000-0000-0000-0000-000000000000"}}, + Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedError: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) + err := scenario.Provider.Send( + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if scenario.ExpectedError && err == nil { + t.Error("expected error, got none") + } + if !scenario.ExpectedError && err != nil { + t.Error("expected no error, got", err.Error()) + } + }) + } +} + +func TestAlertProvider_buildCreateRequestBody(t *testing.T) { + t.Parallel() + description := "alert description" + scenarios := []struct { + Name string + Provider *AlertProvider + Alert *alert.Alert + Endpoint *endpoint.Endpoint + Result *endpoint.Result + Resolved bool + want alertCreateRequest + }{ + { + Name: "missing all params (unresolved)", + Provider: &AlertProvider{DefaultConfig: Config{APIKey: "00000000-0000-0000-0000-000000000000"}}, + Alert: &alert.Alert{}, + Endpoint: &endpoint.Endpoint{}, + Result: &endpoint.Result{}, + Resolved: false, + want: alertCreateRequest{ + Message: " - ", + Priority: "P1", + Source: "gatus", + Entity: "gatus-", + Alias: "gatus-healthcheck-", + Description: "An alert for ** has been triggered due to having failed 0 time(s) in a row\n", + Tags: nil, + Details: map[string]string{}, + }, + }, + { + Name: "missing all params (resolved)", + Provider: &AlertProvider{DefaultConfig: Config{APIKey: "00000000-0000-0000-0000-000000000000"}}, + Alert: &alert.Alert{}, + Endpoint: &endpoint.Endpoint{}, + Result: &endpoint.Result{}, + Resolved: true, + want: alertCreateRequest{ + Message: "RESOLVED: - ", + Priority: "P1", + Source: "gatus", + Entity: "gatus-", + Alias: "gatus-healthcheck-", + Description: "An alert for ** has been resolved after passing successfully 0 time(s) in a row\n", + Tags: nil, + Details: map[string]string{}, + }, + }, + { + Name: "with default options (unresolved)", + Provider: &AlertProvider{DefaultConfig: Config{APIKey: "00000000-0000-0000-0000-000000000000"}}, + Alert: &alert.Alert{ + Description: &description, + FailureThreshold: 3, + }, + Endpoint: &endpoint.Endpoint{ + Name: "my super app", + }, + Result: &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + { + Condition: "[STATUS] == 200", + Success: true, + }, + { + Condition: "[BODY] == OK", + Success: false, + }, + }, + }, + Resolved: false, + want: alertCreateRequest{ + Message: "my super app - " + description, + Priority: "P1", + Source: "gatus", + Entity: "gatus-my-super-app", + Alias: "gatus-healthcheck-my-super-app", + Description: "An alert for *my super app* has been triggered due to having failed 3 time(s) in a row\n▣ - `[STATUS] == 200`\n▢ - `[BODY] == OK`\n", + Tags: nil, + Details: map[string]string{}, + }, + }, + { + Name: "with custom options (resolved)", + Provider: &AlertProvider{ + DefaultConfig: Config{ + Priority: "P5", + EntityPrefix: "oompa-", + AliasPrefix: "loompa-", + Source: "gatus-hc", + Tags: []string{"do-ba-dee-doo"}, + }, + }, + Alert: &alert.Alert{ + Description: &description, + SuccessThreshold: 4, + }, + Endpoint: &endpoint.Endpoint{ + Name: "my mega app", + }, + Result: &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + { + Condition: "[STATUS] == 200", + Success: true, + }, + }, + }, + Resolved: true, + want: alertCreateRequest{ + Message: "RESOLVED: my mega app - " + description, + Priority: "P5", + Source: "gatus-hc", + Entity: "oompa-my-mega-app", + Alias: "loompa-my-mega-app", + Description: "An alert for *my mega app* has been resolved after passing successfully 4 time(s) in a row\n▣ - `[STATUS] == 200`\n", + Tags: []string{"do-ba-dee-doo"}, + Details: map[string]string{}, + }, + }, + { + Name: "with default options and details (unresolved)", + Provider: &AlertProvider{ + DefaultConfig: Config{Tags: []string{"foo"}, APIKey: "00000000-0000-0000-0000-000000000000"}, + }, + Alert: &alert.Alert{ + Description: &description, + FailureThreshold: 6, + }, + Endpoint: &endpoint.Endpoint{ + Name: "my app", + Group: "end game", + URL: "https://my.go/app", + }, + Result: &endpoint.Result{ + HTTPStatus: 400, + Hostname: "my.go", + Errors: []string{"error 01", "error 02"}, + Success: false, + ConditionResults: []*endpoint.ConditionResult{ + { + Condition: "[STATUS] == 200", + Success: false, + }, + }, + }, + Resolved: false, + want: alertCreateRequest{ + Message: "[end game] my app - " + description, + Priority: "P1", + Source: "gatus", + Entity: "gatus-end-game-my-app", + Alias: "gatus-healthcheck-end-game-my-app", + Description: "An alert for *end game/my app* has been triggered due to having failed 6 time(s) in a row\n▢ - `[STATUS] == 200`\n", + Tags: []string{"foo"}, + Details: map[string]string{ + "endpoint:url": "https://my.go/app", + "endpoint:group": "end game", + "result:hostname": "my.go", + "result:errors": "error 01,error 02", + "result:http_status": "400", + }, + }, + }, + } + for _, scenario := range scenarios { + actual := scenario + t.Run(actual.Name, func(t *testing.T) { + _ = scenario.Provider.Validate() + if got := actual.Provider.buildCreateRequestBody(&scenario.Provider.DefaultConfig, actual.Endpoint, actual.Alert, actual.Result, actual.Resolved); !reflect.DeepEqual(got, actual.want) { + t.Errorf("got:\n%v\nwant:\n%v", got, actual.want) + } + }) + } +} + +func TestAlertProvider_buildCloseRequestBody(t *testing.T) { + t.Parallel() + description := "alert description" + scenarios := []struct { + Name string + Provider *AlertProvider + Alert *alert.Alert + Endpoint *endpoint.Endpoint + want alertCloseRequest + }{ + { + Name: "Missing all values", + Provider: &AlertProvider{}, + Alert: &alert.Alert{}, + Endpoint: &endpoint.Endpoint{}, + want: alertCloseRequest{ + Source: "", + Note: "RESOLVED: - ", + }, + }, + { + Name: "Basic values", + Provider: &AlertProvider{}, + Alert: &alert.Alert{ + Description: &description, + }, + Endpoint: &endpoint.Endpoint{ + Name: "endpoint name", + }, + want: alertCloseRequest{ + Source: "endpoint-name", + Note: "RESOLVED: endpoint name - alert description", + }, + }, + } + for _, scenario := range scenarios { + actual := scenario + t.Run(actual.Name, func(t *testing.T) { + if got := actual.Provider.buildCloseRequestBody(actual.Endpoint, actual.Alert); !reflect.DeepEqual(got, actual.want) { + t.Errorf("buildCloseRequestBody() = %v, want %v", got, actual.want) + } + }) + } +} + +func TestAlertProvider_GetConfig(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + InputAlert alert.Alert + ExpectedOutput Config + }{ + { + Name: "provider-no-override-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{APIKey: "00000000-0000-0000-0000-000000000000"}, + }, + InputAlert: alert.Alert{}, + ExpectedOutput: Config{APIKey: "00000000-0000-0000-0000-000000000000"}, + }, + { + Name: "provider-with-alert-override--alert-override-should-take-precedence", + Provider: AlertProvider{ + DefaultConfig: Config{APIKey: "00000000-0000-0000-0000-000000000000"}, + }, + InputAlert: alert.Alert{ProviderOverride: map[string]any{"api-key": "00000000-0000-0000-0000-000000000001"}}, + ExpectedOutput: Config{APIKey: "00000000-0000-0000-0000-000000000001"}, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + got, err := scenario.Provider.GetConfig("", &scenario.InputAlert) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if got.APIKey != scenario.ExpectedOutput.APIKey { + t.Errorf("expected APIKey to be %s, got %s", scenario.ExpectedOutput.APIKey, got.APIKey) + } + // Test ValidateOverrides as well, since it really just calls GetConfig + if err = scenario.Provider.ValidateOverrides("", &scenario.InputAlert); err != nil { + t.Errorf("unexpected error: %s", err) + } + }) + } +} diff --git a/alerting/provider/pagerduty/pagerduty.go b/alerting/provider/pagerduty/pagerduty.go new file mode 100644 index 00000000..3a563f93 --- /dev/null +++ b/alerting/provider/pagerduty/pagerduty.go @@ -0,0 +1,195 @@ +package pagerduty + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/logr" + "gopkg.in/yaml.v3" +) + +const ( + restAPIURL = "https://events.pagerduty.com/v2/enqueue" +) + +var ( + ErrIntegrationKeyNotSet = errors.New("integration-key must have exactly 32 characters") + ErrDuplicateGroupOverride = errors.New("duplicate group override") +) + +type Config struct { + IntegrationKey string `yaml:"integration-key"` +} + +func (cfg *Config) Validate() error { + if len(cfg.IntegrationKey) != 32 { + return ErrIntegrationKeyNotSet + } + return nil +} + +func (cfg *Config) Merge(override *Config) { + if len(override.IntegrationKey) > 0 { + cfg.IntegrationKey = override.IntegrationKey + } +} + +// AlertProvider is the configuration necessary for sending an alert using PagerDuty +type AlertProvider struct { + DefaultConfig Config `yaml:",inline"` + + // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type + DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` + + // Overrides is a list of Override that may be prioritized over the default configuration + Overrides []Override `yaml:"overrides,omitempty"` +} + +// Override is a case under which the default integration is overridden +type Override struct { + Group string `yaml:"group"` + Config `yaml:",inline"` +} + +// Validate the provider's configuration +func (provider *AlertProvider) Validate() error { + registeredGroups := make(map[string]bool) + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if isAlreadyRegistered := registeredGroups[override.Group]; isAlreadyRegistered || override.Group == "" { + return ErrDuplicateGroupOverride + } + registeredGroups[override.Group] = true + } + } + // Either the default integration key has the right length, or there are overrides who are properly configured. + return provider.DefaultConfig.Validate() +} + +// Send an alert using the provider +// +// Relevant: https://developer.pagerduty.com/docs/events-api-v2/trigger-events/ +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + cfg, err := provider.GetConfig(ep.Group, alert) + if err != nil { + return err + } + buffer := bytes.NewBuffer(provider.buildRequestBody(cfg, ep, alert, result, resolved)) + request, err := http.NewRequest(http.MethodPost, restAPIURL, buffer) + if err != nil { + return err + } + request.Header.Set("Content-Type", "application/json") + response, err := client.GetHTTPClient(nil).Do(request) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode > 399 { + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body)) + } + if alert.IsSendingOnResolved() { + if resolved { + // The alert has been resolved and there's no error, so we can clear the alert's ResolveKey + alert.ResolveKey = "" + } else { + // We need to retrieve the resolve key from the response + var payload pagerDutyResponsePayload + if err = json.NewDecoder(response.Body).Decode(&payload); err != nil { + // Silently fail. We don't want to create tons of alerts just because we failed to parse the body. + logr.Errorf("[pagerduty.Send] Ran into error decoding pagerduty response: %s", err.Error()) + } else { + alert.ResolveKey = payload.DedupKey + } + } + } + return nil +} + +type Body struct { + RoutingKey string `json:"routing_key"` + DedupKey string `json:"dedup_key"` + EventAction string `json:"event_action"` + Payload Payload `json:"payload"` +} + +type Payload struct { + Summary string `json:"summary"` + Source string `json:"source"` + Severity string `json:"severity"` +} + +// buildRequestBody builds the request body for the provider +func (provider *AlertProvider) buildRequestBody(cfg *Config, ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { + var message, eventAction, resolveKey string + if resolved { + message = fmt.Sprintf("RESOLVED: %s - %s", ep.DisplayName(), alert.GetDescription()) + eventAction = "resolve" + resolveKey = alert.ResolveKey + } else { + message = fmt.Sprintf("TRIGGERED: %s - %s", ep.DisplayName(), alert.GetDescription()) + eventAction = "trigger" + resolveKey = "" + } + body, _ := json.Marshal(Body{ + RoutingKey: cfg.IntegrationKey, + DedupKey: resolveKey, + EventAction: eventAction, + Payload: Payload{ + Summary: message, + Source: "Gatus", + Severity: "critical", + }, + }) + return body +} + +// GetDefaultAlert returns the provider's default alert configuration +func (provider *AlertProvider) GetDefaultAlert() *alert.Alert { + return provider.DefaultAlert +} + +// GetConfig returns the configuration for the provider with the overrides applied +func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) { + cfg := provider.DefaultConfig + // Handle group overrides + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if group == override.Group { + cfg.Merge(&override.Config) + break + } + } + } + // Handle alert overrides + if len(alert.ProviderOverride) != 0 { + overrideConfig := Config{} + if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil { + return nil, err + } + cfg.Merge(&overrideConfig) + } + // Validate the configuration + err := cfg.Validate() + return &cfg, err +} + +// ValidateOverrides validates the alert's provider override and, if present, the group override +func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error { + _, err := provider.GetConfig(group, alert) + return err +} + +type pagerDutyResponsePayload struct { + Status string `json:"status"` + Message string `json:"message"` + DedupKey string `json:"dedup_key"` +} diff --git a/alerting/provider/pagerduty/pagerduty_test.go b/alerting/provider/pagerduty/pagerduty_test.go new file mode 100644 index 00000000..2e66cec3 --- /dev/null +++ b/alerting/provider/pagerduty/pagerduty_test.go @@ -0,0 +1,265 @@ +package pagerduty + +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/test" +) + +func TestAlertProvider_Validate(t *testing.T) { + invalidProvider := AlertProvider{DefaultConfig: Config{IntegrationKey: ""}} + if err := invalidProvider.Validate(); err == nil { + t.Error("provider shouldn't have been valid") + } + validProvider := AlertProvider{DefaultConfig: Config{IntegrationKey: "00000000000000000000000000000000"}} + if err := validProvider.Validate(); err != nil { + t.Error("provider should've been valid") + } +} + +func TestAlertProvider_ValidateWithOverride(t *testing.T) { + providerWithInvalidOverrideGroup := AlertProvider{ + DefaultConfig: Config{IntegrationKey: "00000000000000000000000000000001"}, + Overrides: []Override{ + { + Config: Config{IntegrationKey: "00000000000000000000000000000002"}, + Group: "", + }, + }, + } + if err := providerWithInvalidOverrideGroup.Validate(); err == nil { + t.Error("provider Group shouldn't have been valid") + } + providerWithValidOverride := AlertProvider{ + DefaultConfig: Config{IntegrationKey: "00000000000000000000000000000001"}, + Overrides: []Override{ + { + Config: Config{IntegrationKey: "00000000000000000000000000000002"}, + Group: "group", + }, + }, + } + if err := providerWithValidOverride.Validate(); err != nil { + t.Error("provider should've been valid, got error:", err.Error()) + } +} + +func TestAlertProvider_Send(t *testing.T) { + defer client.InjectHTTPClient(nil) + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + MockRoundTripper test.MockRoundTripper + ExpectedError bool + }{ + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{IntegrationKey: "00000000000000000000000000000000"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "triggered-error", + Provider: AlertProvider{DefaultConfig: Config{IntegrationKey: "00000000000000000000000000000000"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + { + Name: "resolved", + Provider: AlertProvider{DefaultConfig: Config{IntegrationKey: "00000000000000000000000000000000"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "resolved-error", + Provider: AlertProvider{DefaultConfig: Config{IntegrationKey: "00000000000000000000000000000000"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) + err := scenario.Provider.Send( + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if scenario.ExpectedError && err == nil { + t.Error("expected error, got none") + } + if !scenario.ExpectedError && err != nil { + t.Error("expected no error, got", err.Error()) + } + }) + } +} + +func TestAlertProvider_buildRequestBody(t *testing.T) { + description := "test" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + ExpectedBody string + }{ + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{IntegrationKey: "00000000000000000000000000000000"}}, + Alert: alert.Alert{Description: &description}, + Resolved: false, + ExpectedBody: "{\"routing_key\":\"00000000000000000000000000000000\",\"dedup_key\":\"\",\"event_action\":\"trigger\",\"payload\":{\"summary\":\"TRIGGERED: endpoint-name - test\",\"source\":\"Gatus\",\"severity\":\"critical\"}}", + }, + { + Name: "resolved", + Provider: AlertProvider{DefaultConfig: Config{IntegrationKey: "00000000000000000000000000000000"}}, + Alert: alert.Alert{Description: &description, ResolveKey: "key"}, + Resolved: true, + ExpectedBody: "{\"routing_key\":\"00000000000000000000000000000000\",\"dedup_key\":\"key\",\"event_action\":\"resolve\",\"payload\":{\"summary\":\"RESOLVED: endpoint-name - test\",\"source\":\"Gatus\",\"severity\":\"critical\"}}", + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + body := scenario.Provider.buildRequestBody(&scenario.Provider.DefaultConfig, &endpoint.Endpoint{Name: "endpoint-name"}, &scenario.Alert, &endpoint.Result{}, scenario.Resolved) + if string(body) != scenario.ExpectedBody { + t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body) + } + out := make(map[string]interface{}) + if err := json.Unmarshal(body, &out); err != nil { + t.Error("expected body to be valid JSON, got error:", err.Error()) + } + }) + } +} + +func TestAlertProvider_GetDefaultAlert(t *testing.T) { + if (&AlertProvider{DefaultAlert: &alert.Alert{}}).GetDefaultAlert() == nil { + t.Error("expected default alert to be not nil") + } + if (&AlertProvider{DefaultAlert: nil}).GetDefaultAlert() != nil { + t.Error("expected default alert to be nil") + } +} + +func TestAlertProvider_GetConfig(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + InputGroup string + InputAlert alert.Alert + ExpectedOutput Config + }{ + { + Name: "provider-no-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{IntegrationKey: "00000000000000000000000000000001"}, + Overrides: nil, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{IntegrationKey: "00000000000000000000000000000001"}, + }, + { + Name: "provider-no-override-specify-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{IntegrationKey: "00000000000000000000000000000001"}, + Overrides: nil, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{IntegrationKey: "00000000000000000000000000000001"}, + }, + { + Name: "provider-with-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{IntegrationKey: "00000000000000000000000000000001"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{IntegrationKey: "00000000000000000000000000000002"}, + }, + }, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{IntegrationKey: "00000000000000000000000000000001"}, + }, + { + Name: "provider-with-override-specify-group-should-override", + Provider: AlertProvider{ + DefaultConfig: Config{IntegrationKey: "00000000000000000000000000000001"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{IntegrationKey: "00000000000000000000000000000002"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{IntegrationKey: "00000000000000000000000000000002"}, + }, + { + Name: "provider-with-group-override-and-alert-override--alert-override-should-take-precedence", + Provider: AlertProvider{ + DefaultConfig: Config{IntegrationKey: "00000000000000000000000000000001"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{IntegrationKey: "00000000000000000000000000000002"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{ProviderOverride: map[string]any{"integration-key": "00000000000000000000000000000003"}}, + ExpectedOutput: Config{IntegrationKey: "00000000000000000000000000000003"}, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + got, err := scenario.Provider.GetConfig(scenario.InputGroup, &scenario.InputAlert) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if got.IntegrationKey != scenario.ExpectedOutput.IntegrationKey { + t.Errorf("expected %s, got %s", scenario.ExpectedOutput.IntegrationKey, got.IntegrationKey) + } + // Test ValidateOverrides as well, since it really just calls GetConfig + if err = scenario.Provider.ValidateOverrides(scenario.InputGroup, &scenario.InputAlert); err != nil { + t.Errorf("unexpected error: %s", err) + } + }) + } +} diff --git a/alerting/provider/provider.go b/alerting/provider/provider.go new file mode 100644 index 00000000..94e600f0 --- /dev/null +++ b/alerting/provider/provider.go @@ -0,0 +1,123 @@ +package provider + +import ( + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/alerting/provider/awsses" + "github.com/TwiN/gatus/v5/alerting/provider/custom" + "github.com/TwiN/gatus/v5/alerting/provider/discord" + "github.com/TwiN/gatus/v5/alerting/provider/email" + "github.com/TwiN/gatus/v5/alerting/provider/gitea" + "github.com/TwiN/gatus/v5/alerting/provider/github" + "github.com/TwiN/gatus/v5/alerting/provider/gitlab" + "github.com/TwiN/gatus/v5/alerting/provider/googlechat" + "github.com/TwiN/gatus/v5/alerting/provider/incidentio" + "github.com/TwiN/gatus/v5/alerting/provider/jetbrainsspace" + "github.com/TwiN/gatus/v5/alerting/provider/matrix" + "github.com/TwiN/gatus/v5/alerting/provider/mattermost" + "github.com/TwiN/gatus/v5/alerting/provider/messagebird" + "github.com/TwiN/gatus/v5/alerting/provider/ntfy" + "github.com/TwiN/gatus/v5/alerting/provider/opsgenie" + "github.com/TwiN/gatus/v5/alerting/provider/pagerduty" + "github.com/TwiN/gatus/v5/alerting/provider/pushover" + "github.com/TwiN/gatus/v5/alerting/provider/slack" + "github.com/TwiN/gatus/v5/alerting/provider/teams" + "github.com/TwiN/gatus/v5/alerting/provider/teamsworkflows" + "github.com/TwiN/gatus/v5/alerting/provider/telegram" + "github.com/TwiN/gatus/v5/alerting/provider/twilio" + "github.com/TwiN/gatus/v5/alerting/provider/zulip" + "github.com/TwiN/gatus/v5/config/endpoint" +) + +// AlertProvider is the interface that each provider should implement +type AlertProvider interface { + // Validate the provider's configuration + Validate() error + + // Send an alert using the provider + Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error + + // GetDefaultAlert returns the provider's default alert configuration + GetDefaultAlert() *alert.Alert + + // ValidateOverrides validates the alert's provider override and, if present, the group override + ValidateOverrides(group string, alert *alert.Alert) error +} + +type Config[T any] interface { + Validate() error + Merge(override *T) +} + +// MergeProviderDefaultAlertIntoEndpointAlert parses an Endpoint alert by using the provider's default alert as a baseline +func MergeProviderDefaultAlertIntoEndpointAlert(providerDefaultAlert, endpointAlert *alert.Alert) { + if providerDefaultAlert == nil || endpointAlert == nil { + return + } + if endpointAlert.Enabled == nil { + endpointAlert.Enabled = providerDefaultAlert.Enabled + } + if endpointAlert.SendOnResolved == nil { + endpointAlert.SendOnResolved = providerDefaultAlert.SendOnResolved + } + if endpointAlert.Description == nil { + endpointAlert.Description = providerDefaultAlert.Description + } + if endpointAlert.FailureThreshold == 0 { + endpointAlert.FailureThreshold = providerDefaultAlert.FailureThreshold + } + if endpointAlert.SuccessThreshold == 0 { + endpointAlert.SuccessThreshold = providerDefaultAlert.SuccessThreshold + } +} + +var ( + // Validate provider interface implementation on compile + _ AlertProvider = (*awsses.AlertProvider)(nil) + _ AlertProvider = (*custom.AlertProvider)(nil) + _ AlertProvider = (*discord.AlertProvider)(nil) + _ AlertProvider = (*email.AlertProvider)(nil) + _ AlertProvider = (*gitea.AlertProvider)(nil) + _ AlertProvider = (*github.AlertProvider)(nil) + _ AlertProvider = (*gitlab.AlertProvider)(nil) + _ AlertProvider = (*googlechat.AlertProvider)(nil) + _ AlertProvider = (*jetbrainsspace.AlertProvider)(nil) + _ AlertProvider = (*matrix.AlertProvider)(nil) + _ AlertProvider = (*mattermost.AlertProvider)(nil) + _ AlertProvider = (*messagebird.AlertProvider)(nil) + _ AlertProvider = (*ntfy.AlertProvider)(nil) + _ AlertProvider = (*opsgenie.AlertProvider)(nil) + _ AlertProvider = (*pagerduty.AlertProvider)(nil) + _ AlertProvider = (*pushover.AlertProvider)(nil) + _ AlertProvider = (*slack.AlertProvider)(nil) + _ AlertProvider = (*teams.AlertProvider)(nil) + _ AlertProvider = (*teamsworkflows.AlertProvider)(nil) + _ AlertProvider = (*telegram.AlertProvider)(nil) + _ AlertProvider = (*twilio.AlertProvider)(nil) + _ AlertProvider = (*zulip.AlertProvider)(nil) + _ AlertProvider = (*incidentio.AlertProvider)(nil) + + // Validate config interface implementation on compile + _ Config[awsses.Config] = (*awsses.Config)(nil) + _ Config[custom.Config] = (*custom.Config)(nil) + _ Config[discord.Config] = (*discord.Config)(nil) + _ Config[email.Config] = (*email.Config)(nil) + _ Config[gitea.Config] = (*gitea.Config)(nil) + _ Config[github.Config] = (*github.Config)(nil) + _ Config[gitlab.Config] = (*gitlab.Config)(nil) + _ Config[googlechat.Config] = (*googlechat.Config)(nil) + _ Config[incidentio.Config] = (*incidentio.Config)(nil) + _ Config[jetbrainsspace.Config] = (*jetbrainsspace.Config)(nil) + _ Config[matrix.Config] = (*matrix.Config)(nil) + _ Config[mattermost.Config] = (*mattermost.Config)(nil) + _ Config[messagebird.Config] = (*messagebird.Config)(nil) + _ Config[ntfy.Config] = (*ntfy.Config)(nil) + _ Config[opsgenie.Config] = (*opsgenie.Config)(nil) + _ Config[pagerduty.Config] = (*pagerduty.Config)(nil) + _ Config[pushover.Config] = (*pushover.Config)(nil) + _ Config[slack.Config] = (*slack.Config)(nil) + _ Config[teams.Config] = (*teams.Config)(nil) + _ Config[teamsworkflows.Config] = (*teamsworkflows.Config)(nil) + _ Config[telegram.Config] = (*telegram.Config)(nil) + _ Config[twilio.Config] = (*twilio.Config)(nil) + _ Config[zulip.Config] = (*zulip.Config)(nil) +) diff --git a/alerting/provider/provider_test.go b/alerting/provider/provider_test.go new file mode 100644 index 00000000..fa1de566 --- /dev/null +++ b/alerting/provider/provider_test.go @@ -0,0 +1,153 @@ +package provider + +import ( + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" +) + +func TestParseWithDefaultAlert(t *testing.T) { + type Scenario struct { + Name string + DefaultAlert, EndpointAlert, ExpectedOutputAlert *alert.Alert + } + enabled := true + disabled := false + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []Scenario{ + { + Name: "endpoint-alert-type-only", + DefaultAlert: &alert.Alert{ + Enabled: &enabled, + SendOnResolved: &enabled, + Description: &firstDescription, + FailureThreshold: 5, + SuccessThreshold: 10, + }, + EndpointAlert: &alert.Alert{ + Type: alert.TypeDiscord, + }, + ExpectedOutputAlert: &alert.Alert{ + Type: alert.TypeDiscord, + Enabled: &enabled, + SendOnResolved: &enabled, + Description: &firstDescription, + FailureThreshold: 5, + SuccessThreshold: 10, + }, + }, + { + Name: "endpoint-alert-overwrites-default-alert", + DefaultAlert: &alert.Alert{ + Enabled: &disabled, + SendOnResolved: &disabled, + Description: &firstDescription, + FailureThreshold: 5, + SuccessThreshold: 10, + }, + EndpointAlert: &alert.Alert{ + Type: alert.TypeTelegram, + Enabled: &enabled, + SendOnResolved: &enabled, + Description: &secondDescription, + FailureThreshold: 6, + SuccessThreshold: 11, + }, + ExpectedOutputAlert: &alert.Alert{ + Type: alert.TypeTelegram, + Enabled: &enabled, + SendOnResolved: &enabled, + Description: &secondDescription, + FailureThreshold: 6, + SuccessThreshold: 11, + }, + }, + { + Name: "endpoint-alert-partially-overwrites-default-alert", + DefaultAlert: &alert.Alert{ + Enabled: &enabled, + SendOnResolved: &enabled, + Description: &firstDescription, + FailureThreshold: 5, + SuccessThreshold: 10, + }, + EndpointAlert: &alert.Alert{ + Type: alert.TypeDiscord, + Enabled: nil, + SendOnResolved: nil, + FailureThreshold: 6, + SuccessThreshold: 11, + }, + ExpectedOutputAlert: &alert.Alert{ + Type: alert.TypeDiscord, + Enabled: &enabled, + SendOnResolved: &enabled, + Description: &firstDescription, + FailureThreshold: 6, + SuccessThreshold: 11, + }, + }, + { + Name: "default-alert-type-should-be-ignored", + DefaultAlert: &alert.Alert{ + Type: alert.TypeTelegram, + Enabled: &enabled, + SendOnResolved: &enabled, + Description: &firstDescription, + FailureThreshold: 5, + SuccessThreshold: 10, + }, + EndpointAlert: &alert.Alert{ + Type: alert.TypeDiscord, + }, + ExpectedOutputAlert: &alert.Alert{ + Type: alert.TypeDiscord, + Enabled: &enabled, + SendOnResolved: &enabled, + Description: &firstDescription, + FailureThreshold: 5, + SuccessThreshold: 10, + }, + }, + { + Name: "no-default-alert", + DefaultAlert: &alert.Alert{ + Type: alert.TypeDiscord, + Enabled: nil, + SendOnResolved: nil, + Description: &firstDescription, + FailureThreshold: 2, + SuccessThreshold: 5, + }, + EndpointAlert: nil, + ExpectedOutputAlert: nil, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + MergeProviderDefaultAlertIntoEndpointAlert(scenario.DefaultAlert, scenario.EndpointAlert) + if scenario.ExpectedOutputAlert == nil { + if scenario.EndpointAlert != nil { + t.Fail() + } + return + } + if scenario.EndpointAlert.IsEnabled() != scenario.ExpectedOutputAlert.IsEnabled() { + t.Errorf("expected EndpointAlert.IsEnabled() to be %v, got %v", scenario.ExpectedOutputAlert.IsEnabled(), scenario.EndpointAlert.IsEnabled()) + } + if scenario.EndpointAlert.IsSendingOnResolved() != scenario.ExpectedOutputAlert.IsSendingOnResolved() { + t.Errorf("expected EndpointAlert.IsSendingOnResolved() to be %v, got %v", scenario.ExpectedOutputAlert.IsSendingOnResolved(), scenario.EndpointAlert.IsSendingOnResolved()) + } + if scenario.EndpointAlert.GetDescription() != scenario.ExpectedOutputAlert.GetDescription() { + t.Errorf("expected EndpointAlert.GetDescription() to be %v, got %v", scenario.ExpectedOutputAlert.GetDescription(), scenario.EndpointAlert.GetDescription()) + } + if scenario.EndpointAlert.FailureThreshold != scenario.ExpectedOutputAlert.FailureThreshold { + t.Errorf("expected EndpointAlert.FailureThreshold to be %v, got %v", scenario.ExpectedOutputAlert.FailureThreshold, scenario.EndpointAlert.FailureThreshold) + } + if scenario.EndpointAlert.SuccessThreshold != scenario.ExpectedOutputAlert.SuccessThreshold { + t.Errorf("expected EndpointAlert.SuccessThreshold to be %v, got %v", scenario.ExpectedOutputAlert.SuccessThreshold, scenario.EndpointAlert.SuccessThreshold) + } + }) + } +} diff --git a/alerting/provider/pushover/pushover.go b/alerting/provider/pushover/pushover.go new file mode 100644 index 00000000..aba7a4e8 --- /dev/null +++ b/alerting/provider/pushover/pushover.go @@ -0,0 +1,205 @@ +package pushover + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "gopkg.in/yaml.v3" +) + +const ( + restAPIURL = "https://api.pushover.net/1/messages.json" + defaultPriority = 0 +) + +var ( + ErrInvalidApplicationToken = errors.New("application-token must be 30 characters long") + ErrInvalidUserKey = errors.New("user-key must be 30 characters long") + ErrInvalidPriority = errors.New("priority and resolved-priority must be between -2 and 2") +) + +type Config struct { + // Key used to authenticate the application sending + // See "Your Applications" on the dashboard, or add a new one: https://pushover.net/apps/build + ApplicationToken string `yaml:"application-token"` + + // Key of the user or group the messages should be sent to + UserKey string `yaml:"user-key"` + + // The title of your message + // default: "Gatus: "" + Title string `yaml:"title,omitempty"` + + // Priority of all messages, ranging from -2 (very low) to 2 (Emergency) + // default: 0 + Priority int `yaml:"priority,omitempty"` + + // Priority of resolved messages, ranging from -2 (very low) to 2 (Emergency) + // default: 0 + ResolvedPriority int `yaml:"resolved-priority,omitempty"` + + // Sound of the messages (see: https://pushover.net/api#sounds) + // default: "" (pushover) + Sound string `yaml:"sound,omitempty"` +} + +func (cfg *Config) Validate() error { + if cfg.Priority == 0 { + cfg.Priority = defaultPriority + } + if cfg.ResolvedPriority == 0 { + cfg.ResolvedPriority = defaultPriority + } + if len(cfg.ApplicationToken) != 30 { + return ErrInvalidApplicationToken + } + if len(cfg.UserKey) != 30 { + return ErrInvalidUserKey + } + if cfg.Priority < -2 || cfg.Priority > 2 || cfg.ResolvedPriority < -2 || cfg.ResolvedPriority > 2 { + return ErrInvalidPriority + } + return nil +} + +func (cfg *Config) Merge(override *Config) { + if len(override.ApplicationToken) > 0 { + cfg.ApplicationToken = override.ApplicationToken + } + if len(override.UserKey) > 0 { + cfg.UserKey = override.UserKey + } + if len(override.Title) > 0 { + cfg.Title = override.Title + } + if override.Priority != 0 { + cfg.Priority = override.Priority + } + if override.ResolvedPriority != 0 { + cfg.ResolvedPriority = override.ResolvedPriority + } + if len(override.Sound) > 0 { + cfg.Sound = override.Sound + } +} + +// AlertProvider is the configuration necessary for sending an alert using Pushover +type AlertProvider struct { + DefaultConfig Config `yaml:",inline"` + + // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type + DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` +} + +// Validate the provider's configuration +func (provider *AlertProvider) Validate() error { + return provider.DefaultConfig.Validate() +} + +// Send an alert using the provider +// Reference doc for pushover: https://pushover.net/api +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + cfg, err := provider.GetConfig(ep.Group, alert) + if err != nil { + return err + } + buffer := bytes.NewBuffer(provider.buildRequestBody(cfg, ep, alert, result, resolved)) + request, err := http.NewRequest(http.MethodPost, restAPIURL, buffer) + if err != nil { + return err + } + request.Header.Set("Content-Type", "application/json") + response, err := client.GetHTTPClient(nil).Do(request) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode > 399 { + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body)) + } + return err +} + +type Body struct { + Token string `json:"token"` + User string `json:"user"` + Title string `json:"title,omitempty"` + Message string `json:"message"` + Priority int `json:"priority"` + Html int `json:"html"` + Sound string `json:"sound,omitempty"` +} + +// buildRequestBody builds the request body for the provider +func (provider *AlertProvider) buildRequestBody(cfg *Config, ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { + var message, formattedConditionResults string + priority := cfg.Priority + if resolved { + priority = cfg.ResolvedPriority + message = fmt.Sprintf("An alert for %s has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) + } else { + message = fmt.Sprintf("An alert for %s has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) + } + for _, conditionResult := range result.ConditionResults { + var prefix string + if conditionResult.Success { + prefix = "✅" + } else { + prefix = "❌" + } + formattedConditionResults += fmt.Sprintf("\n%s - %s", prefix, conditionResult.Condition) + } + if len(alert.GetDescription()) > 0 { + message += " with the following description: " + alert.GetDescription() + } + message += formattedConditionResults + title := "Gatus: " + ep.DisplayName() + if cfg.Title != "" { + title = cfg.Title + } + body, _ := json.Marshal(Body{ + Token: cfg.ApplicationToken, + User: cfg.UserKey, + Title: title, + Message: message, + Priority: priority, + Html: 1, + Sound: cfg.Sound, + }) + return body +} + +// GetDefaultAlert returns the provider's default alert configuration +func (provider *AlertProvider) GetDefaultAlert() *alert.Alert { + return provider.DefaultAlert +} + +// GetConfig returns the configuration for the provider with the overrides applied +func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) { + cfg := provider.DefaultConfig + // Handle alert overrides + if len(alert.ProviderOverride) != 0 { + overrideConfig := Config{} + if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil { + return nil, err + } + cfg.Merge(&overrideConfig) + } + // Validate the configuration + err := cfg.Validate() + return &cfg, err +} + +// ValidateOverrides validates the alert's provider override and, if present, the group override +func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error { + _, err := provider.GetConfig(group, alert) + return err +} diff --git a/alerting/provider/pushover/pushover_test.go b/alerting/provider/pushover/pushover_test.go new file mode 100644 index 00000000..aa0436c0 --- /dev/null +++ b/alerting/provider/pushover/pushover_test.go @@ -0,0 +1,252 @@ +package pushover + +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/test" +) + +func TestPushoverAlertProvider_IsValid(t *testing.T) { + t.Run("empty-invalid-provider", func(t *testing.T) { + invalidProvider := AlertProvider{} + if err := invalidProvider.Validate(); err == nil { + t.Error("provider shouldn't have been valid") + } + }) + t.Run("valid-provider", func(t *testing.T) { + validProvider := AlertProvider{ + DefaultConfig: Config{ + ApplicationToken: "aTokenWithLengthOf30characters", + UserKey: "aTokenWithLengthOf30characters", + Title: "Gatus Notification", + Priority: 1, + ResolvedPriority: 1, + }, + } + if err := validProvider.Validate(); err != nil { + t.Error("provider should've been valid") + } + }) + t.Run("invalid-provider", func(t *testing.T) { + invalidProvider := AlertProvider{ + DefaultConfig: Config{ + ApplicationToken: "aTokenWithLengthOfMoreThan30characters", + UserKey: "aTokenWithLengthOfMoreThan30characters", + Priority: 5, + }, + } + if err := invalidProvider.Validate(); err == nil { + t.Error("provider should've been invalid") + } + }) +} + +func TestAlertProvider_Send(t *testing.T) { + defer client.InjectHTTPClient(nil) + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + MockRoundTripper test.MockRoundTripper + ExpectedError bool + }{ + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{ApplicationToken: "aTokenWithLengthOf30characters", UserKey: "aTokenWithLengthOf30characters"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "triggered-error", + Provider: AlertProvider{DefaultConfig: Config{ApplicationToken: "aTokenWithLengthOf30characters", UserKey: "aTokenWithLengthOf30characters"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + { + Name: "resolved", + Provider: AlertProvider{DefaultConfig: Config{ApplicationToken: "aTokenWithLengthOf30characters", UserKey: "aTokenWithLengthOf30characters"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "resolved-error", + Provider: AlertProvider{DefaultConfig: Config{ApplicationToken: "aTokenWithLengthOf30characters", UserKey: "aTokenWithLengthOf30characters"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) + err := scenario.Provider.Send( + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if scenario.ExpectedError && err == nil { + t.Error("expected error, got none") + } + if !scenario.ExpectedError && err != nil { + t.Error("expected no error, got", err.Error()) + } + }) + } +} + +func TestAlertProvider_buildRequestBody(t *testing.T) { + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + ResolvedPriority bool + ExpectedBody string + }{ + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{ApplicationToken: "TokenWithLengthOf30Characters1", UserKey: "TokenWithLengthOf30Characters4"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: "{\"token\":\"TokenWithLengthOf30Characters1\",\"user\":\"TokenWithLengthOf30Characters4\",\"title\":\"Gatus: endpoint-name\",\"message\":\"An alert for \\u003cb\\u003eendpoint-name\\u003c/b\\u003e has been triggered due to having failed 3 time(s) in a row with the following description: description-1\\n❌ - [CONNECTED] == true\\n❌ - [STATUS] == 200\",\"priority\":0,\"html\":1}", + }, + { + Name: "triggered-customtitle", + Provider: AlertProvider{DefaultConfig: Config{ApplicationToken: "TokenWithLengthOf30Characters1", UserKey: "TokenWithLengthOf30Characters4", Title: "Gatus Notifications"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: "{\"token\":\"TokenWithLengthOf30Characters1\",\"user\":\"TokenWithLengthOf30Characters4\",\"title\":\"Gatus Notifications\",\"message\":\"An alert for \\u003cb\\u003eendpoint-name\\u003c/b\\u003e has been triggered due to having failed 3 time(s) in a row with the following description: description-1\\n❌ - [CONNECTED] == true\\n❌ - [STATUS] == 200\",\"priority\":0,\"html\":1}", + }, + { + Name: "resolved", + Provider: AlertProvider{DefaultConfig: Config{ApplicationToken: "TokenWithLengthOf30Characters2", UserKey: "TokenWithLengthOf30Characters5", Priority: 2, ResolvedPriority: 2}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: "{\"token\":\"TokenWithLengthOf30Characters2\",\"user\":\"TokenWithLengthOf30Characters5\",\"title\":\"Gatus: endpoint-name\",\"message\":\"An alert for \\u003cb\\u003eendpoint-name\\u003c/b\\u003e has been resolved after passing successfully 5 time(s) in a row with the following description: description-2\\n✅ - [CONNECTED] == true\\n✅ - [STATUS] == 200\",\"priority\":2,\"html\":1}", + }, + { + Name: "resolved-priority", + Provider: AlertProvider{DefaultConfig: Config{ApplicationToken: "TokenWithLengthOf30Characters2", UserKey: "TokenWithLengthOf30Characters5", Title: "Gatus Notifications", Priority: 2, ResolvedPriority: 0}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: "{\"token\":\"TokenWithLengthOf30Characters2\",\"user\":\"TokenWithLengthOf30Characters5\",\"title\":\"Gatus Notifications\",\"message\":\"An alert for \\u003cb\\u003eendpoint-name\\u003c/b\\u003e has been resolved after passing successfully 5 time(s) in a row with the following description: description-2\\n✅ - [CONNECTED] == true\\n✅ - [STATUS] == 200\",\"priority\":0,\"html\":1}", + }, + { + Name: "with-sound", + Provider: AlertProvider{DefaultConfig: Config{ApplicationToken: "TokenWithLengthOf30Characters2", UserKey: "TokenWithLengthOf30Characters5", Title: "Gatus Notifications", Priority: 2, ResolvedPriority: 2, Sound: "falling"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: "{\"token\":\"TokenWithLengthOf30Characters2\",\"user\":\"TokenWithLengthOf30Characters5\",\"title\":\"Gatus Notifications\",\"message\":\"An alert for \\u003cb\\u003eendpoint-name\\u003c/b\\u003e has been resolved after passing successfully 5 time(s) in a row with the following description: description-2\\n✅ - [CONNECTED] == true\\n✅ - [STATUS] == 200\",\"priority\":2,\"html\":1,\"sound\":\"falling\"}", + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + body := scenario.Provider.buildRequestBody( + &scenario.Provider.DefaultConfig, + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if string(body) != scenario.ExpectedBody { + t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body) + } + out := make(map[string]interface{}) + if err := json.Unmarshal(body, &out); err != nil { + t.Error("expected body to be valid JSON, got error:", err.Error()) + } + }) + } +} + +func TestAlertProvider_GetDefaultAlert(t *testing.T) { + if (&AlertProvider{DefaultAlert: &alert.Alert{}}).GetDefaultAlert() == nil { + t.Error("expected default alert to be not nil") + } + if (&AlertProvider{DefaultAlert: nil}).GetDefaultAlert() != nil { + t.Error("expected default alert to be nil") + } +} + +func TestAlertProvider_GetConfig(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + InputGroup string + InputAlert alert.Alert + ExpectedOutput Config + }{ + { + Name: "provider-no-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{ApplicationToken: "aTokenWithLengthOf30characters", UserKey: "aTokenWithLengthOf30characters"}, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{ApplicationToken: "aTokenWithLengthOf30characters", UserKey: "aTokenWithLengthOf30characters"}, + }, + { + Name: "provider-with-alert-override", + Provider: AlertProvider{ + DefaultConfig: Config{ApplicationToken: "aTokenWithLengthOf30characters", UserKey: "aTokenWithLengthOf30characters"}, + }, + InputGroup: "group", + InputAlert: alert.Alert{ProviderOverride: map[string]any{"application-token": "TokenWithLengthOf30Characters2", "user-key": "TokenWithLengthOf30Characters3"}}, + ExpectedOutput: Config{ApplicationToken: "TokenWithLengthOf30Characters2", UserKey: "TokenWithLengthOf30Characters3"}, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + got, err := scenario.Provider.GetConfig(scenario.InputGroup, &scenario.InputAlert) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if got.ApplicationToken != scenario.ExpectedOutput.ApplicationToken { + t.Errorf("expected application token to be %s, got %s", scenario.ExpectedOutput.ApplicationToken, got.ApplicationToken) + } + if got.UserKey != scenario.ExpectedOutput.UserKey { + t.Errorf("expected user key to be %s, got %s", scenario.ExpectedOutput.UserKey, got.UserKey) + } + // Test ValidateOverrides as well, since it really just calls GetConfig + if err = scenario.Provider.ValidateOverrides(scenario.InputGroup, &scenario.InputAlert); err != nil { + t.Errorf("unexpected error: %s", err) + } + }) + } +} diff --git a/alerting/provider/slack/slack.go b/alerting/provider/slack/slack.go new file mode 100644 index 00000000..e8f5376f --- /dev/null +++ b/alerting/provider/slack/slack.go @@ -0,0 +1,193 @@ +package slack + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "gopkg.in/yaml.v3" +) + +var ( + ErrWebhookURLNotSet = errors.New("webhook-url not set") + ErrDuplicateGroupOverride = errors.New("duplicate group override") +) + +type Config struct { + WebhookURL string `yaml:"webhook-url"` // Slack webhook URL +} + +func (cfg *Config) Validate() error { + if len(cfg.WebhookURL) == 0 { + return ErrWebhookURLNotSet + } + return nil +} + +func (cfg *Config) Merge(override *Config) { + if len(override.WebhookURL) > 0 { + cfg.WebhookURL = override.WebhookURL + } +} + +// AlertProvider is the configuration necessary for sending an alert using Slack +type AlertProvider struct { + DefaultConfig Config `yaml:",inline"` + + // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type + DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` + + // Overrides is a list of Override that may be prioritized over the default configuration + Overrides []Override `yaml:"overrides,omitempty"` +} + +// Override is a case under which the default integration is overridden +type Override struct { + Group string `yaml:"group"` + Config `yaml:",inline"` +} + +// Validate the provider's configuration +func (provider *AlertProvider) Validate() error { + registeredGroups := make(map[string]bool) + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if isAlreadyRegistered := registeredGroups[override.Group]; isAlreadyRegistered || override.Group == "" { + return ErrDuplicateGroupOverride + } + registeredGroups[override.Group] = true + } + } + return provider.DefaultConfig.Validate() +} + +// Send an alert using the provider +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + cfg, err := provider.GetConfig(ep.Group, alert) + if err != nil { + return err + } + buffer := bytes.NewBuffer(provider.buildRequestBody(ep, alert, result, resolved)) + request, err := http.NewRequest(http.MethodPost, cfg.WebhookURL, buffer) + if err != nil { + return err + } + request.Header.Set("Content-Type", "application/json") + response, err := client.GetHTTPClient(nil).Do(request) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode > 399 { + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body)) + } + return err +} + +type Body struct { + Text string `json:"text"` + Attachments []Attachment `json:"attachments"` +} + +type Attachment struct { + Title string `json:"title"` + Text string `json:"text"` + Short bool `json:"short"` + Color string `json:"color"` + Fields []Field `json:"fields,omitempty"` +} + +type Field struct { + Title string `json:"title"` + Value string `json:"value"` + Short bool `json:"short"` +} + +// buildRequestBody builds the request body for the provider +func (provider *AlertProvider) buildRequestBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { + var message, color string + if resolved { + message = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) + color = "#36A64F" + } else { + message = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) + color = "#DD0000" + } + var formattedConditionResults string + for _, conditionResult := range result.ConditionResults { + var prefix string + if conditionResult.Success { + prefix = ":white_check_mark:" + } else { + prefix = ":x:" + } + formattedConditionResults += fmt.Sprintf("%s - `%s`\n", prefix, conditionResult.Condition) + } + var description string + if alertDescription := alert.GetDescription(); len(alertDescription) > 0 { + description = ":\n> " + alertDescription + } + body := Body{ + Text: "", + Attachments: []Attachment{ + { + Title: ":helmet_with_white_cross: Gatus", + Text: message + description, + Short: false, + Color: color, + }, + }, + } + if len(formattedConditionResults) > 0 { + body.Attachments[0].Fields = append(body.Attachments[0].Fields, Field{ + Title: "Condition results", + Value: formattedConditionResults, + Short: false, + }) + } + bodyAsJSON, _ := json.Marshal(body) + return bodyAsJSON +} + +// GetDefaultAlert returns the provider's default alert configuration +func (provider *AlertProvider) GetDefaultAlert() *alert.Alert { + return provider.DefaultAlert +} + +// GetConfig returns the configuration for the provider with the overrides applied +func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) { + cfg := provider.DefaultConfig + // Handle group overrides + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if group == override.Group { + cfg.Merge(&override.Config) + break + } + } + } + // Handle alert overrides + if len(alert.ProviderOverride) != 0 { + overrideConfig := Config{} + if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil { + return nil, err + } + cfg.Merge(&overrideConfig) + } + // Validate the configuration + err := cfg.Validate() + return &cfg, err +} + +// ValidateOverrides validates the alert's provider override and, if present, the group override +func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error { + _, err := provider.GetConfig(group, alert) + return err +} diff --git a/alerting/provider/slack/slack_test.go b/alerting/provider/slack/slack_test.go new file mode 100644 index 00000000..aa9bd1d3 --- /dev/null +++ b/alerting/provider/slack/slack_test.go @@ -0,0 +1,319 @@ +package slack + +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/test" +) + +func TestAlertProvider_Validate(t *testing.T) { + invalidProvider := AlertProvider{DefaultConfig: Config{WebhookURL: ""}} + if err := invalidProvider.Validate(); err == nil { + t.Error("provider shouldn't have been valid") + } + validProvider := AlertProvider{DefaultConfig: Config{WebhookURL: "https://example.com"}} + if err := validProvider.Validate(); err != nil { + t.Error("provider should've been valid") + } +} + +func TestAlertProvider_ValidateWithOverride(t *testing.T) { + providerWithInvalidOverrideGroup := AlertProvider{ + Overrides: []Override{ + { + Config: Config{WebhookURL: "http://example.com"}, + Group: "", + }, + }, + } + if err := providerWithInvalidOverrideGroup.Validate(); err == nil { + t.Error("provider Group shouldn't have been valid") + } + providerWithInvalidOverrideTo := AlertProvider{ + Overrides: []Override{ + { + Config: Config{WebhookURL: ""}, + Group: "group", + }, + }, + } + if err := providerWithInvalidOverrideTo.Validate(); err == nil { + t.Error("provider integration key shouldn't have been valid") + } + providerWithValidOverride := AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: []Override{ + { + Config: Config{WebhookURL: "http://example.com"}, + Group: "group", + }, + }, + } + if err := providerWithValidOverride.Validate(); err != nil { + t.Error("provider should've been valid") + } +} + +func TestAlertProvider_Send(t *testing.T) { + defer client.InjectHTTPClient(nil) + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + MockRoundTripper test.MockRoundTripper + ExpectedError bool + }{ + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "triggered-error", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + { + Name: "resolved", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "resolved-error", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) + err := scenario.Provider.Send( + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if scenario.ExpectedError && err == nil { + t.Error("expected error, got none") + } + if !scenario.ExpectedError && err != nil { + t.Error("expected no error, got", err.Error()) + } + }) + } +} + +func TestAlertProvider_buildRequestBody(t *testing.T) { + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Endpoint endpoint.Endpoint + Alert alert.Alert + NoConditions bool + Resolved bool + ExpectedBody string + }{ + { + Name: "triggered", + Provider: AlertProvider{}, + Endpoint: endpoint.Endpoint{Name: "name"}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: "{\"text\":\"\",\"attachments\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"text\":\"An alert for *name* has been triggered due to having failed 3 time(s) in a row:\\n\\u003e description-1\",\"short\":false,\"color\":\"#DD0000\",\"fields\":[{\"title\":\"Condition results\",\"value\":\":x: - `[CONNECTED] == true`\\n:x: - `[STATUS] == 200`\\n\",\"short\":false}]}]}", + }, + { + Name: "triggered-with-group", + Provider: AlertProvider{}, + Endpoint: endpoint.Endpoint{Name: "name", Group: "group"}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: "{\"text\":\"\",\"attachments\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"text\":\"An alert for *group/name* has been triggered due to having failed 3 time(s) in a row:\\n\\u003e description-1\",\"short\":false,\"color\":\"#DD0000\",\"fields\":[{\"title\":\"Condition results\",\"value\":\":x: - `[CONNECTED] == true`\\n:x: - `[STATUS] == 200`\\n\",\"short\":false}]}]}", + }, + { + Name: "triggered-with-no-conditions", + NoConditions: true, + Provider: AlertProvider{}, + Endpoint: endpoint.Endpoint{Name: "name"}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: "{\"text\":\"\",\"attachments\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"text\":\"An alert for *name* has been triggered due to having failed 3 time(s) in a row:\\n\\u003e description-1\",\"short\":false,\"color\":\"#DD0000\"}]}", + }, + { + Name: "resolved", + Provider: AlertProvider{}, + Endpoint: endpoint.Endpoint{Name: "name"}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: "{\"text\":\"\",\"attachments\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"text\":\"An alert for *name* has been resolved after passing successfully 5 time(s) in a row:\\n\\u003e description-2\",\"short\":false,\"color\":\"#36A64F\",\"fields\":[{\"title\":\"Condition results\",\"value\":\":white_check_mark: - `[CONNECTED] == true`\\n:white_check_mark: - `[STATUS] == 200`\\n\",\"short\":false}]}]}", + }, + { + Name: "resolved-with-group", + Provider: AlertProvider{}, + Endpoint: endpoint.Endpoint{Name: "name", Group: "group"}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: "{\"text\":\"\",\"attachments\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"text\":\"An alert for *group/name* has been resolved after passing successfully 5 time(s) in a row:\\n\\u003e description-2\",\"short\":false,\"color\":\"#36A64F\",\"fields\":[{\"title\":\"Condition results\",\"value\":\":white_check_mark: - `[CONNECTED] == true`\\n:white_check_mark: - `[STATUS] == 200`\\n\",\"short\":false}]}]}", + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + var conditionResults []*endpoint.ConditionResult + if !scenario.NoConditions { + conditionResults = []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + } + } + body := scenario.Provider.buildRequestBody( + &scenario.Endpoint, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: conditionResults, + }, + scenario.Resolved, + ) + if string(body) != scenario.ExpectedBody { + t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body) + } + out := make(map[string]interface{}) + if err := json.Unmarshal(body, &out); err != nil { + t.Error("expected body to be valid JSON, got error:", err.Error()) + } + }) + } +} + +func TestAlertProvider_GetDefaultAlert(t *testing.T) { + if (&AlertProvider{DefaultAlert: &alert.Alert{}}).GetDefaultAlert() == nil { + t.Error("expected default alert to be not nil") + } + if (&AlertProvider{DefaultAlert: nil}).GetDefaultAlert() != nil { + t.Error("expected default alert to be nil") + } +} + +func TestAlertProvider_GetConfig(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + InputGroup string + InputAlert alert.Alert + ExpectedOutput Config + }{ + { + Name: "provider-no-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: nil, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://example.com"}, + }, + { + Name: "provider-no-override-specify-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: nil, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://example.com"}, + }, + { + Name: "provider-with-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{WebhookURL: "http://example01.com"}, + }, + }, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://example.com"}, + }, + { + Name: "provider-with-override-specify-group-should-override", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{WebhookURL: "http://group-example.com"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://group-example.com"}, + }, + { + Name: "provider-with-group-override-and-alert-override--alert-override-should-take-precedence", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{WebhookURL: "http://group-example.com"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{ProviderOverride: map[string]any{"webhook-url": "http://alert-example.com"}}, + ExpectedOutput: Config{WebhookURL: "http://alert-example.com"}, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + got, err := scenario.Provider.GetConfig(scenario.InputGroup, &scenario.InputAlert) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if got.WebhookURL != scenario.ExpectedOutput.WebhookURL { + t.Errorf("expected webhook URL to be %s, got %s", scenario.ExpectedOutput.WebhookURL, got.WebhookURL) + } + // Test ValidateOverrides as well, since it really just calls GetConfig + if err = scenario.Provider.ValidateOverrides(scenario.InputGroup, &scenario.InputAlert); err != nil { + t.Errorf("unexpected error: %s", err) + } + }) + } +} diff --git a/alerting/provider/teams/teams.go b/alerting/provider/teams/teams.go new file mode 100644 index 00000000..bd9e9295 --- /dev/null +++ b/alerting/provider/teams/teams.go @@ -0,0 +1,196 @@ +package teams + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "gopkg.in/yaml.v3" +) + +var ( + ErrWebhookURLNotSet = errors.New("webhook-url not set") + ErrDuplicateGroupOverride = errors.New("duplicate group override") +) + +type Config struct { + WebhookURL string `yaml:"webhook-url"` + Title string `yaml:"title,omitempty"` // Title of the message that will be sent + + // ClientConfig is the configuration of the client used to communicate with the provider's target + ClientConfig *client.Config `yaml:"client,omitempty"` +} + +func (cfg *Config) Validate() error { + if len(cfg.WebhookURL) == 0 { + return ErrWebhookURLNotSet + } + return nil +} + +func (cfg *Config) Merge(override *Config) { + if override.ClientConfig != nil { + cfg.ClientConfig = override.ClientConfig + } + if len(override.WebhookURL) > 0 { + cfg.WebhookURL = override.WebhookURL + } + if len(override.Title) > 0 { + cfg.Title = override.Title + } +} + +// AlertProvider is the configuration necessary for sending an alert using Teams +type AlertProvider struct { + DefaultConfig Config `yaml:",inline"` + + // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type + DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` + + // Overrides is a list of Override that may be prioritized over the default configuration + Overrides []Override `yaml:"overrides,omitempty"` +} + +// Override is a case under which the default integration is overridden +type Override struct { + Group string `yaml:"group"` + Config `yaml:",inline"` +} + +// Validate the provider's configuration +func (provider *AlertProvider) Validate() error { + registeredGroups := make(map[string]bool) + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if isAlreadyRegistered := registeredGroups[override.Group]; isAlreadyRegistered || override.Group == "" { + return ErrDuplicateGroupOverride + } + registeredGroups[override.Group] = true + } + } + return provider.DefaultConfig.Validate() +} + +// Send an alert using the provider +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + cfg, err := provider.GetConfig(ep.Group, alert) + if err != nil { + return err + } + buffer := bytes.NewBuffer(provider.buildRequestBody(cfg, ep, alert, result, resolved)) + request, err := http.NewRequest(http.MethodPost, cfg.WebhookURL, buffer) + if err != nil { + return err + } + request.Header.Set("Content-Type", "application/json") + response, err := client.GetHTTPClient(cfg.ClientConfig).Do(request) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode > 399 { + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body)) + } + return err +} + +type Body struct { + Type string `json:"@type"` + Context string `json:"@context"` + ThemeColor string `json:"themeColor"` + Title string `json:"title"` + Text string `json:"text"` + Sections []Section `json:"sections,omitempty"` +} + +type Section struct { + ActivityTitle string `json:"activityTitle"` + Text string `json:"text"` +} + +// buildRequestBody builds the request body for the provider +func (provider *AlertProvider) buildRequestBody(cfg *Config, ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { + var message, color string + if resolved { + message = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) + color = "#36A64F" + } else { + message = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) + color = "#DD0000" + } + var formattedConditionResults string + for _, conditionResult := range result.ConditionResults { + var prefix string + if conditionResult.Success { + prefix = "✅" + } else { + prefix = "❌" + } + formattedConditionResults += fmt.Sprintf("%s - `%s`
", prefix, conditionResult.Condition) + } + var description string + if alertDescription := alert.GetDescription(); len(alertDescription) > 0 { + description = ": " + alertDescription + } + body := Body{ + Type: "MessageCard", + Context: "http://schema.org/extensions", + ThemeColor: color, + Title: cfg.Title, + Text: message + description, + } + if len(body.Title) == 0 { + body.Title = "🚨 Gatus" + } + if len(formattedConditionResults) > 0 { + body.Sections = append(body.Sections, Section{ + ActivityTitle: "Condition results", + Text: formattedConditionResults, + }) + } + bodyAsJSON, _ := json.Marshal(body) + return bodyAsJSON +} + +// GetDefaultAlert returns the provider's default alert configuration +func (provider *AlertProvider) GetDefaultAlert() *alert.Alert { + return provider.DefaultAlert +} + +// GetConfig returns the configuration for the provider with the overrides applied +func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) { + cfg := provider.DefaultConfig + // Handle group overrides + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if group == override.Group { + cfg.Merge(&override.Config) + break + } + } + } + // Handle alert overrides + if len(alert.ProviderOverride) != 0 { + overrideConfig := Config{} + if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil { + return nil, err + } + cfg.Merge(&overrideConfig) + } + // Validate the configuration + err := cfg.Validate() + return &cfg, err +} + +// ValidateOverrides validates the alert's provider override and, if present, the group override +func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error { + _, err := provider.GetConfig(group, alert) + return err +} diff --git a/alerting/provider/teams/teams_test.go b/alerting/provider/teams/teams_test.go new file mode 100644 index 00000000..f2ce4cf2 --- /dev/null +++ b/alerting/provider/teams/teams_test.go @@ -0,0 +1,298 @@ +package teams + +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/test" +) + +func TestAlertProvider_Validate(t *testing.T) { + invalidProvider := AlertProvider{DefaultConfig: Config{WebhookURL: ""}} + if err := invalidProvider.Validate(); err == nil { + t.Error("provider shouldn't have been valid") + } + validProvider := AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}} + if err := validProvider.Validate(); err != nil { + t.Error("provider should've been valid") + } +} + +func TestAlertProvider_ValidateWithOverride(t *testing.T) { + providerWithInvalidOverrideGroup := AlertProvider{ + Overrides: []Override{ + { + Config: Config{WebhookURL: "http://example.com"}, + Group: "", + }, + }, + } + if err := providerWithInvalidOverrideGroup.Validate(); err == nil { + t.Error("provider Group shouldn't have been valid") + } + providerWithInvalidOverrideTo := AlertProvider{ + Overrides: []Override{ + { + Config: Config{WebhookURL: ""}, + Group: "group", + }, + }, + } + if err := providerWithInvalidOverrideTo.Validate(); err == nil { + t.Error("provider integration key shouldn't have been valid") + } + providerWithValidOverride := AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: []Override{ + { + Config: Config{WebhookURL: "http://example.com"}, + Group: "group", + }, + }, + } + if err := providerWithValidOverride.Validate(); err != nil { + t.Error("provider should've been valid") + } +} + +func TestAlertProvider_Send(t *testing.T) { + defer client.InjectHTTPClient(nil) + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + MockRoundTripper test.MockRoundTripper + ExpectedError bool + }{ + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "triggered-error", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + { + Name: "resolved", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "resolved-error", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) + err := scenario.Provider.Send( + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if scenario.ExpectedError && err == nil { + t.Error("expected error, got none") + } + if !scenario.ExpectedError && err != nil { + t.Error("expected no error, got", err.Error()) + } + }) + } +} + +func TestAlertProvider_buildRequestBody(t *testing.T) { + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + NoConditions bool + Resolved bool + ExpectedBody string + }{ + { + Name: "triggered", + Provider: AlertProvider{}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: "{\"@type\":\"MessageCard\",\"@context\":\"http://schema.org/extensions\",\"themeColor\":\"#DD0000\",\"title\":\"\\u0026#x1F6A8; Gatus\",\"text\":\"An alert for *endpoint-name* has been triggered due to having failed 3 time(s) in a row: description-1\",\"sections\":[{\"activityTitle\":\"Condition results\",\"text\":\"\\u0026#x274C; - `[CONNECTED] == true`\\u003cbr/\\u003e\\u0026#x274C; - `[STATUS] == 200`\\u003cbr/\\u003e\"}]}", + }, + { + Name: "resolved", + Provider: AlertProvider{}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: "{\"@type\":\"MessageCard\",\"@context\":\"http://schema.org/extensions\",\"themeColor\":\"#36A64F\",\"title\":\"\\u0026#x1F6A8; Gatus\",\"text\":\"An alert for *endpoint-name* has been resolved after passing successfully 5 time(s) in a row: description-2\",\"sections\":[{\"activityTitle\":\"Condition results\",\"text\":\"\\u0026#x2705; - `[CONNECTED] == true`\\u003cbr/\\u003e\\u0026#x2705; - `[STATUS] == 200`\\u003cbr/\\u003e\"}]}", + }, + { + Name: "resolved-with-no-conditions", + NoConditions: true, + Provider: AlertProvider{}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: "{\"@type\":\"MessageCard\",\"@context\":\"http://schema.org/extensions\",\"themeColor\":\"#36A64F\",\"title\":\"\\u0026#x1F6A8; Gatus\",\"text\":\"An alert for *endpoint-name* has been resolved after passing successfully 5 time(s) in a row: description-2\"}", + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + var conditionResults []*endpoint.ConditionResult + if !scenario.NoConditions { + conditionResults = []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + } + } + body := scenario.Provider.buildRequestBody( + &scenario.Provider.DefaultConfig, + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ConditionResults: conditionResults}, + scenario.Resolved, + ) + if string(body) != scenario.ExpectedBody { + t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body) + } + out := make(map[string]interface{}) + if err := json.Unmarshal(body, &out); err != nil { + t.Error("expected body to be valid JSON, got error:", err.Error()) + } + }) + } +} + +func TestAlertProvider_GetDefaultAlert(t *testing.T) { + if (&AlertProvider{DefaultAlert: &alert.Alert{}}).GetDefaultAlert() == nil { + t.Error("expected default alert to be not nil") + } + if (&AlertProvider{DefaultAlert: nil}).GetDefaultAlert() != nil { + t.Error("expected default alert to be nil") + } +} + +func TestAlertProvider_GetConfig(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + InputGroup string + InputAlert alert.Alert + ExpectedOutput Config + }{ + { + Name: "provider-no-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: nil, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://example.com"}, + }, + { + Name: "provider-no-override-specify-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: nil, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://example.com"}, + }, + { + Name: "provider-with-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{WebhookURL: "http://example01.com"}, + }, + }, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://example.com"}, + }, + { + Name: "provider-with-override-specify-group-should-override", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{WebhookURL: "http://group-example.com"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://group-example.com"}, + }, + { + Name: "provider-with-group-override-and-alert-override--alert-override-should-take-precedence", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{WebhookURL: "http://group-example.com"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{ProviderOverride: map[string]any{"webhook-url": "http://alert-example.com"}}, + ExpectedOutput: Config{WebhookURL: "http://alert-example.com"}, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + got, err := scenario.Provider.GetConfig(scenario.InputGroup, &scenario.InputAlert) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if got.WebhookURL != scenario.ExpectedOutput.WebhookURL { + t.Errorf("expected webhook URL to be %s, got %s", scenario.ExpectedOutput.WebhookURL, got.WebhookURL) + } + // Test ValidateOverrides as well, since it really just calls GetConfig + if err = scenario.Provider.ValidateOverrides(scenario.InputGroup, &scenario.InputAlert); err != nil { + t.Errorf("unexpected error: %s", err) + } + }) + } +} diff --git a/alerting/provider/teamsworkflows/teamsworkflows.go b/alerting/provider/teamsworkflows/teamsworkflows.go new file mode 100644 index 00000000..6ab5db14 --- /dev/null +++ b/alerting/provider/teamsworkflows/teamsworkflows.go @@ -0,0 +1,255 @@ +package teamsworkflows + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "gopkg.in/yaml.v3" +) + +var ( + ErrWebhookURLNotSet = errors.New("webhook-url not set") + ErrDuplicateGroupOverride = errors.New("duplicate group override") +) + +type Config struct { + WebhookURL string `yaml:"webhook-url"` + Title string `yaml:"title,omitempty"` // Title of the message that will be sent +} + +func (cfg *Config) Validate() error { + if len(cfg.WebhookURL) == 0 { + return ErrWebhookURLNotSet + } + return nil +} + +func (cfg *Config) Merge(override *Config) { + if len(override.WebhookURL) > 0 { + cfg.WebhookURL = override.WebhookURL + } + if len(override.Title) > 0 { + cfg.Title = override.Title + } +} + +// AlertProvider is the configuration necessary for sending an alert using Teams +type AlertProvider struct { + DefaultConfig Config `yaml:",inline"` + + // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type + DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` + + // Overrides is a list of Override that may be prioritized over the default configuration + Overrides []Override `yaml:"overrides,omitempty"` +} + +// Override is a case under which the default integration is overridden +type Override struct { + Group string `yaml:"group"` + Config `yaml:",inline"` +} + +// Validate the provider's configuration +func (provider *AlertProvider) Validate() error { + registeredGroups := make(map[string]bool) + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if isAlreadyRegistered := registeredGroups[override.Group]; isAlreadyRegistered || override.Group == "" { + return ErrDuplicateGroupOverride + } + registeredGroups[override.Group] = true + } + } + return provider.DefaultConfig.Validate() +} + +// Send an alert using the provider +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + cfg, err := provider.GetConfig(ep.Group, alert) + if err != nil { + return err + } + buffer := bytes.NewBuffer(provider.buildRequestBody(cfg, ep, alert, result, resolved)) + request, err := http.NewRequest(http.MethodPost, cfg.WebhookURL, buffer) + if err != nil { + return err + } + request.Header.Set("Content-Type", "application/json") + response, err := client.GetHTTPClient(nil).Do(request) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode > 399 { + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body)) + } + return err +} + +// AdaptiveCardBody represents the structure of an Adaptive Card +type AdaptiveCardBody struct { + Type string `json:"type"` + Version string `json:"version"` + Body []CardBody `json:"body"` + MSTeams MSTeamsBody `json:"msteams"` +} + +// CardBody represents the body of the Adaptive Card +type CardBody struct { + Type string `json:"type"` + Text string `json:"text,omitempty"` + Wrap bool `json:"wrap"` + Separator bool `json:"separator,omitempty"` + Size string `json:"size,omitempty"` + Weight string `json:"weight,omitempty"` + Items []CardBody `json:"items,omitempty"` + Facts []Fact `json:"facts,omitempty"` + FactSet *FactSetBody `json:"factSet,omitempty"` + Style string `json:"style,omitempty"` +} + +// MSTeamsBody represents the msteams options +type MSTeamsBody struct { + Width string `json:"width"` +} + +// FactSetBody represents the FactSet in the Adaptive Card +type FactSetBody struct { + Type string `json:"type"` + Facts []Fact `json:"facts"` +} + +// Fact represents an individual fact in the FactSet +type Fact struct { + Title string `json:"title"` + Value string `json:"value"` +} + +// buildRequestBody builds the request body for the provider +func (provider *AlertProvider) buildRequestBody(cfg *Config, ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { + var message string + var themeColor string + if resolved { + message = fmt.Sprintf("An alert for **%s** has been resolved after passing successfully %d time(s) in a row.", ep.DisplayName(), alert.SuccessThreshold) + themeColor = "Good" // green + } else { + message = fmt.Sprintf("An alert for **%s** has been triggered due to having failed %d time(s) in a row.", ep.DisplayName(), alert.FailureThreshold) + themeColor = "Attention" // red + } + + // Configure default title if it's not provided + title := "⛑️ Gatus" + if cfg.Title != "" { + title = cfg.Title + } + + // Build the facts from the condition results + var facts []Fact + for _, conditionResult := range result.ConditionResults { + var key string + if conditionResult.Success { + key = "✅" + } else { + key = "❌" + } + facts = append(facts, Fact{ + Title: key, + Value: conditionResult.Condition, + }) + } + + cardContent := AdaptiveCardBody{ + Type: "AdaptiveCard", + Version: "1.4", // Version 1.5 and 1.6 doesn't seem to be supported by Teams as of 27/08/2024 + Body: []CardBody{ + { + Type: "Container", + Style: themeColor, + Items: []CardBody{ + { + Type: "Container", + Style: "Default", + Items: []CardBody{ + { + Type: "TextBlock", + Text: title, + Size: "Medium", + Weight: "Bolder", + }, + { + Type: "TextBlock", + Text: message, + Wrap: true, + }, + { + Type: "FactSet", + Facts: facts, + }, + }, + }, + }, + }, + }, + MSTeams: MSTeamsBody{ + Width: "Full", + }, + } + + attachment := map[string]interface{}{ + "contentType": "application/vnd.microsoft.card.adaptive", + "content": cardContent, + } + + payload := map[string]interface{}{ + "type": "message", + "attachments": []interface{}{attachment}, + } + + bodyAsJSON, _ := json.Marshal(payload) + return bodyAsJSON +} + +// GetDefaultAlert returns the provider's default alert configuration +func (provider *AlertProvider) GetDefaultAlert() *alert.Alert { + return provider.DefaultAlert +} + +// GetConfig returns the configuration for the provider with the overrides applied +func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) { + cfg := provider.DefaultConfig + // Handle group overrides + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if group == override.Group { + cfg.Merge(&override.Config) + break + } + } + } + // Handle alert overrides + if len(alert.ProviderOverride) != 0 { + overrideConfig := Config{} + if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil { + return nil, err + } + cfg.Merge(&overrideConfig) + } + // Validate the configuration + err := cfg.Validate() + return &cfg, err +} + +// ValidateOverrides validates the alert's provider override and, if present, the group override +func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error { + _, err := provider.GetConfig(group, alert) + return err +} diff --git a/alerting/provider/teamsworkflows/teamsworkflows_test.go b/alerting/provider/teamsworkflows/teamsworkflows_test.go new file mode 100644 index 00000000..c0716940 --- /dev/null +++ b/alerting/provider/teamsworkflows/teamsworkflows_test.go @@ -0,0 +1,298 @@ +package teamsworkflows + +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/test" +) + +func TestAlertProvider_Validate(t *testing.T) { + invalidProvider := AlertProvider{DefaultConfig: Config{WebhookURL: ""}} + if err := invalidProvider.Validate(); err == nil { + t.Error("provider shouldn't have been valid") + } + validProvider := AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}} + if err := validProvider.Validate(); err != nil { + t.Error("provider should've been valid, got", err.Error()) + } +} + +func TestAlertProvider_ValidateWithOverride(t *testing.T) { + providerWithInvalidOverrideGroup := AlertProvider{ + Overrides: []Override{ + { + Config: Config{WebhookURL: "http://example.com"}, + Group: "", + }, + }, + } + if err := providerWithInvalidOverrideGroup.Validate(); err == nil { + t.Error("provider Group shouldn't have been valid") + } + providerWithInvalidOverrideTo := AlertProvider{ + Overrides: []Override{ + { + Config: Config{WebhookURL: ""}, + Group: "group", + }, + }, + } + if err := providerWithInvalidOverrideTo.Validate(); err == nil { + t.Error("provider integration key shouldn't have been valid") + } + providerWithValidOverride := AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: []Override{ + { + Config: Config{WebhookURL: "http://example.com"}, + Group: "group", + }, + }, + } + if err := providerWithValidOverride.Validate(); err != nil { + t.Error("provider should've been valid") + } +} + +func TestAlertProvider_Send(t *testing.T) { + defer client.InjectHTTPClient(nil) + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + MockRoundTripper test.MockRoundTripper + ExpectedError bool + }{ + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "triggered-error", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + { + Name: "resolved", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "resolved-error", + Provider: AlertProvider{DefaultConfig: Config{WebhookURL: "http://example.com"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) + err := scenario.Provider.Send( + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if scenario.ExpectedError && err == nil { + t.Error("expected error, got none") + } + if !scenario.ExpectedError && err != nil { + t.Error("expected no error, got", err.Error()) + } + }) + } +} + +func TestAlertProvider_buildRequestBody(t *testing.T) { + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + NoConditions bool + Resolved bool + ExpectedBody string + }{ + { + Name: "triggered", + Provider: AlertProvider{}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: "{\"attachments\":[{\"content\":{\"type\":\"AdaptiveCard\",\"version\":\"1.4\",\"body\":[{\"type\":\"Container\",\"wrap\":false,\"items\":[{\"type\":\"Container\",\"wrap\":false,\"items\":[{\"type\":\"TextBlock\",\"text\":\"⛑️ Gatus\",\"wrap\":false,\"size\":\"Medium\",\"weight\":\"Bolder\"},{\"type\":\"TextBlock\",\"text\":\"An alert for **endpoint-name** has been triggered due to having failed 3 time(s) in a row.\",\"wrap\":true},{\"type\":\"FactSet\",\"wrap\":false,\"facts\":[{\"title\":\"❌\",\"value\":\"[CONNECTED] == true\"},{\"title\":\"❌\",\"value\":\"[STATUS] == 200\"}]}],\"style\":\"Default\"}],\"style\":\"Attention\"}],\"msteams\":{\"width\":\"Full\"}},\"contentType\":\"application/vnd.microsoft.card.adaptive\"}],\"type\":\"message\"}", + }, + { + Name: "resolved", + Provider: AlertProvider{}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: "{\"attachments\":[{\"content\":{\"type\":\"AdaptiveCard\",\"version\":\"1.4\",\"body\":[{\"type\":\"Container\",\"wrap\":false,\"items\":[{\"type\":\"Container\",\"wrap\":false,\"items\":[{\"type\":\"TextBlock\",\"text\":\"⛑️ Gatus\",\"wrap\":false,\"size\":\"Medium\",\"weight\":\"Bolder\"},{\"type\":\"TextBlock\",\"text\":\"An alert for **endpoint-name** has been resolved after passing successfully 5 time(s) in a row.\",\"wrap\":true},{\"type\":\"FactSet\",\"wrap\":false,\"facts\":[{\"title\":\"✅\",\"value\":\"[CONNECTED] == true\"},{\"title\":\"✅\",\"value\":\"[STATUS] == 200\"}]}],\"style\":\"Default\"}],\"style\":\"Good\"}],\"msteams\":{\"width\":\"Full\"}},\"contentType\":\"application/vnd.microsoft.card.adaptive\"}],\"type\":\"message\"}", + }, + { + Name: "resolved-with-no-conditions", + NoConditions: true, + Provider: AlertProvider{}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: "{\"attachments\":[{\"content\":{\"type\":\"AdaptiveCard\",\"version\":\"1.4\",\"body\":[{\"type\":\"Container\",\"wrap\":false,\"items\":[{\"type\":\"Container\",\"wrap\":false,\"items\":[{\"type\":\"TextBlock\",\"text\":\"⛑️ Gatus\",\"wrap\":false,\"size\":\"Medium\",\"weight\":\"Bolder\"},{\"type\":\"TextBlock\",\"text\":\"An alert for **endpoint-name** has been resolved after passing successfully 5 time(s) in a row.\",\"wrap\":true},{\"type\":\"FactSet\",\"wrap\":false}],\"style\":\"Default\"}],\"style\":\"Good\"}],\"msteams\":{\"width\":\"Full\"}},\"contentType\":\"application/vnd.microsoft.card.adaptive\"}],\"type\":\"message\"}", + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + var conditionResults []*endpoint.ConditionResult + if !scenario.NoConditions { + conditionResults = []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + } + } + body := scenario.Provider.buildRequestBody( + &scenario.Provider.DefaultConfig, + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ConditionResults: conditionResults}, + scenario.Resolved, + ) + if string(body) != scenario.ExpectedBody { + t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body) + } + out := make(map[string]interface{}) + if err := json.Unmarshal(body, &out); err != nil { + t.Error("expected body to be valid JSON, got error:", err.Error()) + } + }) + } +} + +func TestAlertProvider_GetDefaultAlert(t *testing.T) { + if (&AlertProvider{DefaultAlert: &alert.Alert{}}).GetDefaultAlert() == nil { + t.Error("expected default alert to be not nil") + } + if (&AlertProvider{DefaultAlert: nil}).GetDefaultAlert() != nil { + t.Error("expected default alert to be nil") + } +} + +func TestAlertProvider_GetConfig(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + InputGroup string + InputAlert alert.Alert + ExpectedOutput Config + }{ + { + Name: "provider-no-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: nil, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://example.com"}, + }, + { + Name: "provider-no-override-specify-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: nil, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://example.com"}, + }, + { + Name: "provider-with-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{WebhookURL: "http://example01.com"}, + }, + }, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://example.com"}, + }, + { + Name: "provider-with-override-specify-group-should-override", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{WebhookURL: "http://group-example.com"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{WebhookURL: "http://group-example.com"}, + }, + { + Name: "provider-with-group-override-and-alert-override--alert-override-should-take-precedence", + Provider: AlertProvider{ + DefaultConfig: Config{WebhookURL: "http://example.com"}, + Overrides: []Override{ + { + Group: "group", + Config: Config{WebhookURL: "http://group-example.com"}, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{ProviderOverride: map[string]any{"webhook-url": "http://alert-example.com"}}, + ExpectedOutput: Config{WebhookURL: "http://alert-example.com"}, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + got, err := scenario.Provider.GetConfig(scenario.InputGroup, &scenario.InputAlert) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if got.WebhookURL != scenario.ExpectedOutput.WebhookURL { + t.Errorf("expected webhook URL to be %s, got %s", scenario.ExpectedOutput.WebhookURL, got.WebhookURL) + } + // Test ValidateOverrides as well, since it really just calls GetConfig + if err = scenario.Provider.ValidateOverrides(scenario.InputGroup, &scenario.InputAlert); err != nil { + t.Errorf("unexpected error: %s", err) + } + }) + } +} diff --git a/alerting/provider/telegram/telegram.go b/alerting/provider/telegram/telegram.go new file mode 100644 index 00000000..b2624c7c --- /dev/null +++ b/alerting/provider/telegram/telegram.go @@ -0,0 +1,191 @@ +package telegram + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "gopkg.in/yaml.v3" +) + +const defaultApiUrl = "https://api.telegram.org" + +var ( + ErrTokenNotSet = errors.New("token not set") + ErrIDNotSet = errors.New("id not set") + ErrDuplicateGroupOverride = errors.New("duplicate group override") +) + +type Config struct { + Token string `yaml:"token"` + ID string `yaml:"id"` + ApiUrl string `yaml:"api-url"` + + ClientConfig *client.Config `yaml:"client,omitempty"` +} + +func (cfg *Config) Validate() error { + if len(cfg.ApiUrl) == 0 { + cfg.ApiUrl = defaultApiUrl + } + if len(cfg.Token) == 0 { + return ErrTokenNotSet + } + if len(cfg.ID) == 0 { + return ErrIDNotSet + } + return nil +} + +func (cfg *Config) Merge(override *Config) { + if override.ClientConfig != nil { + cfg.ClientConfig = override.ClientConfig + } + if len(override.Token) > 0 { + cfg.Token = override.Token + } + if len(override.ID) > 0 { + cfg.ID = override.ID + } + if len(override.ApiUrl) > 0 { + cfg.ApiUrl = override.ApiUrl + } +} + +// AlertProvider is the configuration necessary for sending an alert using Telegram +type AlertProvider struct { + DefaultConfig Config `yaml:",inline"` + + // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type + DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` + + // Overrides is a list of overrides that may be prioritized over the default configuration + Overrides []*Override `yaml:"overrides,omitempty"` +} + +// Override is a configuration that may be prioritized over the default configuration +type Override struct { + Group string `yaml:"group"` + Config `yaml:",inline"` +} + +// Validate the provider's configuration +func (provider *AlertProvider) Validate() error { + registeredGroups := make(map[string]bool) + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if isAlreadyRegistered := registeredGroups[override.Group]; isAlreadyRegistered || override.Group == "" { + return ErrDuplicateGroupOverride + } + registeredGroups[override.Group] = true + } + } + return provider.DefaultConfig.Validate() +} + +// Send an alert using the provider +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + cfg, err := provider.GetConfig(ep.Group, alert) + if err != nil { + return err + } + buffer := bytes.NewBuffer(provider.buildRequestBody(cfg, ep, alert, result, resolved)) + request, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/bot%s/sendMessage", cfg.ApiUrl, cfg.Token), buffer) + if err != nil { + return err + } + request.Header.Set("Content-Type", "application/json") + response, err := client.GetHTTPClient(cfg.ClientConfig).Do(request) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode > 399 { + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body)) + } + return err +} + +type Body struct { + ChatID string `json:"chat_id"` + Text string `json:"text"` + ParseMode string `json:"parse_mode"` +} + +// buildRequestBody builds the request body for the provider +func (provider *AlertProvider) buildRequestBody(cfg *Config, ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte { + var message string + if resolved { + message = fmt.Sprintf("An alert for *%s* has been resolved:\n—\n _healthcheck passing successfully %d time(s) in a row_\n— ", ep.DisplayName(), alert.SuccessThreshold) + } else { + message = fmt.Sprintf("An alert for *%s* has been triggered:\n—\n _healthcheck failed %d time(s) in a row_\n— ", ep.DisplayName(), alert.FailureThreshold) + } + var formattedConditionResults string + if len(result.ConditionResults) > 0 { + formattedConditionResults = "\n*Condition results*\n" + for _, conditionResult := range result.ConditionResults { + var prefix string + if conditionResult.Success { + prefix = "✅" + } else { + prefix = "❌" + } + formattedConditionResults += fmt.Sprintf("%s - `%s`\n", prefix, conditionResult.Condition) + } + } + var text string + if len(alert.GetDescription()) > 0 { + text = fmt.Sprintf("⛑ *Gatus* \n%s \n*Description* \n_%s_ \n%s", message, alert.GetDescription(), formattedConditionResults) + } else { + text = fmt.Sprintf("⛑ *Gatus* \n%s%s", message, formattedConditionResults) + } + bodyAsJSON, _ := json.Marshal(Body{ + ChatID: cfg.ID, + Text: text, + ParseMode: "MARKDOWN", + }) + return bodyAsJSON +} + +// GetDefaultAlert returns the provider's default alert configuration +func (provider *AlertProvider) GetDefaultAlert() *alert.Alert { + return provider.DefaultAlert +} + +// GetConfig returns the configuration for the provider with the overrides applied +func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) { + cfg := provider.DefaultConfig + // Handle group overrides + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if group == override.Group { + cfg.Merge(&override.Config) + break + } + } + } + // Handle alert overrides + if len(alert.ProviderOverride) != 0 { + overrideConfig := Config{} + if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil { + return nil, err + } + cfg.Merge(&overrideConfig) + } + // Validate the configuration + err := cfg.Validate() + return &cfg, err +} + +// ValidateOverrides validates the alert's provider override and, if present, the group override +func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error { + _, err := provider.GetConfig(group, alert) + return err +} diff --git a/alerting/provider/telegram/telegram_test.go b/alerting/provider/telegram/telegram_test.go new file mode 100644 index 00000000..2d4fd6ab --- /dev/null +++ b/alerting/provider/telegram/telegram_test.go @@ -0,0 +1,252 @@ +package telegram + +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/test" +) + +func TestAlertProvider_Validate(t *testing.T) { + t.Run("invalid-provider", func(t *testing.T) { + invalidProvider := AlertProvider{DefaultConfig: Config{Token: "", ID: ""}} + if err := invalidProvider.Validate(); err == nil { + t.Error("provider shouldn't have been valid") + } + }) + t.Run("valid-provider", func(t *testing.T) { + validProvider := AlertProvider{DefaultConfig: Config{Token: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", ID: "12345678"}} + if err := validProvider.Validate(); err != nil { + t.Error("provider should've been valid") + } + }) + t.Run("invalid-provider-override-nonexist-group", func(t *testing.T) { + invalidProvider := AlertProvider{DefaultConfig: Config{Token: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", ID: "12345678"}, Overrides: []*Override{{Config: Config{Token: "token", ID: "id"}}}} + if err := invalidProvider.Validate(); err == nil { + t.Error("provider shouldn't have been valid") + } + }) + t.Run("invalid-provider-override-duplicate-group", func(t *testing.T) { + invalidProvider := AlertProvider{DefaultConfig: Config{Token: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", ID: "12345678"}, Overrides: []*Override{{Group: "group1", Config: Config{Token: "token", ID: "id"}}, {Group: "group1", Config: Config{ID: "id2"}}}} + if err := invalidProvider.Validate(); err == nil { + t.Error("provider shouldn't have been valid") + } + }) + t.Run("valid-provider-with-overrides", func(t *testing.T) { + validProvider := AlertProvider{DefaultConfig: Config{Token: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", ID: "12345678"}, Overrides: []*Override{{Group: "group", Config: Config{Token: "token", ID: "id"}}}} + if err := validProvider.Validate(); err != nil { + t.Error("provider should've been valid") + } + }) +} + +func TestAlertProvider_Send(t *testing.T) { + defer client.InjectHTTPClient(nil) + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + MockRoundTripper test.MockRoundTripper + ExpectedError bool + }{ + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{ID: "123", Token: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "triggered-error", + Provider: AlertProvider{DefaultConfig: Config{ID: "123", Token: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + { + Name: "resolved", + Provider: AlertProvider{DefaultConfig: Config{ID: "123", Token: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "resolved-error", + Provider: AlertProvider{DefaultConfig: Config{ID: "123", Token: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) + err := scenario.Provider.Send( + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if scenario.ExpectedError && err == nil { + t.Error("expected error, got none") + } + if !scenario.ExpectedError && err != nil { + t.Error("expected no error, got", err.Error()) + } + }) + } +} + +func TestAlertProvider_buildRequestBody(t *testing.T) { + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + NoConditions bool + Resolved bool + ExpectedBody string + }{ + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{ID: "123"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: "{\"chat_id\":\"123\",\"text\":\"⛑ *Gatus* \\nAn alert for *endpoint-name* has been triggered:\\n—\\n _healthcheck failed 3 time(s) in a row_\\n— \\n*Description* \\n_description-1_ \\n\\n*Condition results*\\n❌ - `[CONNECTED] == true`\\n❌ - `[STATUS] == 200`\\n\",\"parse_mode\":\"MARKDOWN\"}", + }, + { + Name: "resolved", + Provider: AlertProvider{DefaultConfig: Config{ID: "123"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: "{\"chat_id\":\"123\",\"text\":\"⛑ *Gatus* \\nAn alert for *endpoint-name* has been resolved:\\n—\\n _healthcheck passing successfully 5 time(s) in a row_\\n— \\n*Description* \\n_description-2_ \\n\\n*Condition results*\\n✅ - `[CONNECTED] == true`\\n✅ - `[STATUS] == 200`\\n\",\"parse_mode\":\"MARKDOWN\"}", + }, + { + Name: "resolved-with-no-conditions", + NoConditions: true, + Provider: AlertProvider{DefaultConfig: Config{ID: "123"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: "{\"chat_id\":\"123\",\"text\":\"⛑ *Gatus* \\nAn alert for *endpoint-name* has been resolved:\\n—\\n _healthcheck passing successfully 5 time(s) in a row_\\n— \\n*Description* \\n_description-2_ \\n\",\"parse_mode\":\"MARKDOWN\"}", + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + var conditionResults []*endpoint.ConditionResult + if !scenario.NoConditions { + conditionResults = []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + } + } + body := scenario.Provider.buildRequestBody( + &scenario.Provider.DefaultConfig, + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ConditionResults: conditionResults}, + scenario.Resolved, + ) + if string(body) != scenario.ExpectedBody { + t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body) + } + out := make(map[string]interface{}) + if err := json.Unmarshal(body, &out); err != nil { + t.Error("expected body to be valid JSON, got error:", err.Error()) + } + }) + } +} + +func TestAlertProvider_GetDefaultAlert(t *testing.T) { + if (&AlertProvider{DefaultAlert: &alert.Alert{}}).GetDefaultAlert() == nil { + t.Error("expected default alert to be not nil") + } + if (&AlertProvider{DefaultAlert: nil}).GetDefaultAlert() != nil { + t.Error("expected default alert to be nil") + } +} + +func TestAlertProvider_GetConfig(t *testing.T) { + t.Run("get-token-with-override", func(t *testing.T) { + provider := AlertProvider{DefaultConfig: Config{Token: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", ID: "12345678"}, Overrides: []*Override{{Group: "group", Config: Config{Token: "groupToken", ID: "overrideID"}}}} + cfg, err := provider.GetConfig("group", &alert.Alert{}) + if err != nil { + t.Error("expected no error, got", err) + } + if cfg.Token != "groupToken" { + t.Error("token should have been 'groupToken'") + } + if cfg.ID != "overrideID" { + t.Error("id should have been 'overrideID'") + } + }) + t.Run("get-default-token-with-overridden-id", func(t *testing.T) { + provider := AlertProvider{DefaultConfig: Config{Token: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", ID: "12345678"}, Overrides: []*Override{{Group: "group", Config: Config{ID: "overrideID"}}}} + cfg, err := provider.GetConfig("group", &alert.Alert{}) + if err != nil { + t.Error("expected no error, got", err) + } + if cfg.Token != provider.DefaultConfig.Token { + t.Error("token should have been the default token") + } + if cfg.ID != "overrideID" { + t.Error("id should have been 'overrideID'") + } + }) + t.Run("get-default-token-with-overridden-token", func(t *testing.T) { + provider := AlertProvider{DefaultConfig: Config{Token: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", ID: "12345678"}, Overrides: []*Override{{Group: "group", Config: Config{Token: "groupToken"}}}} + cfg, err := provider.GetConfig("group", &alert.Alert{}) + if err != nil { + t.Error("expected no error, got", err) + } + if cfg.Token != "groupToken" { + t.Error("token should have been 'groupToken'") + } + if cfg.ID != provider.DefaultConfig.ID { + t.Error("id should have been the default id") + } + }) + t.Run("get-default-token-with-overridden-token-and-alert-token-override", func(t *testing.T) { + provider := AlertProvider{DefaultConfig: Config{Token: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", ID: "12345678"}, Overrides: []*Override{{Group: "group", Config: Config{Token: "groupToken"}}}} + alert := &alert.Alert{ProviderOverride: map[string]any{"token": "alertToken"}} + cfg, err := provider.GetConfig("group", alert) + if err != nil { + t.Error("expected no error, got", err) + } + if cfg.Token != "alertToken" { + t.Error("token should have been 'alertToken'") + } + if cfg.ID != provider.DefaultConfig.ID { + t.Error("id should have been the default id") + } + // Test ValidateOverrides as well, since it really just calls GetConfig + if err = provider.ValidateOverrides("group", alert); err != nil { + t.Errorf("unexpected error: %s", err) + } + }) +} diff --git a/alerting/provider/twilio/twilio.go b/alerting/provider/twilio/twilio.go new file mode 100644 index 00000000..544397fb --- /dev/null +++ b/alerting/provider/twilio/twilio.go @@ -0,0 +1,141 @@ +package twilio + +import ( + "bytes" + "encoding/base64" + "errors" + "fmt" + "io" + "net/http" + "net/url" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "gopkg.in/yaml.v3" +) + +var ( + ErrSIDNotSet = errors.New("sid not set") + ErrTokenNotSet = errors.New("token not set") + ErrFromNotSet = errors.New("from not set") + ErrToNotSet = errors.New("to not set") +) + +type Config struct { + SID string `yaml:"sid"` + Token string `yaml:"token"` + From string `yaml:"from"` + To string `yaml:"to"` +} + +func (cfg *Config) Validate() error { + if len(cfg.SID) == 0 { + return ErrSIDNotSet + } + if len(cfg.Token) == 0 { + return ErrTokenNotSet + } + if len(cfg.From) == 0 { + return ErrFromNotSet + } + if len(cfg.To) == 0 { + return ErrToNotSet + } + return nil +} + +func (cfg *Config) Merge(override *Config) { + if len(override.SID) > 0 { + cfg.SID = override.SID + } + if len(override.Token) > 0 { + cfg.Token = override.Token + } + if len(override.From) > 0 { + cfg.From = override.From + } + if len(override.To) > 0 { + cfg.To = override.To + } +} + +// AlertProvider is the configuration necessary for sending an alert using Twilio +type AlertProvider struct { + DefaultConfig Config `yaml:",inline"` + + // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type + DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` +} + +// Validate the provider's configuration +func (provider *AlertProvider) Validate() error { + return provider.DefaultConfig.Validate() +} + +// Send an alert using the provider +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + cfg, err := provider.GetConfig(ep.Group, alert) + if err != nil { + return err + } + buffer := bytes.NewBuffer([]byte(provider.buildRequestBody(cfg, ep, alert, result, resolved))) + request, err := http.NewRequest(http.MethodPost, fmt.Sprintf("https://api.twilio.com/2010-04-01/Accounts/%s/Messages.json", cfg.SID), buffer) + if err != nil { + return err + } + request.Header.Set("Content-Type", "application/x-www-form-urlencoded") + request.Header.Set("Authorization", fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(cfg.SID+":"+cfg.Token)))) + response, err := client.GetHTTPClient(nil).Do(request) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode > 399 { + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body)) + } + return err +} + +// buildRequestBody builds the request body for the provider +func (provider *AlertProvider) buildRequestBody(cfg *Config, ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) string { + var message string + if resolved { + message = fmt.Sprintf("RESOLVED: %s - %s", ep.DisplayName(), alert.GetDescription()) + } else { + message = fmt.Sprintf("TRIGGERED: %s - %s", ep.DisplayName(), alert.GetDescription()) + } + return url.Values{ + "To": {cfg.To}, + "From": {cfg.From}, + "Body": {message}, + }.Encode() +} + +// GetDefaultAlert returns the provider's default alert configuration +func (provider *AlertProvider) GetDefaultAlert() *alert.Alert { + return provider.DefaultAlert +} + +// GetConfig returns the configuration for the provider with the overrides applied +func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) { + cfg := provider.DefaultConfig + // Handle alert overrides + if len(alert.ProviderOverride) != 0 { + overrideConfig := Config{} + if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil { + return nil, err + } + cfg.Merge(&overrideConfig) + } + // Validate the configuration + err := cfg.Validate() + return &cfg, err +} + +// ValidateOverrides validates the alert's provider override and, if present, the group override +func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error { + _, err := provider.GetConfig(group, alert) + return err +} diff --git a/alerting/provider/twilio/twilio_test.go b/alerting/provider/twilio/twilio_test.go new file mode 100644 index 00000000..f31f6349 --- /dev/null +++ b/alerting/provider/twilio/twilio_test.go @@ -0,0 +1,211 @@ +package twilio + +import ( + "net/http" + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/test" +) + +func TestTwilioAlertProvider_IsValid(t *testing.T) { + invalidProvider := AlertProvider{} + if err := invalidProvider.Validate(); err == nil { + t.Error("provider shouldn't have been valid") + } + validProvider := AlertProvider{ + DefaultConfig: Config{ + SID: "1", + Token: "1", + From: "1", + To: "1", + }, + } + if err := validProvider.Validate(); err != nil { + t.Error("provider should've been valid") + } +} + +func TestAlertProvider_Send(t *testing.T) { + defer client.InjectHTTPClient(nil) + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + MockRoundTripper test.MockRoundTripper + ExpectedError bool + }{ + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{SID: "1", Token: "2", From: "3", To: "4"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "triggered-error", + Provider: AlertProvider{DefaultConfig: Config{SID: "1", Token: "2", From: "3", To: "4"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + { + Name: "resolved", + Provider: AlertProvider{DefaultConfig: Config{SID: "1", Token: "2", From: "3", To: "4"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + ExpectedError: false, + }, + { + Name: "resolved-error", + Provider: AlertProvider{DefaultConfig: Config{SID: "1", Token: "2", From: "3", To: "4"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} + }), + ExpectedError: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) + err := scenario.Provider.Send( + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if scenario.ExpectedError && err == nil { + t.Error("expected error, got none") + } + if !scenario.ExpectedError && err != nil { + t.Error("expected no error, got", err.Error()) + } + }) + } +} + +func TestAlertProvider_buildRequestBody(t *testing.T) { + firstDescription := "description-1" + secondDescription := "description-2" + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + ExpectedBody string + }{ + { + Name: "triggered", + Provider: AlertProvider{DefaultConfig: Config{SID: "1", Token: "2", From: "3", To: "4"}}, + Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: "Body=TRIGGERED%3A+endpoint-name+-+description-1&From=3&To=4", + }, + { + Name: "resolved", + Provider: AlertProvider{DefaultConfig: Config{SID: "1", Token: "2", From: "3", To: "4"}}, + Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: "Body=RESOLVED%3A+endpoint-name+-+description-2&From=3&To=4", + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + body := scenario.Provider.buildRequestBody( + &scenario.Provider.DefaultConfig, + &endpoint.Endpoint{Name: "endpoint-name"}, + &scenario.Alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if body != scenario.ExpectedBody { + t.Errorf("expected %s, got %s", scenario.ExpectedBody, body) + } + }) + } +} + +func TestAlertProvider_GetDefaultAlert(t *testing.T) { + if (&AlertProvider{DefaultAlert: &alert.Alert{}}).GetDefaultAlert() == nil { + t.Error("expected default alert to be not nil") + } + if (&AlertProvider{DefaultAlert: nil}).GetDefaultAlert() != nil { + t.Error("expected default alert to be nil") + } +} + +func TestAlertProvider_GetConfig(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + InputAlert alert.Alert + ExpectedOutput Config + }{ + { + Name: "provider-no-override-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{SID: "1", Token: "2", From: "3", To: "4"}, + }, + InputAlert: alert.Alert{}, + ExpectedOutput: Config{SID: "1", Token: "2", From: "3", To: "4"}, + }, + { + Name: "provider-with-alert-override", + Provider: AlertProvider{ + DefaultConfig: Config{SID: "1", Token: "2", From: "3", To: "4"}, + }, + InputAlert: alert.Alert{ProviderOverride: map[string]any{"sid": "5", "token": "6", "from": "7", "to": "8"}}, + ExpectedOutput: Config{SID: "5", Token: "6", From: "7", To: "8"}, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + got, err := scenario.Provider.GetConfig("", &scenario.InputAlert) + if err != nil { + t.Error("expected no error, got:", err.Error()) + } + if got.SID != scenario.ExpectedOutput.SID { + t.Errorf("expected SID to be %s, got %s", scenario.ExpectedOutput.SID, got.SID) + } + if got.Token != scenario.ExpectedOutput.Token { + t.Errorf("expected token to be %s, got %s", scenario.ExpectedOutput.Token, got.Token) + } + if got.From != scenario.ExpectedOutput.From { + t.Errorf("expected from to be %s, got %s", scenario.ExpectedOutput.From, got.From) + } + if got.To != scenario.ExpectedOutput.To { + t.Errorf("expected to to be %s, got %s", scenario.ExpectedOutput.To, got.To) + } + // Test ValidateOverrides as well, since it really just calls GetConfig + if err = scenario.Provider.ValidateOverrides("", &scenario.InputAlert); err != nil { + t.Errorf("unexpected error: %s", err) + } + }) + } +} diff --git a/alerting/provider/zulip/zulip.go b/alerting/provider/zulip/zulip.go new file mode 100644 index 00000000..9160cf9d --- /dev/null +++ b/alerting/provider/zulip/zulip.go @@ -0,0 +1,183 @@ +package zulip + +import ( + "bytes" + "errors" + "fmt" + "io" + "net/http" + "net/url" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "gopkg.in/yaml.v3" +) + +var ( + ErrBotEmailNotSet = errors.New("bot-email not set") + ErrBotAPIKeyNotSet = errors.New("bot-api-key not set") + ErrDomainNotSet = errors.New("domain not set") + ErrChannelIDNotSet = errors.New("channel-id not set") + ErrDuplicateGroupOverride = errors.New("duplicate group override") +) + +type Config struct { + BotEmail string `yaml:"bot-email"` // Email of the bot user + BotAPIKey string `yaml:"bot-api-key"` // API key of the bot user + Domain string `yaml:"domain"` // Domain of the Zulip server + ChannelID string `yaml:"channel-id"` // ID of the channel to send the message to +} + +func (cfg *Config) Validate() error { + if len(cfg.BotEmail) == 0 { + return ErrBotEmailNotSet + } + if len(cfg.BotAPIKey) == 0 { + return ErrBotAPIKeyNotSet + } + if len(cfg.Domain) == 0 { + return ErrDomainNotSet + } + if len(cfg.ChannelID) == 0 { + return ErrChannelIDNotSet + } + return nil +} + +func (cfg *Config) Merge(override *Config) { + if len(override.BotEmail) > 0 { + cfg.BotEmail = override.BotEmail + } + if len(override.BotAPIKey) > 0 { + cfg.BotAPIKey = override.BotAPIKey + } + if len(override.Domain) > 0 { + cfg.Domain = override.Domain + } + if len(override.ChannelID) > 0 { + cfg.ChannelID = override.ChannelID + } +} + +// AlertProvider is the configuration necessary for sending an alert using Zulip +type AlertProvider struct { + DefaultConfig Config `yaml:",inline"` + + // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type + DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` + + // Overrides is a list of Override that may be prioritized over the default configuration + Overrides []Override `yaml:"overrides,omitempty"` +} + +// Override is a case under which the default integration is overridden +type Override struct { + Group string `yaml:"group"` + Config `yaml:",inline"` +} + +// Validate the provider's configuration +func (provider *AlertProvider) Validate() error { + registeredGroups := make(map[string]bool) + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if isAlreadyRegistered := registeredGroups[override.Group]; isAlreadyRegistered || override.Group == "" { + return ErrDuplicateGroupOverride + } + registeredGroups[override.Group] = true + } + } + return provider.DefaultConfig.Validate() +} + +// Send an alert using the provider +func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error { + cfg, err := provider.GetConfig(ep.Group, alert) + if err != nil { + return err + } + buffer := bytes.NewBufferString(provider.buildRequestBody(cfg, ep, alert, result, resolved)) + zulipEndpoint := fmt.Sprintf("https://%s/api/v1/messages", cfg.Domain) + request, err := http.NewRequest(http.MethodPost, zulipEndpoint, buffer) + if err != nil { + return err + } + request.SetBasicAuth(cfg.BotEmail, cfg.BotAPIKey) + request.Header.Set("Content-Type", "application/x-www-form-urlencoded") + request.Header.Set("User-Agent", "Gatus") + response, err := client.GetHTTPClient(nil).Do(request) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode > 399 { + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body)) + } + return nil +} + +// buildRequestBody builds the request body for the provider +func (provider *AlertProvider) buildRequestBody(cfg *Config, ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) string { + var message string + if resolved { + message = fmt.Sprintf("An alert for **%s** has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold) + } else { + message = fmt.Sprintf("An alert for **%s** has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold) + } + if alertDescription := alert.GetDescription(); len(alertDescription) > 0 { + message += "\n> " + alertDescription + "\n" + } + for _, conditionResult := range result.ConditionResults { + var prefix string + if conditionResult.Success { + prefix = ":check:" + } else { + prefix = ":cross_mark:" + } + message += fmt.Sprintf("\n%s - `%s`", prefix, conditionResult.Condition) + } + return url.Values{ + "type": {"channel"}, + "to": {cfg.ChannelID}, + "topic": {"Gatus"}, + "content": {message}, + }.Encode() +} + +// GetDefaultAlert returns the provider's default alert configuration +func (provider *AlertProvider) GetDefaultAlert() *alert.Alert { + return provider.DefaultAlert +} + +// GetConfig returns the configuration for the provider with the overrides applied +func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) { + cfg := provider.DefaultConfig + // Handle group overrides + if provider.Overrides != nil { + for _, override := range provider.Overrides { + if group == override.Group { + cfg.Merge(&override.Config) + break + } + } + } + // Handle alert overrides + if len(alert.ProviderOverride) != 0 { + overrideConfig := Config{} + if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil { + return nil, err + } + cfg.Merge(&overrideConfig) + } + // Validate the configuration + err := cfg.Validate() + return &cfg, err +} + +// ValidateOverrides validates the alert's provider override and, if present, the group override +func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error { + _, err := provider.GetConfig(group, alert) + return err +} diff --git a/alerting/provider/zulip/zulip_test.go b/alerting/provider/zulip/zulip_test.go new file mode 100644 index 00000000..8d9c34f3 --- /dev/null +++ b/alerting/provider/zulip/zulip_test.go @@ -0,0 +1,482 @@ +package zulip + +import ( + "errors" + "fmt" + "net/http" + "net/url" + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/test" +) + +func TestAlertProvider_Validate(t *testing.T) { + scenarios := []struct { + Name string + AlertProvider AlertProvider + ExpectedError error + }{ + { + Name: "Empty provider", + AlertProvider: AlertProvider{}, + ExpectedError: ErrBotEmailNotSet, + }, + { + Name: "Empty channel id", + AlertProvider: AlertProvider{ + DefaultConfig: Config{ + BotEmail: "something", + BotAPIKey: "something", + Domain: "something", + }, + }, + ExpectedError: ErrChannelIDNotSet, + }, + { + Name: "Empty domain", + AlertProvider: AlertProvider{ + DefaultConfig: Config{ + BotEmail: "something", + BotAPIKey: "something", + ChannelID: "something", + }, + }, + ExpectedError: ErrDomainNotSet, + }, + { + Name: "Empty bot api key", + AlertProvider: AlertProvider{ + DefaultConfig: Config{ + BotEmail: "something", + Domain: "something", + ChannelID: "something", + }, + }, + ExpectedError: ErrBotAPIKeyNotSet, + }, + { + Name: "Empty bot email", + AlertProvider: AlertProvider{ + DefaultConfig: Config{ + BotAPIKey: "something", + Domain: "something", + ChannelID: "something", + }, + }, + ExpectedError: ErrBotEmailNotSet, + }, + { + Name: "Valid provider", + AlertProvider: AlertProvider{ + DefaultConfig: Config{ + BotEmail: "something", + BotAPIKey: "something", + Domain: "something", + ChannelID: "something", + }, + }, + ExpectedError: nil, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + if err := scenario.AlertProvider.Validate(); !errors.Is(err, scenario.ExpectedError) { + t.Errorf("ExpectedError error %v, got %v", scenario.ExpectedError, err) + } + }) + } +} + +func TestAlertProvider_buildRequestBody(t *testing.T) { + basicConfig := Config{ + BotEmail: "bot-email", + BotAPIKey: "bot-api-key", + Domain: "domain", + ChannelID: "channel-id", + } + alertDesc := "Description" + basicAlert := alert.Alert{ + SuccessThreshold: 2, + FailureThreshold: 3, + Description: &alertDesc, + } + testCases := []struct { + name string + provider AlertProvider + alert alert.Alert + resolved bool + hasConditions bool + expectedBody url.Values + }{ + { + name: "Resolved alert with no conditions", + provider: AlertProvider{ + DefaultConfig: basicConfig, + }, + alert: basicAlert, + resolved: true, + hasConditions: false, + expectedBody: url.Values{ + "content": {`An alert for **endpoint-Name** has been resolved after passing successfully 2 time(s) in a row +> Description +`}, + "to": {"channel-id"}, + "topic": {"Gatus"}, + "type": {"channel"}, + }, + }, + { + name: "Resolved alert with conditions", + provider: AlertProvider{ + DefaultConfig: basicConfig, + }, + alert: basicAlert, + resolved: true, + hasConditions: true, + expectedBody: url.Values{ + "content": {`An alert for **endpoint-Name** has been resolved after passing successfully 2 time(s) in a row +> Description + +:check: - ` + "`[CONNECTED] == true`" + ` +:check: - ` + "`[STATUS] == 200`" + ` +:check: - ` + "`[BODY] != \"\"`"}, + "to": {"channel-id"}, + "topic": {"Gatus"}, + "type": {"channel"}, + }, + }, + { + name: "Failed alert with no conditions", + provider: AlertProvider{ + DefaultConfig: basicConfig, + }, + alert: basicAlert, + resolved: false, + hasConditions: false, + expectedBody: url.Values{ + "content": {`An alert for **endpoint-Name** has been triggered due to having failed 3 time(s) in a row +> Description +`}, + "to": {"channel-id"}, + "topic": {"Gatus"}, + "type": {"channel"}, + }, + }, + { + name: "Failed alert with conditions", + provider: AlertProvider{ + DefaultConfig: basicConfig, + }, + alert: basicAlert, + resolved: false, + hasConditions: true, + expectedBody: url.Values{ + "content": {`An alert for **endpoint-Name** has been triggered due to having failed 3 time(s) in a row +> Description + +:cross_mark: - ` + "`[CONNECTED] == true`" + ` +:cross_mark: - ` + "`[STATUS] == 200`" + ` +:cross_mark: - ` + "`[BODY] != \"\"`"}, + "to": {"channel-id"}, + "topic": {"Gatus"}, + "type": {"channel"}, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var conditionResults []*endpoint.ConditionResult + if tc.hasConditions { + conditionResults = []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: tc.resolved}, + {Condition: "[STATUS] == 200", Success: tc.resolved}, + {Condition: "[BODY] != \"\"", Success: tc.resolved}, + } + } + body := tc.provider.buildRequestBody( + &tc.provider.DefaultConfig, + &endpoint.Endpoint{Name: "endpoint-Name"}, + &tc.alert, + &endpoint.Result{ + ConditionResults: conditionResults, + }, + tc.resolved, + ) + valuesResult, err := url.ParseQuery(body) + if err != nil { + t.Error(err) + } + if fmt.Sprintf("%v", valuesResult) != fmt.Sprintf("%v", tc.expectedBody) { + t.Errorf("Expected body:\n%v\ngot:\n%v", tc.expectedBody, valuesResult) + } + }) + } +} + +func TestAlertProvider_GetDefaultAlert(t *testing.T) { + if (&AlertProvider{DefaultAlert: &alert.Alert{}}).GetDefaultAlert() == nil { + t.Error("ExpectedError default alert to be not nil") + } + if (&AlertProvider{DefaultAlert: nil}).GetDefaultAlert() != nil { + t.Error("ExpectedError default alert to be nil") + } +} + +func TestAlertProvider_Send(t *testing.T) { + defer client.InjectHTTPClient(nil) + validateRequest := func(req *http.Request) { + if req.URL.String() != "https://custom-domain/api/v1/messages" { + t.Errorf("ExpectedError url https://custom-domain.zulipchat.com/api/v1/messages, got %s", req.URL.String()) + } + if req.Method != http.MethodPost { + t.Errorf("ExpectedError POST request, got %s", req.Method) + } + if req.Header.Get("Content-Type") != "application/x-www-form-urlencoded" { + t.Errorf("ExpectedError Content-Type header to be application/x-www-form-urlencoded, got %s", req.Header.Get("Content-Type")) + } + if req.Header.Get("User-Agent") != "Gatus" { + t.Errorf("ExpectedError User-Agent header to be Gatus, got %s", req.Header.Get("User-Agent")) + } + } + basicConfig := Config{ + BotEmail: "bot-email", + BotAPIKey: "bot-api-key", + Domain: "custom-domain", + ChannelID: "channel-id", + } + basicAlert := alert.Alert{ + SuccessThreshold: 2, + FailureThreshold: 3, + } + testCases := []struct { + name string + provider AlertProvider + alert alert.Alert + resolved bool + mockRoundTripper test.MockRoundTripper + expectedError bool + }{ + { + name: "resolved", + provider: AlertProvider{ + DefaultConfig: basicConfig, + }, + alert: basicAlert, + resolved: true, + mockRoundTripper: test.MockRoundTripper(func(req *http.Request) *http.Response { + validateRequest(req) + return &http.Response{StatusCode: http.StatusOK} + }), + expectedError: false, + }, + { + name: "resolved error", + provider: AlertProvider{ + DefaultConfig: basicConfig, + }, + alert: basicAlert, + resolved: true, + mockRoundTripper: test.MockRoundTripper(func(req *http.Request) *http.Response { + validateRequest(req) + return &http.Response{StatusCode: http.StatusInternalServerError} + }), + expectedError: true, + }, + { + name: "triggered", + provider: AlertProvider{ + DefaultConfig: basicConfig, + }, + alert: basicAlert, + resolved: false, + mockRoundTripper: test.MockRoundTripper(func(req *http.Request) *http.Response { + validateRequest(req) + return &http.Response{StatusCode: http.StatusOK} + }), + expectedError: false, + }, + { + name: "triggered error", + provider: AlertProvider{ + DefaultConfig: basicConfig, + }, + alert: basicAlert, + resolved: false, + mockRoundTripper: test.MockRoundTripper(func(req *http.Request) *http.Response { + validateRequest(req) + return &http.Response{StatusCode: http.StatusInternalServerError} + }), + expectedError: true, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + client.InjectHTTPClient(&http.Client{Transport: tc.mockRoundTripper}) + err := tc.provider.Send( + &endpoint.Endpoint{Name: "endpoint-Name"}, + &tc.alert, + &endpoint.Result{ + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: tc.resolved}, + {Condition: "[STATUS] == 200", Success: tc.resolved}, + }, + }, + tc.resolved, + ) + if tc.expectedError && err == nil { + t.Error("ExpectedError error, got none") + } + if !tc.expectedError && err != nil { + t.Errorf("ExpectedError no error, got: %v", err) + } + }) + } +} + +func TestAlertProvider_GetConfig(t *testing.T) { + scenarios := []struct { + Name string + Provider AlertProvider + InputGroup string + InputAlert alert.Alert + ExpectedOutput Config + }{ + { + Name: "provider-no-overrides", + Provider: AlertProvider{ + DefaultConfig: Config{ + BotEmail: "default-bot-email", + BotAPIKey: "default-bot-api-key", + Domain: "default-domain", + ChannelID: "default-channel-id", + }, + Overrides: nil, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{ + BotEmail: "default-bot-email", + BotAPIKey: "default-bot-api-key", + Domain: "default-domain", + ChannelID: "default-channel-id", + }, + }, + { + Name: "provider-with-override-specify-no-group-should-default", + Provider: AlertProvider{ + DefaultConfig: Config{ + BotEmail: "default-bot-email", + BotAPIKey: "default-bot-api-key", + Domain: "default-domain", + ChannelID: "default-channel-id", + }, + Overrides: []Override{ + { + Group: "group", + Config: Config{ChannelID: "group-channel-id"}, + }, + }, + }, + InputGroup: "", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{ + BotEmail: "default-bot-email", + BotAPIKey: "default-bot-api-key", + Domain: "default-domain", + ChannelID: "default-channel-id", + }, + }, + { + Name: "provider-with-override-specify-group-should-override", + Provider: AlertProvider{ + DefaultConfig: Config{ + BotEmail: "default-bot-email", + BotAPIKey: "default-bot-api-key", + Domain: "default-domain", + ChannelID: "default-channel-id", + }, + Overrides: []Override{ + { + Group: "group", + Config: Config{ + BotEmail: "group-bot-email", + BotAPIKey: "group-bot-api-key", + Domain: "group-domain", + ChannelID: "group-channel-id", + }, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{}, + ExpectedOutput: Config{ + BotEmail: "group-bot-email", + BotAPIKey: "group-bot-api-key", + Domain: "group-domain", + ChannelID: "group-channel-id", + }, + }, + { + Name: "provider-with-group-override-and-alert-override--alert-override-should-take-precedence", + Provider: AlertProvider{ + DefaultConfig: Config{ + BotEmail: "default-bot-email", + BotAPIKey: "default-bot-api-key", + Domain: "default-domain", + ChannelID: "default-channel-id", + }, + Overrides: []Override{ + { + Group: "group", + Config: Config{ + BotEmail: "group-bot-email", + BotAPIKey: "group-bot-api-key", + Domain: "group-domain", + ChannelID: "group-channel-id", + }, + }, + }, + }, + InputGroup: "group", + InputAlert: alert.Alert{ProviderOverride: map[string]any{ + "bot-email": "alert-bot-email", + "bot-api-key": "alert-bot-api-key", + "domain": "alert-domain", + "channel-id": "alert-channel-id", + }}, + ExpectedOutput: Config{ + BotEmail: "alert-bot-email", + BotAPIKey: "alert-bot-api-key", + Domain: "alert-domain", + ChannelID: "alert-channel-id", + }, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + got, err := scenario.Provider.GetConfig(scenario.InputGroup, &scenario.InputAlert) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if got.BotEmail != scenario.ExpectedOutput.BotEmail { + t.Errorf("expected %s, got %s", scenario.ExpectedOutput.BotEmail, got.BotEmail) + } + if got.BotAPIKey != scenario.ExpectedOutput.BotAPIKey { + t.Errorf("expected %s, got %s", scenario.ExpectedOutput.BotAPIKey, got.BotAPIKey) + } + if got.Domain != scenario.ExpectedOutput.Domain { + t.Errorf("expected %s, got %s", scenario.ExpectedOutput.Domain, got.Domain) + } + if got.ChannelID != scenario.ExpectedOutput.ChannelID { + t.Errorf("expected %s, got %s", scenario.ExpectedOutput.ChannelID, got.ChannelID) + } + // Test ValidateOverrides as well, since it really just calls GetConfig + if err = scenario.Provider.ValidateOverrides(scenario.InputGroup, &scenario.InputAlert); err != nil { + t.Errorf("unexpected error: %s", err) + } + }) + } +} diff --git a/api/api.go b/api/api.go new file mode 100644 index 00000000..9ecc56f4 --- /dev/null +++ b/api/api.go @@ -0,0 +1,130 @@ +package api + +import ( + "io/fs" + "net/http" + "os" + + "github.com/TwiN/gatus/v5/config" + "github.com/TwiN/gatus/v5/config/ui" + "github.com/TwiN/gatus/v5/config/web" + static "github.com/TwiN/gatus/v5/web" + "github.com/TwiN/health" + "github.com/TwiN/logr" + fiber "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/adaptor" + "github.com/gofiber/fiber/v2/middleware/compress" + "github.com/gofiber/fiber/v2/middleware/cors" + fiberfs "github.com/gofiber/fiber/v2/middleware/filesystem" + "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/fiber/v2/middleware/redirect" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +type API struct { + router *fiber.App +} + +func New(cfg *config.Config) *API { + api := &API{} + if cfg.Web == nil { + logr.Warnf("[api.New] nil web config passed as parameter. This should only happen in tests. Using default web configuration") + cfg.Web = web.GetDefaultConfig() + } + if cfg.UI == nil { + logr.Warnf("[api.New] nil ui config passed as parameter. This should only happen in tests. Using default ui configuration") + cfg.UI = ui.GetDefaultConfig() + } + api.router = api.createRouter(cfg) + return api +} + +func (a *API) Router() *fiber.App { + return a.router +} + +func (a *API) createRouter(cfg *config.Config) *fiber.App { + app := fiber.New(fiber.Config{ + ErrorHandler: func(c *fiber.Ctx, err error) error { + logr.Errorf("[api.ErrorHandler] %s", err.Error()) + return fiber.DefaultErrorHandler(c, err) + }, + ReadBufferSize: cfg.Web.ReadBufferSize, + Network: fiber.NetworkTCP, + }) + if os.Getenv("ENVIRONMENT") == "dev" { + app.Use(cors.New(cors.Config{ + AllowOrigins: "http://localhost:8081", + AllowCredentials: true, + })) + } + // Middlewares + app.Use(recover.New()) + app.Use(compress.New()) + // Define metrics handler, if necessary + if cfg.Metrics { + metricsHandler := promhttp.InstrumentMetricHandler(prometheus.DefaultRegisterer, promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{ + DisableCompression: true, + })) + app.Get("/metrics", adaptor.HTTPHandler(metricsHandler)) + } + // Define main router + apiRouter := app.Group("/api") + //////////////////////// + // UNPROTECTED ROUTES // + //////////////////////// + unprotectedAPIRouter := apiRouter.Group("/") + unprotectedAPIRouter.Get("/v1/config", ConfigHandler{securityConfig: cfg.Security}.GetConfig) + unprotectedAPIRouter.Get("/v1/endpoints/:key/health/badge.svg", HealthBadge) + unprotectedAPIRouter.Get("/v1/endpoints/:key/health/badge.shields", HealthBadgeShields) + unprotectedAPIRouter.Get("/v1/endpoints/:key/uptimes/:duration", UptimeRaw) + unprotectedAPIRouter.Get("/v1/endpoints/:key/uptimes/:duration/badge.svg", UptimeBadge) + unprotectedAPIRouter.Get("/v1/endpoints/:key/response-times/:duration/badge.svg", ResponseTimeBadge(cfg)) + unprotectedAPIRouter.Get("/v1/endpoints/:key/response-times/:duration/chart.svg", ResponseTimeChart) + // This endpoint requires authz with bearer token, so technically it is protected + unprotectedAPIRouter.Post("/v1/endpoints/:key/external", CreateExternalEndpointResult(cfg)) + // SPA + app.Get("/", SinglePageApplication(cfg.UI)) + app.Get("/endpoints/:name", SinglePageApplication(cfg.UI)) + // Health endpoint + healthHandler := health.Handler().WithJSON(true) + app.Get("/health", func(c *fiber.Ctx) error { + statusCode, body := healthHandler.GetResponseStatusCodeAndBody() + return c.Status(statusCode).Send(body) + }) + // Custom CSS + app.Get("/css/custom.css", CustomCSSHandler{customCSS: cfg.UI.CustomCSS}.GetCustomCSS) + // Everything else falls back on static content + app.Use(redirect.New(redirect.Config{ + Rules: map[string]string{ + "/index.html": "/", + }, + StatusCode: 301, + })) + staticFileSystem, err := fs.Sub(static.FileSystem, static.RootPath) + if err != nil { + panic(err) + } + app.Use("/", fiberfs.New(fiberfs.Config{ + Root: http.FS(staticFileSystem), + Index: "index.html", + Browse: true, + })) + ////////////////////// + // PROTECTED ROUTES // + ////////////////////// + // ORDER IS IMPORTANT: all routes applied AFTER the security middleware will require authn + protectedAPIRouter := apiRouter.Group("/") + if cfg.Security != nil { + if err := cfg.Security.RegisterHandlers(app); err != nil { + panic(err) + } + if err := cfg.Security.ApplySecurityMiddleware(protectedAPIRouter); err != nil { + panic(err) + } + } + protectedAPIRouter.Get("/v1/endpoints/statuses", EndpointStatuses(cfg)) + protectedAPIRouter.Get("/v1/endpoints/:key/statuses", EndpointStatus) + return app +} diff --git a/api/api_test.go b/api/api_test.go new file mode 100644 index 00000000..713f6b7d --- /dev/null +++ b/api/api_test.go @@ -0,0 +1,132 @@ +package api + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/TwiN/gatus/v5/config" + "github.com/TwiN/gatus/v5/config/ui" + "github.com/TwiN/gatus/v5/security" + "github.com/gofiber/fiber/v2" +) + +func TestNew(t *testing.T) { + type Scenario struct { + Name string + Path string + ExpectedCode int + Gzip bool + WithSecurity bool + } + scenarios := []Scenario{ + { + Name: "health", + Path: "/health", + ExpectedCode: fiber.StatusOK, + }, + { + Name: "custom.css", + Path: "/css/custom.css", + ExpectedCode: fiber.StatusOK, + }, + { + Name: "custom.css-gzipped", + Path: "/css/custom.css", + ExpectedCode: fiber.StatusOK, + Gzip: true, + }, + { + Name: "metrics", + Path: "/metrics", + ExpectedCode: fiber.StatusOK, + }, + { + Name: "favicon.ico", + Path: "/favicon.ico", + ExpectedCode: fiber.StatusOK, + }, + { + Name: "app.js", + Path: "/js/app.js", + ExpectedCode: fiber.StatusOK, + }, + { + Name: "app.js-gzipped", + Path: "/js/app.js", + ExpectedCode: fiber.StatusOK, + Gzip: true, + }, + { + Name: "chunk-vendors.js", + Path: "/js/chunk-vendors.js", + ExpectedCode: fiber.StatusOK, + }, + { + Name: "chunk-vendors.js-gzipped", + Path: "/js/chunk-vendors.js", + ExpectedCode: fiber.StatusOK, + Gzip: true, + }, + { + Name: "index", + Path: "/", + ExpectedCode: fiber.StatusOK, + }, + { + Name: "index-html-redirect", + Path: "/index.html", + ExpectedCode: fiber.StatusMovedPermanently, + }, + { + Name: "index-should-return-200-even-if-not-authenticated", + Path: "/", + ExpectedCode: fiber.StatusOK, + WithSecurity: true, + }, + { + Name: "endpoints-should-return-401-if-not-authenticated", + Path: "/api/v1/endpoints/statuses", + ExpectedCode: fiber.StatusUnauthorized, + WithSecurity: true, + }, + { + Name: "config-should-return-200-even-if-not-authenticated", + Path: "/api/v1/config", + ExpectedCode: fiber.StatusOK, + WithSecurity: true, + }, + { + Name: "config-should-always-return-200", + Path: "/api/v1/config", + ExpectedCode: fiber.StatusOK, + WithSecurity: false, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + cfg := &config.Config{Metrics: true, UI: &ui.Config{}} + if scenario.WithSecurity { + cfg.Security = &security.Config{ + Basic: &security.BasicConfig{ + Username: "john.doe", + PasswordBcryptHashBase64Encoded: "JDJhJDA4JDFoRnpPY1hnaFl1OC9ISlFsa21VS09wOGlPU1ZOTDlHZG1qeTFvb3dIckRBUnlHUmNIRWlT", + }, + } + } + api := New(cfg) + router := api.Router() + request := httptest.NewRequest("GET", scenario.Path, http.NoBody) + if scenario.Gzip { + request.Header.Set("Accept-Encoding", "gzip") + } + response, err := router.Test(request) + if err != nil { + t.Fatal(err) + } + if response.StatusCode != scenario.ExpectedCode { + t.Errorf("%s %s should have returned %d, but returned %d instead", request.Method, request.URL, scenario.ExpectedCode, response.StatusCode) + } + }) + } +} diff --git a/api/badge.go b/api/badge.go new file mode 100644 index 00000000..e2ed3f46 --- /dev/null +++ b/api/badge.go @@ -0,0 +1,373 @@ +package api + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" + "time" + + "github.com/TwiN/gatus/v5/config" + "github.com/TwiN/gatus/v5/config/endpoint/ui" + "github.com/TwiN/gatus/v5/storage/store" + "github.com/TwiN/gatus/v5/storage/store/common" + "github.com/TwiN/gatus/v5/storage/store/common/paging" + "github.com/gofiber/fiber/v2" +) + +const ( + badgeColorHexAwesome = "#40cc11" + badgeColorHexGreat = "#94cc11" + badgeColorHexGood = "#ccd311" + badgeColorHexPassable = "#ccb311" + badgeColorHexBad = "#cc8111" + badgeColorHexVeryBad = "#c7130a" +) + +const ( + HealthStatusUp = "up" + HealthStatusDown = "down" + HealthStatusUnknown = "?" +) + +var ( + badgeColors = []string{badgeColorHexAwesome, badgeColorHexGreat, badgeColorHexGood, badgeColorHexPassable, badgeColorHexBad} +) + +// UptimeBadge handles the automatic generation of badge based on the group name and endpoint name passed. +// +// Valid values for :duration -> 30d, 7d, 24h, 1h +func UptimeBadge(c *fiber.Ctx) error { + duration := c.Params("duration") + var from time.Time + switch duration { + case "30d": + from = time.Now().Add(-30 * 24 * time.Hour) + case "7d": + from = time.Now().Add(-7 * 24 * time.Hour) + case "24h": + from = time.Now().Add(-24 * time.Hour) + case "1h": + from = time.Now().Add(-2 * time.Hour) // Because uptime metrics are stored by hour, we have to cheat a little + default: + return c.Status(400).SendString("Durations supported: 30d, 7d, 24h, 1h") + } + key := c.Params("key") + uptime, err := store.Get().GetUptimeByKey(key, from, time.Now()) + if err != nil { + if errors.Is(err, common.ErrEndpointNotFound) { + return c.Status(404).SendString(err.Error()) + } else if errors.Is(err, common.ErrInvalidTimeRange) { + return c.Status(400).SendString(err.Error()) + } + return c.Status(500).SendString(err.Error()) + } + c.Set("Content-Type", "image/svg+xml") + c.Set("Cache-Control", "no-cache, no-store, must-revalidate") + c.Set("Expires", "0") + return c.Status(200).Send(generateUptimeBadgeSVG(duration, uptime)) +} + +// ResponseTimeBadge handles the automatic generation of badge based on the group name and endpoint name passed. +// +// Valid values for :duration -> 30d, 7d, 24h, 1h +func ResponseTimeBadge(cfg *config.Config) fiber.Handler { + return func(c *fiber.Ctx) error { + duration := c.Params("duration") + var from time.Time + switch duration { + case "30d": + from = time.Now().Add(-30 * 24 * time.Hour) + case "7d": + from = time.Now().Add(-7 * 24 * time.Hour) + case "24h": + from = time.Now().Add(-24 * time.Hour) + case "1h": + from = time.Now().Add(-2 * time.Hour) // Because response time metrics are stored by hour, we have to cheat a little + default: + return c.Status(400).SendString("Durations supported: 30d, 7d, 24h, 1h") + } + key := c.Params("key") + averageResponseTime, err := store.Get().GetAverageResponseTimeByKey(key, from, time.Now()) + if err != nil { + if errors.Is(err, common.ErrEndpointNotFound) { + return c.Status(404).SendString(err.Error()) + } else if errors.Is(err, common.ErrInvalidTimeRange) { + return c.Status(400).SendString(err.Error()) + } + return c.Status(500).SendString(err.Error()) + } + c.Set("Content-Type", "image/svg+xml") + c.Set("Cache-Control", "no-cache, no-store, must-revalidate") + c.Set("Expires", "0") + return c.Status(200).Send(generateResponseTimeBadgeSVG(duration, averageResponseTime, key, cfg)) + } +} + +// HealthBadge handles the automatic generation of badge based on the group name and endpoint name passed. +func HealthBadge(c *fiber.Ctx) error { + key := c.Params("key") + pagingConfig := paging.NewEndpointStatusParams() + status, err := store.Get().GetEndpointStatusByKey(key, pagingConfig.WithResults(1, 1)) + if err != nil { + if errors.Is(err, common.ErrEndpointNotFound) { + return c.Status(404).SendString(err.Error()) + } else if errors.Is(err, common.ErrInvalidTimeRange) { + return c.Status(400).SendString(err.Error()) + } + return c.Status(500).SendString(err.Error()) + } + healthStatus := HealthStatusUnknown + if len(status.Results) > 0 { + if status.Results[0].Success { + healthStatus = HealthStatusUp + } else { + healthStatus = HealthStatusDown + } + } + c.Set("Content-Type", "image/svg+xml") + c.Set("Cache-Control", "no-cache, no-store, must-revalidate") + c.Set("Expires", "0") + return c.Status(200).Send(generateHealthBadgeSVG(healthStatus)) +} + +func HealthBadgeShields(c *fiber.Ctx) error { + key := c.Params("key") + pagingConfig := paging.NewEndpointStatusParams() + status, err := store.Get().GetEndpointStatusByKey(key, pagingConfig.WithResults(1, 1)) + if err != nil { + if errors.Is(err, common.ErrEndpointNotFound) { + return c.Status(404).SendString(err.Error()) + } else if errors.Is(err, common.ErrInvalidTimeRange) { + return c.Status(400).SendString(err.Error()) + } + return c.Status(500).SendString(err.Error()) + } + healthStatus := HealthStatusUnknown + if len(status.Results) > 0 { + if status.Results[0].Success { + healthStatus = HealthStatusUp + } else { + healthStatus = HealthStatusDown + } + } + c.Set("Content-Type", "application/json") + c.Set("Cache-Control", "no-cache, no-store, must-revalidate") + c.Set("Expires", "0") + jsonData, err := generateHealthBadgeShields(healthStatus) + if err != nil { + return c.Status(500).SendString(err.Error()) + } + return c.Status(200).Send(jsonData) +} + +func generateUptimeBadgeSVG(duration string, uptime float64) []byte { + var labelWidth, valueWidth, valueWidthAdjustment int + switch duration { + case "30d": + labelWidth = 70 + case "7d": + labelWidth = 65 + case "24h": + labelWidth = 70 + case "1h": + labelWidth = 65 + default: + } + color := getBadgeColorFromUptime(uptime) + sanitizedValue := strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.2f", uptime*100), "0"), ".") + "%" + if strings.Contains(sanitizedValue, ".") { + valueWidthAdjustment = -10 + } + valueWidth = (len(sanitizedValue) * 11) + valueWidthAdjustment + width := labelWidth + valueWidth + labelX := labelWidth / 2 + valueX := labelWidth + (valueWidth / 2) + svg := []byte(fmt.Sprintf(` + + + + + + + + + + + + + + + uptime %s + + + uptime %s + + + %s + + + %s + + +`, width, width, labelWidth, color, labelWidth, valueWidth, labelWidth, width, labelX, duration, labelX, duration, valueX, sanitizedValue, valueX, sanitizedValue)) + return svg +} + +func getBadgeColorFromUptime(uptime float64) string { + if uptime >= 0.975 { + return badgeColorHexAwesome + } else if uptime >= 0.95 { + return badgeColorHexGreat + } else if uptime >= 0.9 { + return badgeColorHexGood + } else if uptime >= 0.8 { + return badgeColorHexPassable + } else if uptime >= 0.65 { + return badgeColorHexBad + } + return badgeColorHexVeryBad +} + +func generateResponseTimeBadgeSVG(duration string, averageResponseTime int, key string, cfg *config.Config) []byte { + var labelWidth, valueWidth int + switch duration { + case "30d": + labelWidth = 110 + case "7d": + labelWidth = 105 + case "24h": + labelWidth = 110 + case "1h": + labelWidth = 105 + default: + } + color := getBadgeColorFromResponseTime(averageResponseTime, key, cfg) + sanitizedValue := strconv.Itoa(averageResponseTime) + "ms" + valueWidth = len(sanitizedValue) * 11 + width := labelWidth + valueWidth + labelX := labelWidth / 2 + valueX := labelWidth + (valueWidth / 2) + svg := []byte(fmt.Sprintf(` + + + + + + + + + + + + + + + response time %s + + + response time %s + + + %s + + + %s + + +`, width, width, labelWidth, color, labelWidth, valueWidth, labelWidth, width, labelX, duration, labelX, duration, valueX, sanitizedValue, valueX, sanitizedValue)) + return svg +} + +func getBadgeColorFromResponseTime(responseTime int, key string, cfg *config.Config) string { + thresholds := ui.GetDefaultConfig().Badge.ResponseTime.Thresholds + if endpoint := cfg.GetEndpointByKey(key); endpoint != nil { + thresholds = endpoint.UIConfig.Badge.ResponseTime.Thresholds + } + // the threshold config requires 5 values, so we can be sure it's set here + for i := 0; i < 5; i++ { + if responseTime <= thresholds[i] { + return badgeColors[i] + } + } + return badgeColorHexVeryBad +} + +func generateHealthBadgeSVG(healthStatus string) []byte { + var labelWidth, valueWidth int + switch healthStatus { + case HealthStatusUp: + valueWidth = 28 + case HealthStatusDown: + valueWidth = 44 + case HealthStatusUnknown: + valueWidth = 10 + default: + } + color := getBadgeColorFromHealth(healthStatus) + labelWidth = 48 + + width := labelWidth + valueWidth + labelX := labelWidth / 2 + valueX := labelWidth + (valueWidth / 2) + svg := []byte(fmt.Sprintf(` + + + + + + + + + + + + + + + health + + + health + + + %s + + + %s + + +`, width, width, labelWidth, color, labelWidth, valueWidth, labelWidth, width, labelX, labelX, valueX, healthStatus, valueX, healthStatus)) + + return svg +} + +func generateHealthBadgeShields(healthStatus string) ([]byte, error) { + color := getBadgeShieldsColorFromHealth(healthStatus) + data := map[string]interface{}{ + "schemaVersion": 1, + "label": "gatus", + "message": healthStatus, + "color": color, + } + return json.Marshal(data) +} + +func getBadgeColorFromHealth(healthStatus string) string { + if healthStatus == HealthStatusUp { + return badgeColorHexAwesome + } else if healthStatus == HealthStatusDown { + return badgeColorHexVeryBad + } + return badgeColorHexPassable +} + +func getBadgeShieldsColorFromHealth(healthStatus string) string { + if healthStatus == HealthStatusUp { + return "brightgreen" + } else if healthStatus == HealthStatusDown { + return "red" + } + return "yellow" +} diff --git a/api/badge_test.go b/api/badge_test.go new file mode 100644 index 00000000..a5be60d2 --- /dev/null +++ b/api/badge_test.go @@ -0,0 +1,395 @@ +package api + +import ( + "net/http" + "net/http/httptest" + "strconv" + "testing" + "time" + + "github.com/TwiN/gatus/v5/config" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/config/endpoint/ui" + "github.com/TwiN/gatus/v5/storage/store" + "github.com/TwiN/gatus/v5/watchdog" +) + +func TestBadge(t *testing.T) { + defer store.Get().Clear() + defer cache.Clear() + cfg := &config.Config{ + Metrics: true, + Endpoints: []*endpoint.Endpoint{ + { + Name: "frontend", + Group: "core", + }, + { + Name: "backend", + Group: "core", + }, + }, + } + + cfg.Endpoints[0].UIConfig = ui.GetDefaultConfig() + cfg.Endpoints[1].UIConfig = ui.GetDefaultConfig() + + watchdog.UpdateEndpointStatuses(cfg.Endpoints[0], &endpoint.Result{Success: true, Connected: true, Duration: time.Millisecond, Timestamp: time.Now()}) + watchdog.UpdateEndpointStatuses(cfg.Endpoints[1], &endpoint.Result{Success: false, Connected: false, Duration: time.Second, Timestamp: time.Now()}) + api := New(cfg) + router := api.Router() + type Scenario struct { + Name string + Path string + ExpectedCode int + Gzip bool + } + scenarios := []Scenario{ + { + Name: "badge-uptime-1h", + Path: "/api/v1/endpoints/core_frontend/uptimes/1h/badge.svg", + ExpectedCode: http.StatusOK, + }, + { + Name: "badge-uptime-24h", + Path: "/api/v1/endpoints/core_backend/uptimes/24h/badge.svg", + ExpectedCode: http.StatusOK, + }, + { + Name: "badge-uptime-7d", + Path: "/api/v1/endpoints/core_frontend/uptimes/7d/badge.svg", + ExpectedCode: http.StatusOK, + }, + { + Name: "badge-uptime-with-invalid-duration", + Path: "/api/v1/endpoints/core_backend/uptimes/3d/badge.svg", + ExpectedCode: http.StatusBadRequest, + }, + { + Name: "badge-uptime-for-invalid-key", + Path: "/api/v1/endpoints/invalid_key/uptimes/7d/badge.svg", + ExpectedCode: http.StatusNotFound, + }, + { + Name: "badge-response-time-1h", + Path: "/api/v1/endpoints/core_frontend/response-times/1h/badge.svg", + ExpectedCode: http.StatusOK, + }, + { + Name: "badge-response-time-24h", + Path: "/api/v1/endpoints/core_backend/response-times/24h/badge.svg", + ExpectedCode: http.StatusOK, + }, + { + Name: "badge-response-time-7d", + Path: "/api/v1/endpoints/core_frontend/response-times/7d/badge.svg", + ExpectedCode: http.StatusOK, + }, + { + Name: "badge-response-time-with-invalid-duration", + Path: "/api/v1/endpoints/core_backend/response-times/3d/badge.svg", + ExpectedCode: http.StatusBadRequest, + }, + { + Name: "badge-response-time-for-invalid-key", + Path: "/api/v1/endpoints/invalid_key/response-times/7d/badge.svg", + ExpectedCode: http.StatusNotFound, + }, + { + Name: "badge-health-up", + Path: "/api/v1/endpoints/core_frontend/health/badge.svg", + ExpectedCode: http.StatusOK, + }, + { + Name: "badge-health-down", + Path: "/api/v1/endpoints/core_backend/health/badge.svg", + ExpectedCode: http.StatusOK, + }, + { + Name: "badge-health-for-invalid-key", + Path: "/api/v1/endpoints/invalid_key/health/badge.svg", + ExpectedCode: http.StatusNotFound, + }, + { + Name: "badge-shields-health-up", + Path: "/api/v1/endpoints/core_frontend/health/badge.shields", + ExpectedCode: http.StatusOK, + }, + { + Name: "badge-shields-health-down", + Path: "/api/v1/endpoints/core_backend/health/badge.shields", + ExpectedCode: http.StatusOK, + }, + { + Name: "badge-shields-health-for-invalid-key", + Path: "/api/v1/endpoints/invalid_key/health/badge.shields", + ExpectedCode: http.StatusNotFound, + }, + { + Name: "chart-response-time-24h", + Path: "/api/v1/endpoints/core_backend/response-times/24h/chart.svg", + ExpectedCode: http.StatusOK, + }, + { + Name: "chart-response-time-7d", + Path: "/api/v1/endpoints/core_frontend/response-times/7d/chart.svg", + ExpectedCode: http.StatusOK, + }, + { + Name: "chart-response-time-with-invalid-duration", + Path: "/api/v1/endpoints/core_backend/response-times/3d/chart.svg", + ExpectedCode: http.StatusBadRequest, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + request := httptest.NewRequest("GET", scenario.Path, http.NoBody) + if scenario.Gzip { + request.Header.Set("Accept-Encoding", "gzip") + } + response, err := router.Test(request) + if err != nil { + return + } + if response.StatusCode != scenario.ExpectedCode { + t.Errorf("%s %s should have returned %d, but returned %d instead", request.Method, request.URL, scenario.ExpectedCode, response.StatusCode) + } + }) + } +} + +func TestGetBadgeColorFromUptime(t *testing.T) { + scenarios := []struct { + Uptime float64 + ExpectedColor string + }{ + { + Uptime: 1, + ExpectedColor: badgeColorHexAwesome, + }, + { + Uptime: 0.99, + ExpectedColor: badgeColorHexAwesome, + }, + { + Uptime: 0.97, + ExpectedColor: badgeColorHexGreat, + }, + { + Uptime: 0.95, + ExpectedColor: badgeColorHexGreat, + }, + { + Uptime: 0.93, + ExpectedColor: badgeColorHexGood, + }, + { + Uptime: 0.9, + ExpectedColor: badgeColorHexGood, + }, + { + Uptime: 0.85, + ExpectedColor: badgeColorHexPassable, + }, + { + Uptime: 0.7, + ExpectedColor: badgeColorHexBad, + }, + { + Uptime: 0.65, + ExpectedColor: badgeColorHexBad, + }, + { + Uptime: 0.6, + ExpectedColor: badgeColorHexVeryBad, + }, + } + for _, scenario := range scenarios { + t.Run("uptime-"+strconv.Itoa(int(scenario.Uptime*100)), func(t *testing.T) { + if getBadgeColorFromUptime(scenario.Uptime) != scenario.ExpectedColor { + t.Errorf("expected %s from %f, got %v", scenario.ExpectedColor, scenario.Uptime, getBadgeColorFromUptime(scenario.Uptime)) + } + }) + } +} + +func TestGetBadgeColorFromResponseTime(t *testing.T) { + defer store.Get().Clear() + defer cache.Clear() + + var ( + firstCondition = endpoint.Condition("[STATUS] == 200") + secondCondition = endpoint.Condition("[RESPONSE_TIME] < 500") + thirdCondition = endpoint.Condition("[CERTIFICATE_EXPIRATION] < 72h") + ) + + firstTestEndpoint := endpoint.Endpoint{ + Name: "a", + URL: "https://example.org/what/ever", + Method: "GET", + Body: "body", + Interval: 30 * time.Second, + Conditions: []endpoint.Condition{firstCondition, secondCondition, thirdCondition}, + Alerts: nil, + NumberOfFailuresInARow: 0, + NumberOfSuccessesInARow: 0, + UIConfig: ui.GetDefaultConfig(), + } + secondTestEndpoint := endpoint.Endpoint{ + Name: "b", + URL: "https://example.org/what/ever", + Method: "GET", + Body: "body", + Interval: 30 * time.Second, + Conditions: []endpoint.Condition{firstCondition, secondCondition, thirdCondition}, + Alerts: nil, + NumberOfFailuresInARow: 0, + NumberOfSuccessesInARow: 0, + UIConfig: &ui.Config{ + Badge: &ui.Badge{ + ResponseTime: &ui.ResponseTime{ + Thresholds: []int{100, 500, 1000, 2000, 3000}, + }, + }, + }, + } + cfg := &config.Config{ + Metrics: true, + Endpoints: []*endpoint.Endpoint{&firstTestEndpoint, &secondTestEndpoint}, + } + + testSuccessfulResult := endpoint.Result{ + Hostname: "example.org", + IP: "127.0.0.1", + HTTPStatus: 200, + Errors: nil, + Connected: true, + Success: true, + Timestamp: time.Now(), + Duration: 150 * time.Millisecond, + CertificateExpiration: 10 * time.Hour, + ConditionResults: []*endpoint.ConditionResult{ + { + Condition: "[STATUS] == 200", + Success: true, + }, + { + Condition: "[RESPONSE_TIME] < 500", + Success: true, + }, + { + Condition: "[CERTIFICATE_EXPIRATION] < 72h", + Success: true, + }, + }, + } + + store.Get().Insert(&firstTestEndpoint, &testSuccessfulResult) + store.Get().Insert(&secondTestEndpoint, &testSuccessfulResult) + + scenarios := []struct { + Key string + ResponseTime int + ExpectedColor string + }{ + { + Key: firstTestEndpoint.Key(), + ResponseTime: 10, + ExpectedColor: badgeColorHexAwesome, + }, + { + Key: firstTestEndpoint.Key(), + ResponseTime: 50, + ExpectedColor: badgeColorHexAwesome, + }, + { + Key: firstTestEndpoint.Key(), + ResponseTime: 75, + ExpectedColor: badgeColorHexGreat, + }, + { + Key: firstTestEndpoint.Key(), + ResponseTime: 150, + ExpectedColor: badgeColorHexGreat, + }, + { + Key: firstTestEndpoint.Key(), + ResponseTime: 201, + ExpectedColor: badgeColorHexGood, + }, + { + Key: firstTestEndpoint.Key(), + ResponseTime: 300, + ExpectedColor: badgeColorHexGood, + }, + { + Key: firstTestEndpoint.Key(), + ResponseTime: 301, + ExpectedColor: badgeColorHexPassable, + }, + { + Key: firstTestEndpoint.Key(), + ResponseTime: 450, + ExpectedColor: badgeColorHexPassable, + }, + { + Key: firstTestEndpoint.Key(), + ResponseTime: 700, + ExpectedColor: badgeColorHexBad, + }, + { + Key: firstTestEndpoint.Key(), + ResponseTime: 1500, + ExpectedColor: badgeColorHexVeryBad, + }, + { + Key: secondTestEndpoint.Key(), + ResponseTime: 50, + ExpectedColor: badgeColorHexAwesome, + }, + { + Key: secondTestEndpoint.Key(), + ResponseTime: 1500, + ExpectedColor: badgeColorHexPassable, + }, + { + Key: secondTestEndpoint.Key(), + ResponseTime: 2222, + ExpectedColor: badgeColorHexBad, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Key+"-response-time-"+strconv.Itoa(scenario.ResponseTime), func(t *testing.T) { + if getBadgeColorFromResponseTime(scenario.ResponseTime, scenario.Key, cfg) != scenario.ExpectedColor { + t.Errorf("expected %s from %d, got %v", scenario.ExpectedColor, scenario.ResponseTime, getBadgeColorFromResponseTime(scenario.ResponseTime, scenario.Key, cfg)) + } + }) + } +} + +func TestGetBadgeColorFromHealth(t *testing.T) { + scenarios := []struct { + HealthStatus string + ExpectedColor string + }{ + { + HealthStatus: HealthStatusUp, + ExpectedColor: badgeColorHexAwesome, + }, + { + HealthStatus: HealthStatusDown, + ExpectedColor: badgeColorHexVeryBad, + }, + { + HealthStatus: HealthStatusUnknown, + ExpectedColor: badgeColorHexPassable, + }, + } + for _, scenario := range scenarios { + t.Run("health-"+scenario.HealthStatus, func(t *testing.T) { + if getBadgeColorFromHealth(scenario.HealthStatus) != scenario.ExpectedColor { + t.Errorf("expected %s from %s, got %v", scenario.ExpectedColor, scenario.HealthStatus, getBadgeColorFromHealth(scenario.HealthStatus)) + } + }) + } +} diff --git a/api/cache.go b/api/cache.go new file mode 100644 index 00000000..a1a58baf --- /dev/null +++ b/api/cache.go @@ -0,0 +1,15 @@ +package api + +import ( + "time" + + "github.com/TwiN/gocache/v2" +) + +const ( + cacheTTL = 10 * time.Second +) + +var ( + cache = gocache.NewCache().WithMaxSize(100).WithEvictionPolicy(gocache.FirstInFirstOut) +) diff --git a/api/chart.go b/api/chart.go new file mode 100644 index 00000000..81a4d11d --- /dev/null +++ b/api/chart.go @@ -0,0 +1,123 @@ +package api + +import ( + "errors" + "math" + "net/http" + "sort" + "time" + + "github.com/TwiN/gatus/v5/storage/store" + "github.com/TwiN/gatus/v5/storage/store/common" + "github.com/TwiN/logr" + "github.com/gofiber/fiber/v2" + "github.com/wcharczuk/go-chart/v2" + "github.com/wcharczuk/go-chart/v2/drawing" +) + +const timeFormat = "3:04PM" + +var ( + gridStyle = chart.Style{ + StrokeColor: drawing.Color{R: 119, G: 119, B: 119, A: 40}, + StrokeWidth: 1.0, + } + axisStyle = chart.Style{ + FontColor: drawing.Color{R: 119, G: 119, B: 119, A: 255}, + } + transparentStyle = chart.Style{ + FillColor: drawing.Color{R: 255, G: 255, B: 255, A: 0}, + } +) + +func ResponseTimeChart(c *fiber.Ctx) error { + duration := c.Params("duration") + chartTimestampFormatter := chart.TimeValueFormatterWithFormat(timeFormat) + var from time.Time + switch duration { + case "30d": + from = time.Now().Truncate(time.Hour).Add(-30 * 24 * time.Hour) + chartTimestampFormatter = chart.TimeDateValueFormatter + case "7d": + from = time.Now().Truncate(time.Hour).Add(-7 * 24 * time.Hour) + case "24h": + from = time.Now().Truncate(time.Hour).Add(-24 * time.Hour) + default: + return c.Status(400).SendString("Durations supported: 30d, 7d, 24h") + } + hourlyAverageResponseTime, err := store.Get().GetHourlyAverageResponseTimeByKey(c.Params("key"), from, time.Now()) + if err != nil { + if errors.Is(err, common.ErrEndpointNotFound) { + return c.Status(404).SendString(err.Error()) + } else if errors.Is(err, common.ErrInvalidTimeRange) { + return c.Status(400).SendString(err.Error()) + } + return c.Status(500).SendString(err.Error()) + } + if len(hourlyAverageResponseTime) == 0 { + return c.Status(204).SendString("") + } + series := chart.TimeSeries{ + Name: "Average response time per hour", + Style: chart.Style{ + StrokeWidth: 1.5, + DotWidth: 2.0, + }, + } + keys := make([]int, 0, len(hourlyAverageResponseTime)) + earliestTimestamp := int64(0) + for hourlyTimestamp := range hourlyAverageResponseTime { + keys = append(keys, int(hourlyTimestamp)) + if earliestTimestamp == 0 || hourlyTimestamp < earliestTimestamp { + earliestTimestamp = hourlyTimestamp + } + } + for earliestTimestamp > from.Unix() { + earliestTimestamp -= int64(time.Hour.Seconds()) + keys = append(keys, int(earliestTimestamp)) + } + sort.Ints(keys) + var maxAverageResponseTime float64 + for _, key := range keys { + averageResponseTime := float64(hourlyAverageResponseTime[int64(key)]) + if maxAverageResponseTime < averageResponseTime { + maxAverageResponseTime = averageResponseTime + } + series.XValues = append(series.XValues, time.Unix(int64(key), 0)) + series.YValues = append(series.YValues, averageResponseTime) + } + graph := chart.Chart{ + Canvas: transparentStyle, + Background: transparentStyle, + Width: 1280, + Height: 300, + XAxis: chart.XAxis{ + ValueFormatter: chartTimestampFormatter, + GridMajorStyle: gridStyle, + GridMinorStyle: gridStyle, + Style: axisStyle, + NameStyle: axisStyle, + }, + YAxis: chart.YAxis{ + Name: "Average response time", + GridMajorStyle: gridStyle, + GridMinorStyle: gridStyle, + Style: axisStyle, + NameStyle: axisStyle, + Range: &chart.ContinuousRange{ + Min: 0, + Max: math.Ceil(maxAverageResponseTime * 1.25), + }, + }, + Series: []chart.Series{series}, + } + c.Set("Content-Type", "image/svg+xml") + c.Set("Cache-Control", "no-cache, no-store") + c.Set("Expires", "0") + c.Status(http.StatusOK) + if err := graph.Render(chart.SVG, c); err != nil { + logr.Errorf("[api.ResponseTimeChart] Failed to render response time chart: %s", err.Error()) + return c.Status(500).SendString(err.Error()) + } + return nil +} diff --git a/api/chart_test.go b/api/chart_test.go new file mode 100644 index 00000000..2a699a5b --- /dev/null +++ b/api/chart_test.go @@ -0,0 +1,83 @@ +package api + +import ( + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/TwiN/gatus/v5/config" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/storage/store" + "github.com/TwiN/gatus/v5/watchdog" +) + +func TestResponseTimeChart(t *testing.T) { + defer store.Get().Clear() + defer cache.Clear() + cfg := &config.Config{ + Metrics: true, + Endpoints: []*endpoint.Endpoint{ + { + Name: "frontend", + Group: "core", + }, + { + Name: "backend", + Group: "core", + }, + }, + } + watchdog.UpdateEndpointStatuses(cfg.Endpoints[0], &endpoint.Result{Success: true, Duration: time.Millisecond, Timestamp: time.Now()}) + watchdog.UpdateEndpointStatuses(cfg.Endpoints[1], &endpoint.Result{Success: false, Duration: time.Second, Timestamp: time.Now()}) + api := New(cfg) + router := api.Router() + type Scenario struct { + Name string + Path string + ExpectedCode int + Gzip bool + } + scenarios := []Scenario{ + { + Name: "chart-response-time-24h", + Path: "/api/v1/endpoints/core_backend/response-times/24h/chart.svg", + ExpectedCode: http.StatusOK, + }, + { + Name: "chart-response-time-7d", + Path: "/api/v1/endpoints/core_frontend/response-times/7d/chart.svg", + ExpectedCode: http.StatusOK, + }, + { + Name: "chart-response-time-30d", + Path: "/api/v1/endpoints/core_frontend/response-times/30d/chart.svg", + ExpectedCode: http.StatusOK, + }, + { + Name: "chart-response-time-with-invalid-duration", + Path: "/api/v1/endpoints/core_backend/response-times/3d/chart.svg", + ExpectedCode: http.StatusBadRequest, + }, + { + Name: "chart-response-time-for-invalid-key", + Path: "/api/v1/endpoints/invalid_key/response-times/7d/chart.svg", + ExpectedCode: http.StatusNotFound, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + request := httptest.NewRequest("GET", scenario.Path, http.NoBody) + if scenario.Gzip { + request.Header.Set("Accept-Encoding", "gzip") + } + response, err := router.Test(request) + if err != nil { + return + } + if response.StatusCode != scenario.ExpectedCode { + t.Errorf("%s %s should have returned %d, but returned %d instead", request.Method, request.URL, scenario.ExpectedCode, response.StatusCode) + } + }) + } +} diff --git a/api/config.go b/api/config.go new file mode 100644 index 00000000..0b94e92a --- /dev/null +++ b/api/config.go @@ -0,0 +1,25 @@ +package api + +import ( + "fmt" + + "github.com/TwiN/gatus/v5/security" + "github.com/gofiber/fiber/v2" +) + +type ConfigHandler struct { + securityConfig *security.Config +} + +func (handler ConfigHandler) GetConfig(c *fiber.Ctx) error { + hasOIDC := false + isAuthenticated := true // Default to true if no security config is set + if handler.securityConfig != nil { + hasOIDC = handler.securityConfig.OIDC != nil + isAuthenticated = handler.securityConfig.IsAuthenticated(c) + } + // Return the config + c.Set("Content-Type", "application/json") + return c.Status(200). + SendString(fmt.Sprintf(`{"oidc":%v,"authenticated":%v}`, hasOIDC, isAuthenticated)) +} diff --git a/api/config_test.go b/api/config_test.go new file mode 100644 index 00000000..5dab021d --- /dev/null +++ b/api/config_test.go @@ -0,0 +1,46 @@ +package api + +import ( + "io" + "net/http" + "testing" + + "github.com/TwiN/gatus/v5/security" + "github.com/gofiber/fiber/v2" +) + +func TestConfigHandler_ServeHTTP(t *testing.T) { + securityConfig := &security.Config{ + OIDC: &security.OIDCConfig{ + IssuerURL: "https://sso.gatus.io/", + RedirectURL: "http://localhost:80/authorization-code/callback", + Scopes: []string{"openid"}, + AllowedSubjects: []string{"user1@example.com"}, + }, + } + handler := ConfigHandler{securityConfig: securityConfig} + // Create a fake router. We're doing this because I need the gate to be initialized. + app := fiber.New() + app.Get("/api/v1/config", handler.GetConfig) + err := securityConfig.ApplySecurityMiddleware(app) + if err != nil { + t.Error("expected err to be nil, but was", err) + } + // Test the config handler + request, _ := http.NewRequest("GET", "/api/v1/config", http.NoBody) + response, err := app.Test(request) + if err != nil { + t.Error("expected err to be nil, but was", err) + } + defer response.Body.Close() + if response.StatusCode != http.StatusOK { + t.Error("expected code to be 200, but was", response.StatusCode) + } + body, err := io.ReadAll(response.Body) + if err != nil { + t.Error("expected err to be nil, but was", err) + } + if string(body) != `{"oidc":true,"authenticated":false}` { + t.Error("expected body to be `{\"oidc\":true,\"authenticated\":false}`, but was", string(body)) + } +} diff --git a/api/custom_css.go b/api/custom_css.go new file mode 100644 index 00000000..79229435 --- /dev/null +++ b/api/custom_css.go @@ -0,0 +1,14 @@ +package api + +import ( + "github.com/gofiber/fiber/v2" +) + +type CustomCSSHandler struct { + customCSS string +} + +func (handler CustomCSSHandler) GetCustomCSS(c *fiber.Ctx) error { + c.Set("Content-Type", "text/css") + return c.Status(200).SendString(handler.customCSS) +} diff --git a/api/endpoint_status.go b/api/endpoint_status.go new file mode 100644 index 00000000..691fa8ca --- /dev/null +++ b/api/endpoint_status.go @@ -0,0 +1,107 @@ +package api + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/config/remote" + "github.com/TwiN/gatus/v5/storage/store" + "github.com/TwiN/gatus/v5/storage/store/common" + "github.com/TwiN/gatus/v5/storage/store/common/paging" + "github.com/TwiN/logr" + "github.com/gofiber/fiber/v2" +) + +// EndpointStatuses handles requests to retrieve all EndpointStatus +// Due to how intensive this operation can be on the storage, this function leverages a cache. +func EndpointStatuses(cfg *config.Config) fiber.Handler { + return func(c *fiber.Ctx) error { + page, pageSize := extractPageAndPageSizeFromRequest(c) + value, exists := cache.Get(fmt.Sprintf("endpoint-status-%d-%d", page, pageSize)) + var data []byte + if !exists { + endpointStatuses, err := store.Get().GetAllEndpointStatuses(paging.NewEndpointStatusParams().WithResults(page, pageSize)) + if err != nil { + logr.Errorf("[api.EndpointStatuses] Failed to retrieve endpoint statuses: %s", err.Error()) + return c.Status(500).SendString(err.Error()) + } + // ALPHA: Retrieve endpoint statuses from remote instances + if endpointStatusesFromRemote, err := getEndpointStatusesFromRemoteInstances(cfg.Remote); err != nil { + logr.Errorf("[handler.EndpointStatuses] Silently failed to retrieve endpoint statuses from remote: %s", err.Error()) + } else if endpointStatusesFromRemote != nil { + endpointStatuses = append(endpointStatuses, endpointStatusesFromRemote...) + } + // Marshal endpoint statuses to JSON + data, err = json.Marshal(endpointStatuses) + if err != nil { + logr.Errorf("[api.EndpointStatuses] Unable to marshal object to JSON: %s", err.Error()) + return c.Status(500).SendString("unable to marshal object to JSON") + } + cache.SetWithTTL(fmt.Sprintf("endpoint-status-%d-%d", page, pageSize), data, cacheTTL) + } else { + data = value.([]byte) + } + c.Set("Content-Type", "application/json") + return c.Status(200).Send(data) + } +} + +func getEndpointStatusesFromRemoteInstances(remoteConfig *remote.Config) ([]*endpoint.Status, error) { + if remoteConfig == nil || len(remoteConfig.Instances) == 0 { + return nil, nil + } + var endpointStatusesFromAllRemotes []*endpoint.Status + httpClient := client.GetHTTPClient(remoteConfig.ClientConfig) + for _, instance := range remoteConfig.Instances { + response, err := httpClient.Get(instance.URL) + if err != nil { + // Log the error but continue with other instances + logr.Errorf("[api.getEndpointStatusesFromRemoteInstances] Failed to retrieve endpoint statuses from %s: %s", instance.URL, err.Error()) + continue + } + var endpointStatuses []*endpoint.Status + if err = json.NewDecoder(response.Body).Decode(&endpointStatuses); err != nil { + _ = response.Body.Close() + logr.Errorf("[api.getEndpointStatusesFromRemoteInstances] Failed to decode endpoint statuses from %s: %s", instance.URL, err.Error()) + continue + } + _ = response.Body.Close() + for _, endpointStatus := range endpointStatuses { + endpointStatus.Name = instance.EndpointPrefix + endpointStatus.Name + } + endpointStatusesFromAllRemotes = append(endpointStatusesFromAllRemotes, endpointStatuses...) + } + // Only return nil, error if no remote instances were successfully processed + if len(endpointStatusesFromAllRemotes) == 0 && remoteConfig.Instances != nil { + return nil, fmt.Errorf("failed to retrieve endpoint statuses from all remote instances") + } + return endpointStatusesFromAllRemotes, nil +} + +// EndpointStatus retrieves a single endpoint.Status by group and endpoint name +func EndpointStatus(c *fiber.Ctx) error { + page, pageSize := extractPageAndPageSizeFromRequest(c) + endpointStatus, err := store.Get().GetEndpointStatusByKey(c.Params("key"), paging.NewEndpointStatusParams().WithResults(page, pageSize).WithEvents(1, common.MaximumNumberOfEvents)) + if err != nil { + if errors.Is(err, common.ErrEndpointNotFound) { + return c.Status(404).SendString(err.Error()) + } + logr.Errorf("[api.EndpointStatus] Failed to retrieve endpoint status: %s", err.Error()) + return c.Status(500).SendString(err.Error()) + } + if endpointStatus == nil { // XXX: is this check necessary? + logr.Errorf("[api.EndpointStatus] Endpoint with key=%s not found", c.Params("key")) + return c.Status(404).SendString("not found") + } + output, err := json.Marshal(endpointStatus) + if err != nil { + logr.Errorf("[api.EndpointStatus] Unable to marshal object to JSON: %s", err.Error()) + return c.Status(500).SendString("unable to marshal object to JSON") + } + c.Set("Content-Type", "application/json") + return c.Status(200).Send(output) +} diff --git a/api/endpoint_status_test.go b/api/endpoint_status_test.go new file mode 100644 index 00000000..08725393 --- /dev/null +++ b/api/endpoint_status_test.go @@ -0,0 +1,220 @@ +package api + +import ( + "io" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/TwiN/gatus/v5/config" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/storage/store" + "github.com/TwiN/gatus/v5/watchdog" +) + +var ( + timestamp = time.Now() + + testEndpoint = endpoint.Endpoint{ + Name: "name", + Group: "group", + URL: "https://example.org/what/ever", + Method: "GET", + Body: "body", + Interval: 30 * time.Second, + Conditions: []endpoint.Condition{endpoint.Condition("[STATUS] == 200"), endpoint.Condition("[RESPONSE_TIME] < 500"), endpoint.Condition("[CERTIFICATE_EXPIRATION] < 72h")}, + Alerts: nil, + NumberOfFailuresInARow: 0, + NumberOfSuccessesInARow: 0, + } + testSuccessfulResult = endpoint.Result{ + Hostname: "example.org", + IP: "127.0.0.1", + HTTPStatus: 200, + Errors: nil, + Connected: true, + Success: true, + Timestamp: timestamp, + Duration: 150 * time.Millisecond, + CertificateExpiration: 10 * time.Hour, + ConditionResults: []*endpoint.ConditionResult{ + { + Condition: "[STATUS] == 200", + Success: true, + }, + { + Condition: "[RESPONSE_TIME] < 500", + Success: true, + }, + { + Condition: "[CERTIFICATE_EXPIRATION] < 72h", + Success: true, + }, + }, + } + testUnsuccessfulResult = endpoint.Result{ + Hostname: "example.org", + IP: "127.0.0.1", + HTTPStatus: 200, + Errors: []string{"error-1", "error-2"}, + Connected: true, + Success: false, + Timestamp: timestamp, + Duration: 750 * time.Millisecond, + CertificateExpiration: 10 * time.Hour, + ConditionResults: []*endpoint.ConditionResult{ + { + Condition: "[STATUS] == 200", + Success: true, + }, + { + Condition: "[RESPONSE_TIME] < 500", + Success: false, + }, + { + Condition: "[CERTIFICATE_EXPIRATION] < 72h", + Success: false, + }, + }, + } +) + +func TestEndpointStatus(t *testing.T) { + defer store.Get().Clear() + defer cache.Clear() + cfg := &config.Config{ + Metrics: true, + Endpoints: []*endpoint.Endpoint{ + { + Name: "frontend", + Group: "core", + }, + { + Name: "backend", + Group: "core", + }, + }, + } + watchdog.UpdateEndpointStatuses(cfg.Endpoints[0], &endpoint.Result{Success: true, Duration: time.Millisecond, Timestamp: time.Now()}) + watchdog.UpdateEndpointStatuses(cfg.Endpoints[1], &endpoint.Result{Success: false, Duration: time.Second, Timestamp: time.Now()}) + api := New(cfg) + router := api.Router() + type Scenario struct { + Name string + Path string + ExpectedCode int + Gzip bool + } + scenarios := []Scenario{ + { + Name: "endpoint-status", + Path: "/api/v1/endpoints/core_frontend/statuses", + ExpectedCode: http.StatusOK, + }, + { + Name: "endpoint-status-gzip", + Path: "/api/v1/endpoints/core_frontend/statuses", + ExpectedCode: http.StatusOK, + Gzip: true, + }, + { + Name: "endpoint-status-pagination", + Path: "/api/v1/endpoints/core_frontend/statuses?page=1&pageSize=20", + ExpectedCode: http.StatusOK, + }, + { + Name: "endpoint-status-for-invalid-key", + Path: "/api/v1/endpoints/invalid_key/statuses", + ExpectedCode: http.StatusNotFound, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + request := httptest.NewRequest("GET", scenario.Path, http.NoBody) + if scenario.Gzip { + request.Header.Set("Accept-Encoding", "gzip") + } + response, err := router.Test(request) + if err != nil { + return + } + if response.StatusCode != scenario.ExpectedCode { + t.Errorf("%s %s should have returned %d, but returned %d instead", request.Method, request.URL, scenario.ExpectedCode, response.StatusCode) + } + }) + } +} + +func TestEndpointStatuses(t *testing.T) { + defer store.Get().Clear() + defer cache.Clear() + firstResult := &testSuccessfulResult + secondResult := &testUnsuccessfulResult + store.Get().Insert(&testEndpoint, firstResult) + store.Get().Insert(&testEndpoint, secondResult) + // Can't be bothered dealing with timezone issues on the worker that runs the automated tests + firstResult.Timestamp = time.Time{} + secondResult.Timestamp = time.Time{} + api := New(&config.Config{Metrics: true}) + router := api.Router() + type Scenario struct { + Name string + Path string + ExpectedCode int + ExpectedBody string + } + scenarios := []Scenario{ + { + Name: "no-pagination", + Path: "/api/v1/endpoints/statuses", + ExpectedCode: http.StatusOK, + ExpectedBody: `[{"name":"name","group":"group","key":"group_name","results":[{"status":200,"hostname":"example.org","duration":150000000,"conditionResults":[{"condition":"[STATUS] == 200","success":true},{"condition":"[RESPONSE_TIME] \u003c 500","success":true},{"condition":"[CERTIFICATE_EXPIRATION] \u003c 72h","success":true}],"success":true,"timestamp":"0001-01-01T00:00:00Z"},{"status":200,"hostname":"example.org","duration":750000000,"errors":["error-1","error-2"],"conditionResults":[{"condition":"[STATUS] == 200","success":true},{"condition":"[RESPONSE_TIME] \u003c 500","success":false},{"condition":"[CERTIFICATE_EXPIRATION] \u003c 72h","success":false}],"success":false,"timestamp":"0001-01-01T00:00:00Z"}]}]`, + }, + { + Name: "pagination-first-result", + Path: "/api/v1/endpoints/statuses?page=1&pageSize=1", + ExpectedCode: http.StatusOK, + ExpectedBody: `[{"name":"name","group":"group","key":"group_name","results":[{"status":200,"hostname":"example.org","duration":750000000,"errors":["error-1","error-2"],"conditionResults":[{"condition":"[STATUS] == 200","success":true},{"condition":"[RESPONSE_TIME] \u003c 500","success":false},{"condition":"[CERTIFICATE_EXPIRATION] \u003c 72h","success":false}],"success":false,"timestamp":"0001-01-01T00:00:00Z"}]}]`, + }, + { + Name: "pagination-second-result", + Path: "/api/v1/endpoints/statuses?page=2&pageSize=1", + ExpectedCode: http.StatusOK, + ExpectedBody: `[{"name":"name","group":"group","key":"group_name","results":[{"status":200,"hostname":"example.org","duration":150000000,"conditionResults":[{"condition":"[STATUS] == 200","success":true},{"condition":"[RESPONSE_TIME] \u003c 500","success":true},{"condition":"[CERTIFICATE_EXPIRATION] \u003c 72h","success":true}],"success":true,"timestamp":"0001-01-01T00:00:00Z"}]}]`, + }, + { + Name: "pagination-no-results", + Path: "/api/v1/endpoints/statuses?page=5&pageSize=20", + ExpectedCode: http.StatusOK, + ExpectedBody: `[{"name":"name","group":"group","key":"group_name","results":[]}]`, + }, + { + Name: "invalid-pagination-should-fall-back-to-default", + Path: "/api/v1/endpoints/statuses?page=INVALID&pageSize=INVALID", + ExpectedCode: http.StatusOK, + ExpectedBody: `[{"name":"name","group":"group","key":"group_name","results":[{"status":200,"hostname":"example.org","duration":150000000,"conditionResults":[{"condition":"[STATUS] == 200","success":true},{"condition":"[RESPONSE_TIME] \u003c 500","success":true},{"condition":"[CERTIFICATE_EXPIRATION] \u003c 72h","success":true}],"success":true,"timestamp":"0001-01-01T00:00:00Z"},{"status":200,"hostname":"example.org","duration":750000000,"errors":["error-1","error-2"],"conditionResults":[{"condition":"[STATUS] == 200","success":true},{"condition":"[RESPONSE_TIME] \u003c 500","success":false},{"condition":"[CERTIFICATE_EXPIRATION] \u003c 72h","success":false}],"success":false,"timestamp":"0001-01-01T00:00:00Z"}]}]`, + }, + } + + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + request := httptest.NewRequest("GET", scenario.Path, http.NoBody) + response, err := router.Test(request) + if err != nil { + return + } + defer response.Body.Close() + if response.StatusCode != scenario.ExpectedCode { + t.Errorf("%s %s should have returned %d, but returned %d instead", request.Method, request.URL, scenario.ExpectedCode, response.StatusCode) + } + body, err := io.ReadAll(response.Body) + if err != nil { + t.Error("expected err to be nil, but was", err) + } + if string(body) != scenario.ExpectedBody { + t.Errorf("expected:\n %s\n\ngot:\n %s", scenario.ExpectedBody, string(body)) + } + }) + } +} diff --git a/api/external_endpoint.go b/api/external_endpoint.go new file mode 100644 index 00000000..c6492933 --- /dev/null +++ b/api/external_endpoint.go @@ -0,0 +1,70 @@ +package api + +import ( + "errors" + "strings" + "time" + + "github.com/TwiN/gatus/v5/config" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/storage/store" + "github.com/TwiN/gatus/v5/storage/store/common" + "github.com/TwiN/gatus/v5/watchdog" + "github.com/TwiN/logr" + "github.com/gofiber/fiber/v2" +) + +func CreateExternalEndpointResult(cfg *config.Config) fiber.Handler { + return func(c *fiber.Ctx) error { + // Check if the success query parameter is present + success, exists := c.Queries()["success"] + if !exists || (success != "true" && success != "false") { + return c.Status(400).SendString("missing or invalid success query parameter") + } + // Check if the authorization bearer token header is correct + authorizationHeader := string(c.Request().Header.Peek("Authorization")) + if !strings.HasPrefix(authorizationHeader, "Bearer ") { + return c.Status(401).SendString("invalid Authorization header") + } + token := strings.TrimSpace(strings.TrimPrefix(authorizationHeader, "Bearer ")) + if len(token) == 0 { + return c.Status(401).SendString("bearer token must not be empty") + } + key := c.Params("key") + externalEndpoint := cfg.GetExternalEndpointByKey(key) + if externalEndpoint == nil { + logr.Errorf("[api.CreateExternalEndpointResult] External endpoint with key=%s not found", key) + return c.Status(404).SendString("not found") + } + if externalEndpoint.Token != token { + logr.Errorf("[api.CreateExternalEndpointResult] Invalid token for external endpoint with key=%s", key) + return c.Status(401).SendString("invalid token") + } + // Persist the result in the storage + result := &endpoint.Result{ + Timestamp: time.Now(), + Success: c.QueryBool("success"), + Errors: []string{}, + } + if !result.Success && c.Query("error") != "" { + result.Errors = append(result.Errors, c.Query("error")) + } + convertedEndpoint := externalEndpoint.ToEndpoint() + if err := store.Get().Insert(convertedEndpoint, result); err != nil { + if errors.Is(err, common.ErrEndpointNotFound) { + return c.Status(404).SendString(err.Error()) + } + logr.Errorf("[api.CreateExternalEndpointResult] Failed to insert result in storage: %s", err.Error()) + return c.Status(500).SendString(err.Error()) + } + logr.Infof("[api.CreateExternalEndpointResult] Successfully inserted result for external endpoint with key=%s and success=%s", c.Params("key"), success) + // Check if an alert should be triggered or resolved + if !cfg.Maintenance.IsUnderMaintenance() { + watchdog.HandleAlerting(convertedEndpoint, result, cfg.Alerting) + externalEndpoint.NumberOfSuccessesInARow = convertedEndpoint.NumberOfSuccessesInARow + externalEndpoint.NumberOfFailuresInARow = convertedEndpoint.NumberOfFailuresInARow + } + // Return the result + return c.Status(200).SendString("") + } +} diff --git a/api/external_endpoint_test.go b/api/external_endpoint_test.go new file mode 100644 index 00000000..d63ab6c0 --- /dev/null +++ b/api/external_endpoint_test.go @@ -0,0 +1,161 @@ +package api + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/TwiN/gatus/v5/alerting" + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/alerting/provider/discord" + "github.com/TwiN/gatus/v5/config" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/config/maintenance" + "github.com/TwiN/gatus/v5/storage/store" + "github.com/TwiN/gatus/v5/storage/store/common/paging" +) + +func TestCreateExternalEndpointResult(t *testing.T) { + defer store.Get().Clear() + defer cache.Clear() + cfg := &config.Config{ + Alerting: &alerting.Config{ + Discord: &discord.AlertProvider{}, + }, + ExternalEndpoints: []*endpoint.ExternalEndpoint{ + { + Name: "n", + Group: "g", + Token: "token", + Alerts: []*alert.Alert{ + { + Type: alert.TypeDiscord, + FailureThreshold: 2, + SuccessThreshold: 2, + }, + }, + }, + }, + Maintenance: &maintenance.Config{}, + } + api := New(cfg) + router := api.Router() + scenarios := []struct { + Name string + Path string + AuthorizationHeaderBearerToken string + ExpectedCode int + }{ + { + Name: "no-token", + Path: "/api/v1/endpoints/g_n/external?success=true", + AuthorizationHeaderBearerToken: "", + ExpectedCode: 401, + }, + { + Name: "bad-token", + Path: "/api/v1/endpoints/g_n/external?success=true", + AuthorizationHeaderBearerToken: "Bearer bad-token", + ExpectedCode: 401, + }, + { + Name: "bad-key", + Path: "/api/v1/endpoints/bad_key/external?success=true", + AuthorizationHeaderBearerToken: "Bearer token", + ExpectedCode: 404, + }, + { + Name: "bad-success-value", + Path: "/api/v1/endpoints/g_n/external?success=invalid", + AuthorizationHeaderBearerToken: "Bearer token", + ExpectedCode: 400, + }, + { + Name: "good-token-success-true", + Path: "/api/v1/endpoints/g_n/external?success=true", + AuthorizationHeaderBearerToken: "Bearer token", + ExpectedCode: 200, + }, + { + Name: "good-token-success-true-with-ignored-error-because-success-true", + Path: "/api/v1/endpoints/g_n/external?success=true&error=failed", + AuthorizationHeaderBearerToken: "Bearer token", + ExpectedCode: 200, + }, + { + Name: "good-token-success-false", + Path: "/api/v1/endpoints/g_n/external?success=false", + AuthorizationHeaderBearerToken: "Bearer token", + ExpectedCode: 200, + }, + { + Name: "good-token-success-false-again", + Path: "/api/v1/endpoints/g_n/external?success=false", + AuthorizationHeaderBearerToken: "Bearer token", + ExpectedCode: 200, + }, + { + Name: "good-token-success-false-with-error", + Path: "/api/v1/endpoints/g_n/external?success=false&error=failed", + AuthorizationHeaderBearerToken: "Bearer token", + ExpectedCode: 200, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + request := httptest.NewRequest("POST", scenario.Path, http.NoBody) + if len(scenario.AuthorizationHeaderBearerToken) > 0 { + request.Header.Set("Authorization", scenario.AuthorizationHeaderBearerToken) + } + response, err := router.Test(request) + if err != nil { + return + } + defer response.Body.Close() + if response.StatusCode != scenario.ExpectedCode { + t.Errorf("%s %s should have returned %d, but returned %d instead", request.Method, request.URL, scenario.ExpectedCode, response.StatusCode) + } + }) + } + t.Run("verify-end-results", func(t *testing.T) { + endpointStatus, err := store.Get().GetEndpointStatus("g", "n", paging.NewEndpointStatusParams().WithResults(1, 10)) + if err != nil { + t.Errorf("failed to get endpoint status: %s", err.Error()) + return + } + if endpointStatus.Key != "g_n" { + t.Errorf("expected key to be g_n but got %s", endpointStatus.Key) + } + if len(endpointStatus.Results) != 5 { + t.Errorf("expected 3 results but got %d", len(endpointStatus.Results)) + } + if !endpointStatus.Results[0].Success { + t.Errorf("expected first result to be successful") + } + if !endpointStatus.Results[1].Success { + t.Errorf("expected second result to be successful") + } + if len(endpointStatus.Results[1].Errors) > 0 { + t.Errorf("expected second result to have no errors") + } + if endpointStatus.Results[2].Success { + t.Errorf("expected third result to be unsuccessful") + } + if endpointStatus.Results[3].Success { + t.Errorf("expected fourth result to be unsuccessful") + } + if endpointStatus.Results[4].Success { + t.Errorf("expected fifth result to be unsuccessful") + } + if len(endpointStatus.Results[4].Errors) == 0 || endpointStatus.Results[4].Errors[0] != "failed" { + t.Errorf("expected fifth result to have errors: failed") + } + externalEndpointFromConfig := cfg.GetExternalEndpointByKey("g_n") + if externalEndpointFromConfig.NumberOfFailuresInARow != 3 { + t.Errorf("expected 3 failures in a row but got %d", externalEndpointFromConfig.NumberOfFailuresInARow) + } + if externalEndpointFromConfig.NumberOfSuccessesInARow != 0 { + t.Errorf("expected 0 successes in a row but got %d", externalEndpointFromConfig.NumberOfSuccessesInARow) + } + }) +} diff --git a/api/raw.go b/api/raw.go new file mode 100644 index 00000000..de6738ba --- /dev/null +++ b/api/raw.go @@ -0,0 +1,43 @@ +package api + +import ( + "errors" + "fmt" + "time" + + "github.com/TwiN/gatus/v5/storage/store" + "github.com/TwiN/gatus/v5/storage/store/common" + "github.com/gofiber/fiber/v2" +) + +func UptimeRaw(c *fiber.Ctx) error { + duration := c.Params("duration") + var from time.Time + switch duration { + case "30d": + from = time.Now().Add(-30 * 24 * time.Hour) + case "7d": + from = time.Now().Add(-7 * 24 * time.Hour) + case "24h": + from = time.Now().Add(-24 * time.Hour) + case "1h": + from = time.Now().Add(-2 * time.Hour) // Because uptime metrics are stored by hour, we have to cheat a little + default: + return c.Status(400).SendString("Durations supported: 30d, 7d, 24h, 1h") + } + key := c.Params("key") + uptime, err := store.Get().GetUptimeByKey(key, from, time.Now()) + if err != nil { + if errors.Is(err, common.ErrEndpointNotFound) { + return c.Status(404).SendString(err.Error()) + } else if errors.Is(err, common.ErrInvalidTimeRange) { + return c.Status(400).SendString(err.Error()) + } + return c.Status(500).SendString(err.Error()) + } + + c.Set("Content-Type", "text/plain") + c.Set("Cache-Control", "no-cache, no-store, must-revalidate") + c.Set("Expires", "0") + return c.Status(200).Send([]byte(fmt.Sprintf("%f", uptime))) +} diff --git a/api/raw_test.go b/api/raw_test.go new file mode 100644 index 00000000..d227c0aa --- /dev/null +++ b/api/raw_test.go @@ -0,0 +1,93 @@ +package api + +import ( + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/TwiN/gatus/v5/config" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/config/endpoint/ui" + "github.com/TwiN/gatus/v5/storage/store" + "github.com/TwiN/gatus/v5/watchdog" +) + +func TestRawDataEndpoint(t *testing.T) { + defer store.Get().Clear() + defer cache.Clear() + cfg := &config.Config{ + Metrics: true, + Endpoints: []*endpoint.Endpoint{ + { + Name: "frontend", + Group: "core", + }, + { + Name: "backend", + Group: "core", + }, + }, + } + + cfg.Endpoints[0].UIConfig = ui.GetDefaultConfig() + cfg.Endpoints[1].UIConfig = ui.GetDefaultConfig() + + watchdog.UpdateEndpointStatuses(cfg.Endpoints[0], &endpoint.Result{Success: true, Connected: true, Duration: time.Millisecond, Timestamp: time.Now()}) + watchdog.UpdateEndpointStatuses(cfg.Endpoints[1], &endpoint.Result{Success: false, Connected: false, Duration: time.Second, Timestamp: time.Now()}) + api := New(cfg) + router := api.Router() + type Scenario struct { + Name string + Path string + ExpectedCode int + Gzip bool + } + scenarios := []Scenario{ + { + Name: "raw-uptime-1h", + Path: "/api/v1/endpoints/core_frontend/uptimes/1h", + ExpectedCode: http.StatusOK, + }, + { + Name: "raw-uptime-24h", + Path: "/api/v1/endpoints/core_backend/uptimes/24h", + ExpectedCode: http.StatusOK, + }, + { + Name: "raw-uptime-7d", + Path: "/api/v1/endpoints/core_frontend/uptimes/7d", + ExpectedCode: http.StatusOK, + }, + { + Name: "raw-uptime-30d", + Path: "/api/v1/endpoints/core_frontend/uptimes/30d", + ExpectedCode: http.StatusOK, + }, + { + Name: "raw-uptime-with-invalid-duration", + Path: "/api/v1/endpoints/core_backend/uptimes/3d", + ExpectedCode: http.StatusBadRequest, + }, + { + Name: "raw-uptime-for-invalid-key", + Path: "/api/v1/endpoints/invalid_key/uptimes/7d", + ExpectedCode: http.StatusNotFound, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + request := httptest.NewRequest("GET", scenario.Path, http.NoBody) + if scenario.Gzip { + request.Header.Set("Accept-Encoding", "gzip") + } + response, err := router.Test(request) + if err != nil { + return + } + if response.StatusCode != scenario.ExpectedCode { + t.Errorf("%s %s should have returned %d, but returned %d instead", request.Method, request.URL, scenario.ExpectedCode, response.StatusCode) + } + }) + } +} diff --git a/api/spa.go b/api/spa.go new file mode 100644 index 00000000..9e996fa5 --- /dev/null +++ b/api/spa.go @@ -0,0 +1,41 @@ +package api + +import ( + _ "embed" + "html/template" + + "github.com/TwiN/gatus/v5/config/ui" + static "github.com/TwiN/gatus/v5/web" + "github.com/TwiN/logr" + "github.com/gofiber/fiber/v2" +) + +func SinglePageApplication(uiConfig *ui.Config) fiber.Handler { + return func(c *fiber.Ctx) error { + vd := ui.ViewData{UI: uiConfig} + { + themeFromCookie := string(c.Request().Header.Cookie("theme")) + if len(themeFromCookie) > 0 { + if themeFromCookie == "dark" { + vd.Theme = "dark" + } + } else if uiConfig.IsDarkMode() { // Since there's no theme cookie, we'll rely on ui.DarkMode + vd.Theme = "dark" + } + } + t, err := template.ParseFS(static.FileSystem, static.IndexPath) + if err != nil { + // This should never happen, because ui.ValidateAndSetDefaults validates that the template works. + logr.Errorf("[api.SinglePageApplication] Failed to parse template. This should never happen, because the template is validated on start. Error: %s", err.Error()) + return c.Status(500).SendString("Failed to parse template. This should never happen, because the template is validated on start.") + } + c.Set("Content-Type", "text/html") + err = t.Execute(c, vd) + if err != nil { + // This should never happen, because ui.ValidateAndSetDefaults validates that the template works. + logr.Errorf("[api.SinglePageApplication] Failed to execute template. This should never happen, because the template is validated on start. Error: %s", err.Error()) + return c.Status(500).SendString("Failed to parse template. This should never happen, because the template is validated on start.") + } + return c.SendStatus(200) + } +} diff --git a/api/spa_test.go b/api/spa_test.go new file mode 100644 index 00000000..58e02680 --- /dev/null +++ b/api/spa_test.go @@ -0,0 +1,107 @@ +package api + +import ( + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/TwiN/gatus/v5/config" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/config/ui" + "github.com/TwiN/gatus/v5/storage/store" + "github.com/TwiN/gatus/v5/watchdog" +) + +func TestSinglePageApplication(t *testing.T) { + defer store.Get().Clear() + defer cache.Clear() + cfg := &config.Config{ + Metrics: true, + Endpoints: []*endpoint.Endpoint{ + { + Name: "frontend", + Group: "core", + }, + { + Name: "backend", + Group: "core", + }, + }, + UI: &ui.Config{ + Title: "example-title", + }, + } + watchdog.UpdateEndpointStatuses(cfg.Endpoints[0], &endpoint.Result{Success: true, Duration: time.Millisecond, Timestamp: time.Now()}) + watchdog.UpdateEndpointStatuses(cfg.Endpoints[1], &endpoint.Result{Success: false, Duration: time.Second, Timestamp: time.Now()}) + api := New(cfg) + router := api.Router() + type Scenario struct { + Name string + Path string + Gzip bool + CookieDarkMode bool + UIDarkMode bool + ExpectedCode int + ExpectedDarkTheme bool + } + scenarios := []Scenario{ + { + Name: "frontend-home", + Path: "/", + CookieDarkMode: true, + UIDarkMode: false, + ExpectedDarkTheme: true, + ExpectedCode: 200, + }, + { + Name: "frontend-endpoint-light", + Path: "/endpoints/core_frontend", + CookieDarkMode: false, + UIDarkMode: false, + ExpectedDarkTheme: false, + ExpectedCode: 200, + }, + { + Name: "frontend-endpoint-dark", + Path: "/endpoints/core_frontend", + CookieDarkMode: false, + UIDarkMode: true, + ExpectedDarkTheme: true, + ExpectedCode: 200, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + cfg.UI.DarkMode = &scenario.UIDarkMode + request := httptest.NewRequest("GET", scenario.Path, http.NoBody) + if scenario.Gzip { + request.Header.Set("Accept-Encoding", "gzip") + } + if scenario.CookieDarkMode { + request.Header.Set("Cookie", "theme=dark") + } + response, err := router.Test(request) + if err != nil { + return + } + defer response.Body.Close() + if response.StatusCode != scenario.ExpectedCode { + t.Errorf("%s %s should have returned %d, but returned %d instead", request.Method, request.URL, scenario.ExpectedCode, response.StatusCode) + } + body, _ := io.ReadAll(response.Body) + strBody := string(body) + if !strings.Contains(strBody, cfg.UI.Title) { + t.Errorf("%s %s should have contained the title", request.Method, request.URL) + } + if scenario.ExpectedDarkTheme && !strings.Contains(strBody, "class=\"dark\"") { + t.Errorf("%s %s should have responded with dark mode headers", request.Method, request.URL) + } + if !scenario.ExpectedDarkTheme && strings.Contains(strBody, "class=\"dark\"") { + t.Errorf("%s %s should not have responded with dark mode headers", request.Method, request.URL) + } + }) + } +} diff --git a/api/util.go b/api/util.go new file mode 100644 index 00000000..90e647b5 --- /dev/null +++ b/api/util.go @@ -0,0 +1,48 @@ +package api + +import ( + "strconv" + + "github.com/TwiN/gatus/v5/storage/store/common" + "github.com/gofiber/fiber/v2" +) + +const ( + // DefaultPage is the default page to use if none is specified or an invalid value is provided + DefaultPage = 1 + + // DefaultPageSize is the default page siZE to use if none is specified or an invalid value is provided + DefaultPageSize = 20 + + // MaximumPageSize is the maximum page size allowed + MaximumPageSize = common.MaximumNumberOfResults +) + +func extractPageAndPageSizeFromRequest(c *fiber.Ctx) (page, pageSize int) { + var err error + if pageParameter := c.Query("page"); len(pageParameter) == 0 { + page = DefaultPage + } else { + page, err = strconv.Atoi(pageParameter) + if err != nil { + page = DefaultPage + } + if page < 1 { + page = DefaultPage + } + } + if pageSizeParameter := c.Query("pageSize"); len(pageSizeParameter) == 0 { + pageSize = DefaultPageSize + } else { + pageSize, err = strconv.Atoi(pageSizeParameter) + if err != nil { + pageSize = DefaultPageSize + } + if pageSize > MaximumPageSize { + pageSize = MaximumPageSize + } else if pageSize < 1 { + pageSize = DefaultPageSize + } + } + return +} diff --git a/api/util_test.go b/api/util_test.go new file mode 100644 index 00000000..652146ed --- /dev/null +++ b/api/util_test.go @@ -0,0 +1,73 @@ +package api + +import ( + "fmt" + "testing" + + "github.com/gofiber/fiber/v2" + "github.com/valyala/fasthttp" +) + +func TestExtractPageAndPageSizeFromRequest(t *testing.T) { + type Scenario struct { + Name string + Page string + PageSize string + ExpectedPage int + ExpectedPageSize int + } + scenarios := []Scenario{ + { + Page: "1", + PageSize: "20", + ExpectedPage: 1, + ExpectedPageSize: 20, + }, + { + Page: "2", + PageSize: "10", + ExpectedPage: 2, + ExpectedPageSize: 10, + }, + { + Page: "2", + PageSize: "10", + ExpectedPage: 2, + ExpectedPageSize: 10, + }, + { + Page: "1", + PageSize: "999999", + ExpectedPage: 1, + ExpectedPageSize: MaximumPageSize, + }, + { + Page: "-1", + PageSize: "-1", + ExpectedPage: DefaultPage, + ExpectedPageSize: DefaultPageSize, + }, + { + Page: "invalid", + PageSize: "invalid", + ExpectedPage: DefaultPage, + ExpectedPageSize: DefaultPageSize, + }, + } + for _, scenario := range scenarios { + t.Run("page-"+scenario.Page+"-pageSize-"+scenario.PageSize, func(t *testing.T) { + //request := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/statuses?page=%s&pageSize=%s", scenario.Page, scenario.PageSize), http.NoBody) + app := fiber.New() + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + defer app.ReleaseCtx(c) + c.Request().SetRequestURI(fmt.Sprintf("/api/v1/statuses?page=%s&pageSize=%s", scenario.Page, scenario.PageSize)) + actualPage, actualPageSize := extractPageAndPageSizeFromRequest(c) + if actualPage != scenario.ExpectedPage { + t.Errorf("expected %d, got %d", scenario.ExpectedPage, actualPage) + } + if actualPageSize != scenario.ExpectedPageSize { + t.Errorf("expected %d, got %d", scenario.ExpectedPageSize, actualPageSize) + } + }) + } +} diff --git a/client/client.go b/client/client.go new file mode 100644 index 00000000..adc4477e --- /dev/null +++ b/client/client.go @@ -0,0 +1,375 @@ +package client + +import ( + "crypto/tls" + "crypto/x509" + "encoding/json" + "errors" + "fmt" + "io" + "net" + "net/http" + "net/smtp" + "runtime" + "strings" + "time" + + "github.com/TwiN/gocache/v2" + "github.com/TwiN/logr" + "github.com/TwiN/whois" + "github.com/ishidawataru/sctp" + "github.com/miekg/dns" + ping "github.com/prometheus-community/pro-bing" + "golang.org/x/crypto/ssh" + "golang.org/x/net/websocket" +) + +const ( + dnsPort = 53 +) + +var ( + // injectedHTTPClient is used for testing purposes + injectedHTTPClient *http.Client + + whoisClient = whois.NewClient().WithReferralCache(true) + whoisExpirationDateCache = gocache.NewCache().WithMaxSize(10000).WithDefaultTTL(24 * time.Hour) +) + +// GetHTTPClient returns the shared HTTP client, or the client from the configuration passed +func GetHTTPClient(config *Config) *http.Client { + if injectedHTTPClient != nil { + return injectedHTTPClient + } + if config == nil { + return defaultConfig.getHTTPClient() + } + return config.getHTTPClient() +} + +// GetDomainExpiration retrieves the duration until the domain provided expires +func GetDomainExpiration(hostname string) (domainExpiration time.Duration, err error) { + var retrievedCachedValue bool + if v, exists := whoisExpirationDateCache.Get(hostname); exists { + domainExpiration = time.Until(v.(time.Time)) + retrievedCachedValue = true + // If the domain OR the TTL is not going to expire in less than 24 hours + // we don't have to refresh the cache. Otherwise, we'll refresh it. + cacheEntryTTL, _ := whoisExpirationDateCache.TTL(hostname) + if cacheEntryTTL > 24*time.Hour && domainExpiration > 24*time.Hour { + // No need to refresh, so we'll just return the cached values + return domainExpiration, nil + } + } + if whoisResponse, err := whoisClient.QueryAndParse(hostname); err != nil { + if !retrievedCachedValue { // Add an error unless we already retrieved a cached value + return 0, fmt.Errorf("error querying and parsing hostname using whois client: %w", err) + } + } else { + domainExpiration = time.Until(whoisResponse.ExpirationDate) + if domainExpiration > 720*time.Hour { + whoisExpirationDateCache.SetWithTTL(hostname, whoisResponse.ExpirationDate, 240*time.Hour) + } else { + whoisExpirationDateCache.SetWithTTL(hostname, whoisResponse.ExpirationDate, 72*time.Hour) + } + } + return domainExpiration, nil +} + +// CanCreateTCPConnection checks whether a connection can be established with a TCP endpoint +func CanCreateTCPConnection(address string, config *Config) bool { + conn, err := net.DialTimeout("tcp", address, config.Timeout) + if err != nil { + return false + } + _ = conn.Close() + return true +} + +// CanCreateUDPConnection checks whether a connection can be established with a UDP endpoint +func CanCreateUDPConnection(address string, config *Config) bool { + conn, err := net.DialTimeout("udp", address, config.Timeout) + if err != nil { + return false + } + _ = conn.Close() + return true +} + +// CanCreateSCTPConnection checks whether a connection can be established with a SCTP endpoint +func CanCreateSCTPConnection(address string, config *Config) bool { + ch := make(chan bool, 1) + go (func(res chan bool) { + addr, err := sctp.ResolveSCTPAddr("sctp", address) + if err != nil { + res <- false + return + } + + conn, err := sctp.DialSCTP("sctp", nil, addr) + if err != nil { + res <- false + return + } + _ = conn.Close() + res <- true + })(ch) + select { + case result := <-ch: + return result + case <-time.After(config.Timeout): + return false + } +} + +// CanPerformStartTLS checks whether a connection can be established to an address using the STARTTLS protocol +func CanPerformStartTLS(address string, config *Config) (connected bool, certificate *x509.Certificate, err error) { + hostAndPort := strings.Split(address, ":") + if len(hostAndPort) != 2 { + return false, nil, errors.New("invalid address for starttls, format must be host:port") + } + connection, err := net.DialTimeout("tcp", address, config.Timeout) + if err != nil { + return + } + smtpClient, err := smtp.NewClient(connection, hostAndPort[0]) + if err != nil { + return + } + err = smtpClient.StartTLS(&tls.Config{ + InsecureSkipVerify: config.Insecure, + ServerName: hostAndPort[0], + }) + if err != nil { + return + } + if state, ok := smtpClient.TLSConnectionState(); ok { + certificate = state.PeerCertificates[0] + } else { + return false, nil, errors.New("could not get TLS connection state") + } + return true, certificate, nil +} + +// CanPerformTLS checks whether a connection can be established to an address using the TLS protocol +func CanPerformTLS(address string, config *Config) (connected bool, certificate *x509.Certificate, err error) { + connection, err := tls.DialWithDialer(&net.Dialer{Timeout: config.Timeout}, "tcp", address, &tls.Config{ + InsecureSkipVerify: config.Insecure, + }) + if err != nil { + return + } + defer connection.Close() + verifiedChains := connection.ConnectionState().VerifiedChains + // If config.Insecure is set to true, verifiedChains will be an empty list [] + // We should get the parsed certificates from PeerCertificates, it can't be empty on the client side + // Reference: https://pkg.go.dev/crypto/tls#PeerCertificates + if len(verifiedChains) == 0 || len(verifiedChains[0]) == 0 { + peerCertificates := connection.ConnectionState().PeerCertificates + return true, peerCertificates[0], nil + } + return true, verifiedChains[0][0], nil +} + +// CanCreateSSHConnection checks whether a connection can be established and a command can be executed to an address +// using the SSH protocol. +func CanCreateSSHConnection(address, username, password string, config *Config) (bool, *ssh.Client, error) { + var port string + if strings.Contains(address, ":") { + addressAndPort := strings.Split(address, ":") + if len(addressAndPort) != 2 { + return false, nil, errors.New("invalid address for ssh, format must be host:port") + } + address = addressAndPort[0] + port = addressAndPort[1] + } else { + port = "22" + } + cli, err := ssh.Dial("tcp", strings.Join([]string{address, port}, ":"), &ssh.ClientConfig{ + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + User: username, + Auth: []ssh.AuthMethod{ + ssh.Password(password), + }, + Timeout: config.Timeout, + }) + if err != nil { + return false, nil, err + } + return true, cli, nil +} + +func CheckSSHBanner(address string, cfg *Config) (bool, int, error) { + var port string + if strings.Contains(address, ":") { + addressAndPort := strings.Split(address, ":") + if len(addressAndPort) != 2 { + return false, 1, errors.New("invalid address for ssh, format must be ssh://host:port") + } + address = addressAndPort[0] + port = addressAndPort[1] + } else { + port = "22" + } + dialer := net.Dialer{} + connStr := net.JoinHostPort(address, port) + conn, err := dialer.Dial("tcp", connStr) + if err != nil { + return false, 1, err + } + defer conn.Close() + conn.SetReadDeadline(time.Now().Add(time.Second)) + buf := make([]byte, 256) + _, err = io.ReadAtLeast(conn, buf, 1) + if err != nil { + return false, 1, err + } + return true, 0, err +} + +// ExecuteSSHCommand executes a command to an address using the SSH protocol. +func ExecuteSSHCommand(sshClient *ssh.Client, body string, config *Config) (bool, int, error) { + type Body struct { + Command string `json:"command"` + } + defer sshClient.Close() + var b Body + if err := json.Unmarshal([]byte(body), &b); err != nil { + return false, 0, err + } + sess, err := sshClient.NewSession() + if err != nil { + return false, 0, err + } + err = sess.Start(b.Command) + if err != nil { + return false, 0, err + } + defer sess.Close() + err = sess.Wait() + if err == nil { + return true, 0, nil + } + var exitErr *ssh.ExitError + if ok := errors.As(err, &exitErr); !ok { + return false, 0, err + } + return true, exitErr.ExitStatus(), nil +} + +// Ping checks if an address can be pinged and returns the round-trip time if the address can be pinged +// +// Note that this function takes at least 100ms, even if the address is 127.0.0.1 +func Ping(address string, config *Config) (bool, time.Duration) { + pinger := ping.New(address) + pinger.Count = 1 + pinger.Timeout = config.Timeout + // Set the pinger's privileged mode to true for every GOOS except darwin + // See https://github.com/TwiN/gatus/issues/132 + // + // Note that for this to work on Linux, Gatus must run with sudo privileges. + // See https://github.com/prometheus-community/pro-bing#linux + pinger.SetPrivileged(runtime.GOOS != "darwin") + pinger.SetNetwork(config.Network) + err := pinger.Run() + if err != nil { + return false, 0 + } + if pinger.Statistics() != nil { + // If the packet loss is 100, it means that the packet didn't reach the host + if pinger.Statistics().PacketLoss == 100 { + return false, pinger.Timeout + } + return true, pinger.Statistics().MaxRtt + } + return true, 0 +} + +// QueryWebSocket opens a websocket connection, write `body` and return a message from the server +func QueryWebSocket(address, body string, config *Config) (bool, []byte, error) { + const ( + Origin = "http://localhost/" + MaximumMessageSize = 1024 // in bytes + ) + wsConfig, err := websocket.NewConfig(address, Origin) + if err != nil { + return false, nil, fmt.Errorf("error configuring websocket connection: %w", err) + } + if config != nil { + wsConfig.Dialer = &net.Dialer{Timeout: config.Timeout} + } + // Dial URL + ws, err := websocket.DialConfig(wsConfig) + if err != nil { + return false, nil, fmt.Errorf("error dialing websocket: %w", err) + } + defer ws.Close() + // Write message + if _, err := ws.Write([]byte(body)); err != nil { + return false, nil, fmt.Errorf("error writing websocket body: %w", err) + } + // Read message + var n int + msg := make([]byte, MaximumMessageSize) + if n, err = ws.Read(msg); err != nil { + return false, nil, fmt.Errorf("error reading websocket message: %w", err) + } + return true, msg[:n], nil +} + +func QueryDNS(queryType, queryName, url string) (connected bool, dnsRcode string, body []byte, err error) { + if !strings.Contains(url, ":") { + url = fmt.Sprintf("%s:%d", url, dnsPort) + } + queryTypeAsUint16 := dns.StringToType[queryType] + c := new(dns.Client) + m := new(dns.Msg) + m.SetQuestion(queryName, queryTypeAsUint16) + r, _, err := c.Exchange(m, url) + if err != nil { + logr.Infof("[client.QueryDNS] Error exchanging DNS message: %v", err) + return false, "", nil, err + } + connected = true + dnsRcode = dns.RcodeToString[r.Rcode] + for _, rr := range r.Answer { + switch rr.Header().Rrtype { + case dns.TypeA: + if a, ok := rr.(*dns.A); ok { + body = []byte(a.A.String()) + } + case dns.TypeAAAA: + if aaaa, ok := rr.(*dns.AAAA); ok { + body = []byte(aaaa.AAAA.String()) + } + case dns.TypeCNAME: + if cname, ok := rr.(*dns.CNAME); ok { + body = []byte(cname.Target) + } + case dns.TypeMX: + if mx, ok := rr.(*dns.MX); ok { + body = []byte(mx.Mx) + } + case dns.TypeNS: + if ns, ok := rr.(*dns.NS); ok { + body = []byte(ns.Ns) + } + case dns.TypePTR: + if ptr, ok := rr.(*dns.PTR); ok { + body = []byte(ptr.Ptr) + } + case dns.TypeSRV: + if srv, ok := rr.(*dns.SRV); ok { + body = []byte(fmt.Sprintf("%s:%d", srv.Target, srv.Port)) + } + default: + body = []byte("query type is not supported yet") + } + } + return connected, dnsRcode, body, nil +} + +// InjectHTTPClient is used to inject a custom HTTP client for testing purposes +func InjectHTTPClient(httpClient *http.Client) { + injectedHTTPClient = httpClient +} diff --git a/client/client_test.go b/client/client_test.go new file mode 100644 index 00000000..46b08229 --- /dev/null +++ b/client/client_test.go @@ -0,0 +1,511 @@ +package client + +import ( + "bytes" + "crypto/tls" + "io" + "net/http" + "net/netip" + "testing" + "time" + + "github.com/TwiN/gatus/v5/config/endpoint/dns" + "github.com/TwiN/gatus/v5/pattern" + "github.com/TwiN/gatus/v5/test" +) + +func TestGetHTTPClient(t *testing.T) { + cfg := &Config{ + Insecure: false, + IgnoreRedirect: false, + Timeout: 0, + DNSResolver: "tcp://1.1.1.1:53", + OAuth2Config: &OAuth2Config{ + ClientID: "00000000-0000-0000-0000-000000000000", + ClientSecret: "secretsauce", + TokenURL: "https://token-server.local/token", + Scopes: []string{"https://application.local/.default"}, + }, + } + err := cfg.ValidateAndSetDefaults() + if err != nil { + t.Errorf("expected error to be nil, but got: `%s`", err) + } + if GetHTTPClient(cfg) == nil { + t.Error("expected client to not be nil") + } + if GetHTTPClient(nil) == nil { + t.Error("expected client to not be nil") + } +} + +func TestGetDomainExpiration(t *testing.T) { + t.Parallel() + if domainExpiration, err := GetDomainExpiration("example.com"); err != nil { + t.Fatalf("expected error to be nil, but got: `%s`", err) + } else if domainExpiration <= 0 { + t.Error("expected domain expiration to be higher than 0") + } + if domainExpiration, err := GetDomainExpiration("example.com"); err != nil { + t.Errorf("expected error to be nil, but got: `%s`", err) + } else if domainExpiration <= 0 { + t.Error("expected domain expiration to be higher than 0") + } + // Hack to pretend like the domain is expiring in 1 hour, which should trigger a refresh + whoisExpirationDateCache.SetWithTTL("example.com", time.Now().Add(time.Hour), 25*time.Hour) + if domainExpiration, err := GetDomainExpiration("example.com"); err != nil { + t.Errorf("expected error to be nil, but got: `%s`", err) + } else if domainExpiration <= 0 { + t.Error("expected domain expiration to be higher than 0") + } + // Make sure the refresh works when the ttl is <24 hours + whoisExpirationDateCache.SetWithTTL("example.com", time.Now().Add(35*time.Hour), 23*time.Hour) + if domainExpiration, err := GetDomainExpiration("example.com"); err != nil { + t.Errorf("expected error to be nil, but got: `%s`", err) + } else if domainExpiration <= 0 { + t.Error("expected domain expiration to be higher than 0") + } +} + +func TestPing(t *testing.T) { + t.Parallel() + if success, rtt := Ping("127.0.0.1", &Config{Timeout: 500 * time.Millisecond}); !success { + t.Error("expected true") + if rtt == 0 { + t.Error("Round-trip time returned on success should've higher than 0") + } + } + if success, rtt := Ping("256.256.256.256", &Config{Timeout: 500 * time.Millisecond}); success { + t.Error("expected false, because the IP is invalid") + if rtt != 0 { + t.Error("Round-trip time returned on failure should've been 0") + } + } + if success, rtt := Ping("192.168.152.153", &Config{Timeout: 500 * time.Millisecond}); success { + t.Error("expected false, because the IP is valid but the host should be unreachable") + if rtt != 0 { + t.Error("Round-trip time returned on failure should've been 0") + } + } + // Can't perform integration tests (e.g. pinging public targets by single-stacked hostname) here, + // because ICMP is blocked in the network of GitHub-hosted runners. + if success, rtt := Ping("127.0.0.1", &Config{Timeout: 500 * time.Millisecond, Network: "ip"}); !success { + t.Error("expected true") + if rtt == 0 { + t.Error("Round-trip time returned on failure should've been 0") + } + } + if success, rtt := Ping("::1", &Config{Timeout: 500 * time.Millisecond, Network: "ip"}); !success { + t.Error("expected true") + if rtt == 0 { + t.Error("Round-trip time returned on failure should've been 0") + } + } + if success, rtt := Ping("::1", &Config{Timeout: 500 * time.Millisecond, Network: "ip4"}); success { + t.Error("expected false, because the IP isn't an IPv4 address") + if rtt != 0 { + t.Error("Round-trip time returned on failure should've been 0") + } + } + if success, rtt := Ping("127.0.0.1", &Config{Timeout: 500 * time.Millisecond, Network: "ip6"}); success { + t.Error("expected false, because the IP isn't an IPv6 address") + if rtt != 0 { + t.Error("Round-trip time returned on failure should've been 0") + } + } +} + +func TestCanPerformStartTLS(t *testing.T) { + type args struct { + address string + insecure bool + } + tests := []struct { + name string + args args + wantConnected bool + wantErr bool + }{ + { + name: "invalid address", + args: args{ + address: "test", + }, + wantConnected: false, + wantErr: true, + }, + { + name: "error dial", + args: args{ + address: "test:1234", + }, + wantConnected: false, + wantErr: true, + }, + { + name: "valid starttls", + args: args{ + address: "smtp.gmail.com:587", + }, + wantConnected: true, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + connected, _, err := CanPerformStartTLS(tt.args.address, &Config{Insecure: tt.args.insecure, Timeout: 5 * time.Second}) + if (err != nil) != tt.wantErr { + t.Errorf("CanPerformStartTLS() err=%v, wantErr=%v", err, tt.wantErr) + return + } + if connected != tt.wantConnected { + t.Errorf("CanPerformStartTLS() connected=%v, wantConnected=%v", connected, tt.wantConnected) + } + }) + } +} + +func TestCanPerformTLS(t *testing.T) { + type args struct { + address string + insecure bool + } + tests := []struct { + name string + args args + wantConnected bool + wantErr bool + }{ + { + name: "invalid address", + args: args{ + address: "test", + }, + wantConnected: false, + wantErr: true, + }, + { + name: "error dial", + args: args{ + address: "test:1234", + }, + wantConnected: false, + wantErr: true, + }, + { + name: "valid tls", + args: args{ + address: "smtp.gmail.com:465", + }, + wantConnected: true, + wantErr: false, + }, + { + name: "bad cert with insecure true", + args: args{ + address: "expired.badssl.com:443", + insecure: true, + }, + wantConnected: true, + wantErr: false, + }, + { + name: "bad cert with insecure false", + args: args{ + address: "expired.badssl.com:443", + insecure: false, + }, + wantConnected: false, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + connected, _, err := CanPerformTLS(tt.args.address, &Config{Insecure: tt.args.insecure, Timeout: 5 * time.Second}) + if (err != nil) != tt.wantErr { + t.Errorf("CanPerformTLS() err=%v, wantErr=%v", err, tt.wantErr) + return + } + if connected != tt.wantConnected { + t.Errorf("CanPerformTLS() connected=%v, wantConnected=%v", connected, tt.wantConnected) + } + }) + } +} + +func TestCanCreateTCPConnection(t *testing.T) { + if CanCreateTCPConnection("127.0.0.1", &Config{Timeout: 5 * time.Second}) { + t.Error("should've failed, because there's no port in the address") + } + if !CanCreateTCPConnection("1.1.1.1:53", &Config{Timeout: 5 * time.Second}) { + t.Error("should've succeeded, because that IP should always™ be up") + } +} + +// This test checks if a HTTP client configured with `configureOAuth2()` automatically +// performs a Client Credentials OAuth2 flow and adds the obtained token as a `Authorization` +// header to all outgoing HTTP calls. +func TestHttpClientProvidesOAuth2BearerToken(t *testing.T) { + defer InjectHTTPClient(nil) + oAuth2Config := &OAuth2Config{ + ClientID: "00000000-0000-0000-0000-000000000000", + ClientSecret: "secretsauce", + TokenURL: "https://token-server.local/token", + Scopes: []string{"https://application.local/.default"}, + } + mockHttpClient := &http.Client{ + Transport: test.MockRoundTripper(func(r *http.Request) *http.Response { + // if the mock HTTP client tries to get a token from the `token-server` + // we provide the expected token response + if r.Host == "token-server.local" { + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader( + []byte( + `{"token_type":"Bearer","expires_in":3599,"ext_expires_in":3599,"access_token":"secret-token"}`, + ), + )), + } + } + // to verify the headers were sent as expected, we echo them back in the + // `X-Org-Authorization` header and check if the token value matches our + // mocked `token-server` response + return &http.Response{ + StatusCode: http.StatusOK, + Header: map[string][]string{ + "X-Org-Authorization": {r.Header.Get("Authorization")}, + }, + Body: http.NoBody, + } + }), + } + mockHttpClientWithOAuth := configureOAuth2(mockHttpClient, *oAuth2Config) + InjectHTTPClient(mockHttpClientWithOAuth) + request, err := http.NewRequest(http.MethodPost, "http://127.0.0.1:8282", http.NoBody) + if err != nil { + t.Error("expected no error, got", err.Error()) + } + response, err := mockHttpClientWithOAuth.Do(request) + if err != nil { + t.Error("expected no error, got", err.Error()) + } + if response.Header == nil { + t.Error("expected response headers, but got nil") + } + // the mock response echos the Authorization header used in the request back + // to us as `X-Org-Authorization` header, we check here if the value matches + // our expected token `secret-token` + if response.Header.Get("X-Org-Authorization") != "Bearer secret-token" { + t.Error("expected `secret-token` as Bearer token in the mocked response header `X-Org-Authorization`, but got", response.Header.Get("X-Org-Authorization")) + } +} + +func TestQueryWebSocket(t *testing.T) { + _, _, err := QueryWebSocket("", "body", &Config{Timeout: 2 * time.Second}) + if err == nil { + t.Error("expected an error due to the address being invalid") + } + _, _, err = QueryWebSocket("ws://example.org", "body", &Config{Timeout: 2 * time.Second}) + if err == nil { + t.Error("expected an error due to the target not being websocket-friendly") + } +} + +func TestTlsRenegotiation(t *testing.T) { + tests := []struct { + name string + cfg TLSConfig + expectedConfig tls.RenegotiationSupport + }{ + { + name: "default", + cfg: TLSConfig{CertificateFile: "../testdata/cert.pem", PrivateKeyFile: "../testdata/cert.key"}, + expectedConfig: tls.RenegotiateNever, + }, + { + name: "never", + cfg: TLSConfig{RenegotiationSupport: "never", CertificateFile: "../testdata/cert.pem", PrivateKeyFile: "../testdata/cert.key"}, + expectedConfig: tls.RenegotiateNever, + }, + { + name: "once", + cfg: TLSConfig{RenegotiationSupport: "once", CertificateFile: "../testdata/cert.pem", PrivateKeyFile: "../testdata/cert.key"}, + expectedConfig: tls.RenegotiateOnceAsClient, + }, + { + name: "freely", + cfg: TLSConfig{RenegotiationSupport: "freely", CertificateFile: "../testdata/cert.pem", PrivateKeyFile: "../testdata/cert.key"}, + expectedConfig: tls.RenegotiateFreelyAsClient, + }, + { + name: "not-valid-and-broken", + cfg: TLSConfig{RenegotiationSupport: "invalid", CertificateFile: "../testdata/cert.pem", PrivateKeyFile: "../testdata/cert.key"}, + expectedConfig: tls.RenegotiateNever, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + tls := &tls.Config{} + tlsConfig := configureTLS(tls, test.cfg) + if tlsConfig.Renegotiation != test.expectedConfig { + t.Errorf("expected tls renegotiation to be %v, but got %v", test.expectedConfig, tls.Renegotiation) + } + }) + } +} + +func TestQueryDNS(t *testing.T) { + scenarios := []struct { + name string + inputDNS dns.Config + inputURL string + expectedDNSCode string + expectedBody string + isErrExpected bool + }{ + { + name: "test Config with type A", + inputDNS: dns.Config{ + QueryType: "A", + QueryName: "example.com.", + }, + inputURL: "8.8.8.8", + expectedDNSCode: "NOERROR", + expectedBody: "__IPV4__", + }, + { + name: "test Config with type AAAA", + inputDNS: dns.Config{ + QueryType: "AAAA", + QueryName: "example.com.", + }, + inputURL: "8.8.8.8", + expectedDNSCode: "NOERROR", + expectedBody: "__IPV6__", + }, + { + name: "test Config with type CNAME", + inputDNS: dns.Config{ + QueryType: "CNAME", + QueryName: "en.wikipedia.org.", + }, + inputURL: "8.8.8.8", + expectedDNSCode: "NOERROR", + expectedBody: "dyna.wikimedia.org.", + }, + { + name: "test Config with type MX", + inputDNS: dns.Config{ + QueryType: "MX", + QueryName: "example.com.", + }, + inputURL: "8.8.8.8", + expectedDNSCode: "NOERROR", + expectedBody: ".", + }, + { + name: "test Config with type NS", + inputDNS: dns.Config{ + QueryType: "NS", + QueryName: "example.com.", + }, + inputURL: "8.8.8.8", + expectedDNSCode: "NOERROR", + expectedBody: "*.iana-servers.net.", + }, + { + name: "test Config with type PTR", + inputDNS: dns.Config{ + QueryType: "PTR", + QueryName: "8.8.8.8.in-addr.arpa.", + }, + inputURL: "8.8.8.8", + expectedDNSCode: "NOERROR", + expectedBody: "dns.google.", + }, + { + name: "test Config with fake type and retrieve error", + inputDNS: dns.Config{ + QueryType: "B", + QueryName: "example", + }, + inputURL: "8.8.8.8", + isErrExpected: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + _, dnsRCode, body, err := QueryDNS(scenario.inputDNS.QueryType, scenario.inputDNS.QueryName, scenario.inputURL) + if scenario.isErrExpected && err == nil { + t.Errorf("there should be an error") + } + if dnsRCode != scenario.expectedDNSCode { + t.Errorf("expected DNSRCode to be %s, got %s", scenario.expectedDNSCode, dnsRCode) + } + if scenario.inputDNS.QueryType == "NS" { + // Because there are often multiple nameservers backing a single domain, we'll only look at the suffix + if !pattern.Match(scenario.expectedBody, string(body)) { + t.Errorf("got %s, expected result %s,", string(body), scenario.expectedBody) + } + } else { + if string(body) != scenario.expectedBody { + // little hack to validate arbitrary ipv4/ipv6 + switch scenario.expectedBody { + case "__IPV4__": + if addr, err := netip.ParseAddr(string(body)); err != nil { + t.Errorf("got %s, expected result %s", string(body), scenario.expectedBody) + } else if !addr.Is4() { + t.Errorf("got %s, expected valid IPv4", string(body)) + } + case "__IPV6__": + if addr, err := netip.ParseAddr(string(body)); err != nil { + t.Errorf("got %s, expected result %s", string(body), scenario.expectedBody) + } else if !addr.Is6() { + t.Errorf("got %s, expected valid IPv6", string(body)) + } + default: + t.Errorf("got %s, expected result %s", string(body), scenario.expectedBody) + } + } + } + }) + time.Sleep(10 * time.Millisecond) + } +} + +func TestCheckSSHBanner(t *testing.T) { + cfg := &Config{Timeout: 3} + + t.Run("no-auth-ssh", func(t *testing.T) { + connected, status, err := CheckSSHBanner("tty.sdf.org", cfg) + + if err != nil { + t.Errorf("Expected: error != nil, got: %v ", err) + } + + if connected == false { + t.Errorf("Expected: connected == true, got: %v", connected) + } + if status != 0 { + t.Errorf("Expected: 0, got: %v", status) + } + }) + + t.Run("invalid-address", func(t *testing.T) { + connected, status, err := CheckSSHBanner("idontplaytheodds.com", cfg) + + if err == nil { + t.Errorf("Expected: error, got: %v ", err) + } + + if connected != false { + t.Errorf("Expected: connected == false, got: %v", connected) + } + if status != 1 { + t.Errorf("Expected: 1, got: %v", status) + } + }) + +} diff --git a/client/config.go b/client/config.go new file mode 100644 index 00000000..d095ae45 --- /dev/null +++ b/client/config.go @@ -0,0 +1,343 @@ +package client + +import ( + "context" + "crypto/tls" + "errors" + "net" + "net/http" + "net/url" + "regexp" + "strconv" + "time" + + "github.com/TwiN/logr" + "golang.org/x/oauth2" + "golang.org/x/oauth2/clientcredentials" + "google.golang.org/api/idtoken" +) + +const ( + defaultTimeout = 10 * time.Second +) + +var ( + ErrInvalidDNSResolver = errors.New("invalid DNS resolver specified. Required format is {proto}://{ip}:{port}") + ErrInvalidDNSResolverPort = errors.New("invalid DNS resolver port") + ErrInvalidClientOAuth2Config = errors.New("invalid oauth2 configuration: must define all fields for client credentials flow (token-url, client-id, client-secret, scopes)") + ErrInvalidClientIAPConfig = errors.New("invalid Identity-Aware-Proxy configuration: must define all fields for Google Identity-Aware-Proxy programmatic authentication (audience)") + ErrInvalidClientTLSConfig = errors.New("invalid TLS configuration: certificate-file and private-key-file must be specified") + + defaultConfig = Config{ + Insecure: false, + IgnoreRedirect: false, + Timeout: defaultTimeout, + Network: "ip", + } +) + +// GetDefaultConfig returns a copy of the default configuration +func GetDefaultConfig() *Config { + cfg := defaultConfig + return &cfg +} + +// Config is the configuration for clients +type Config struct { + // ProxyURL is the URL of the proxy to use for the client + ProxyURL string `yaml:"proxy-url,omitempty"` + + // Insecure determines whether to skip verifying the server's certificate chain and host name + Insecure bool `yaml:"insecure,omitempty"` + + // IgnoreRedirect determines whether to ignore redirects (true) or follow them (false, default) + IgnoreRedirect bool `yaml:"ignore-redirect,omitempty"` + + // Timeout for the client + Timeout time.Duration `yaml:"timeout"` + + // DNSResolver override for the HTTP client + // Expected format is {protocol}://{host}:{port}, e.g. tcp://8.8.8.8:53 + DNSResolver string `yaml:"dns-resolver,omitempty"` + + // OAuth2Config is the OAuth2 configuration used for the client. + // + // If non-nil, the http.Client returned by getHTTPClient will automatically retrieve a token if necessary. + // See configureOAuth2 for more details. + OAuth2Config *OAuth2Config `yaml:"oauth2,omitempty"` + + // IAPConfig is the Google Cloud Identity-Aware-Proxy configuration used for the client. (e.g. audience) + IAPConfig *IAPConfig `yaml:"identity-aware-proxy,omitempty"` + + httpClient *http.Client + + // Network (ip, ip4 or ip6) for the ICMP client + Network string `yaml:"network"` + + // TLS configuration (optional) + TLS *TLSConfig `yaml:"tls,omitempty"` +} + +// DNSResolverConfig is the parsed configuration from the DNSResolver config string. +type DNSResolverConfig struct { + Protocol string + Host string + Port string +} + +// OAuth2Config is the configuration for the OAuth2 client credentials flow +type OAuth2Config struct { + TokenURL string `yaml:"token-url"` // e.g. https://dev-12345678.okta.com/token + ClientID string `yaml:"client-id"` + ClientSecret string `yaml:"client-secret"` + Scopes []string `yaml:"scopes"` // e.g. ["openid"] +} + +// IAPConfig is the configuration for the Google Cloud Identity-Aware-Proxy +type IAPConfig struct { + Audience string `yaml:"audience"` // e.g. "toto.apps.googleusercontent.com" +} + +// TLSConfig is the configuration for mTLS configurations +type TLSConfig struct { + // CertificateFile is the public certificate for TLS in PEM format. + CertificateFile string `yaml:"certificate-file,omitempty"` + + // PrivateKeyFile is the private key file for TLS in PEM format. + PrivateKeyFile string `yaml:"private-key-file,omitempty"` + + RenegotiationSupport string `yaml:"renegotiation,omitempty"` +} + +// ValidateAndSetDefaults validates the client configuration and sets the default values if necessary +func (c *Config) ValidateAndSetDefaults() error { + if c.Timeout < time.Millisecond { + c.Timeout = 10 * time.Second + } + if c.HasCustomDNSResolver() { + // Validate the DNS resolver now to make sure it will not return an error later. + if _, err := c.parseDNSResolver(); err != nil { + return err + } + } + if c.HasOAuth2Config() && !c.OAuth2Config.isValid() { + return ErrInvalidClientOAuth2Config + } + if c.HasIAPConfig() && !c.IAPConfig.isValid() { + return ErrInvalidClientIAPConfig + } + if c.HasTLSConfig() { + if err := c.TLS.isValid(); err != nil { + return err + } + } + return nil +} + +// HasCustomDNSResolver returns whether a custom DNSResolver is configured +func (c *Config) HasCustomDNSResolver() bool { + return len(c.DNSResolver) > 0 +} + +// parseDNSResolver parses the DNS resolver into the DNSResolverConfig struct +func (c *Config) parseDNSResolver() (*DNSResolverConfig, error) { + re := regexp.MustCompile(`^(?P(.*))://(?P[A-Za-z0-9\-\.]+):(?P[0-9]+)?(.*)$`) + matches := re.FindStringSubmatch(c.DNSResolver) + if len(matches) == 0 { + return nil, ErrInvalidDNSResolver + } + r := make(map[string]string) + for i, k := range re.SubexpNames() { + if i != 0 && k != "" { + r[k] = matches[i] + } + } + port, err := strconv.Atoi(r["port"]) + if err != nil { + return nil, err + } + if port < 1 || port > 65535 { + return nil, ErrInvalidDNSResolverPort + } + return &DNSResolverConfig{ + Protocol: r["proto"], + Host: r["host"], + Port: r["port"], + }, nil +} + +// HasOAuth2Config returns true if the client has OAuth2 configuration parameters +func (c *Config) HasOAuth2Config() bool { + return c.OAuth2Config != nil +} + +// HasIAPConfig returns true if the client has IAP configuration parameters +func (c *Config) HasIAPConfig() bool { + return c.IAPConfig != nil +} + +// HasTLSConfig returns true if the client has client certificate parameters +func (c *Config) HasTLSConfig() bool { + return c.TLS != nil && len(c.TLS.CertificateFile) > 0 && len(c.TLS.PrivateKeyFile) > 0 +} + +// isValid() returns true if the IAP configuration is valid +func (c *IAPConfig) isValid() bool { + return len(c.Audience) > 0 +} + +// isValid() returns true if the OAuth2 configuration is valid +func (c *OAuth2Config) isValid() bool { + return len(c.TokenURL) > 0 && len(c.ClientID) > 0 && len(c.ClientSecret) > 0 && len(c.Scopes) > 0 +} + +// isValid() returns nil if the client tls certificates are valid, otherwise returns an error +func (t *TLSConfig) isValid() error { + if len(t.CertificateFile) > 0 && len(t.PrivateKeyFile) > 0 { + _, err := tls.LoadX509KeyPair(t.CertificateFile, t.PrivateKeyFile) + if err != nil { + return err + } + return nil + } + return ErrInvalidClientTLSConfig +} + +// getHTTPClient return an HTTP client matching the Config's parameters. +func (c *Config) getHTTPClient() *http.Client { + tlsConfig := &tls.Config{ + InsecureSkipVerify: c.Insecure, + } + if c.HasTLSConfig() && c.TLS.isValid() == nil { + tlsConfig = configureTLS(tlsConfig, *c.TLS) + } + if c.httpClient == nil { + c.httpClient = &http.Client{ + Timeout: c.Timeout, + Transport: &http.Transport{ + MaxIdleConns: 100, + MaxIdleConnsPerHost: 20, + Proxy: http.ProxyFromEnvironment, + TLSClientConfig: tlsConfig, + }, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + if c.IgnoreRedirect { + // Don't follow redirects + return http.ErrUseLastResponse + } + // Follow redirects + return nil + }, + } + if c.ProxyURL != "" { + proxyURL, err := url.Parse(c.ProxyURL) + if err != nil { + logr.Errorf("[client.getHTTPClient] THIS SHOULD NOT HAPPEN. Silently ignoring custom proxy due to error: %s", err.Error()) + } else { + c.httpClient.Transport.(*http.Transport).Proxy = http.ProxyURL(proxyURL) + } + } + if c.HasCustomDNSResolver() { + dnsResolver, err := c.parseDNSResolver() + if err != nil { + // We're ignoring the error, because it should have been validated on startup ValidateAndSetDefaults. + // It shouldn't happen, but if it does, we'll log it... Better safe than sorry ;) + logr.Errorf("[client.getHTTPClient] THIS SHOULD NOT HAPPEN. Silently ignoring invalid DNS resolver due to error: %s", err.Error()) + } else { + dialer := &net.Dialer{ + Resolver: &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + d := net.Dialer{} + return d.DialContext(ctx, dnsResolver.Protocol, dnsResolver.Host+":"+dnsResolver.Port) + }, + }, + } + c.httpClient.Transport.(*http.Transport).DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { + return dialer.DialContext(ctx, network, addr) + } + } + } + if c.HasOAuth2Config() && c.HasIAPConfig() { + logr.Errorf("[client.getHTTPClient] Error: Both Identity-Aware-Proxy and Oauth2 configuration are present.") + } else if c.HasOAuth2Config() { + c.httpClient = configureOAuth2(c.httpClient, *c.OAuth2Config) + } else if c.HasIAPConfig() { + c.httpClient = configureIAP(c.httpClient, *c.IAPConfig) + } + } + return c.httpClient +} + +// validateIAPToken returns a boolean that will define if the Google identity-aware-proxy token can be fetched +// and if is it valid. +func validateIAPToken(ctx context.Context, c IAPConfig) bool { + ts, err := idtoken.NewTokenSource(ctx, c.Audience) + if err != nil { + logr.Errorf("[client.ValidateIAPToken] Claiming Identity token failed: %s", err.Error()) + return false + } + tok, err := ts.Token() + if err != nil { + logr.Errorf("[client.ValidateIAPToken] Get Identity-Aware-Proxy token failed: %s", err.Error()) + return false + } + _, err = idtoken.Validate(ctx, tok.AccessToken, c.Audience) + if err != nil { + logr.Errorf("[client.ValidateIAPToken] Token Validation failed: %s", err.Error()) + return false + } + return true +} + +// configureIAP returns an HTTP client that will obtain and refresh Identity-Aware-Proxy tokens as necessary. +// The returned Client and its Transport should not be modified. +func configureIAP(httpClient *http.Client, c IAPConfig) *http.Client { + ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient) + if validateIAPToken(ctx, c) { + ts, err := idtoken.NewTokenSource(ctx, c.Audience) + if err != nil { + logr.Errorf("[client.configureIAP] Claiming Token Source failed: %s", err.Error()) + return httpClient + } + client := oauth2.NewClient(ctx, ts) + client.Timeout = httpClient.Timeout + return client + } + return httpClient +} + +// configureOAuth2 returns an HTTP client that will obtain and refresh tokens as necessary. +// The returned Client and its Transport should not be modified. +func configureOAuth2(httpClient *http.Client, c OAuth2Config) *http.Client { + oauth2cfg := clientcredentials.Config{ + ClientID: c.ClientID, + ClientSecret: c.ClientSecret, + Scopes: c.Scopes, + TokenURL: c.TokenURL, + } + ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient) + client := oauth2cfg.Client(ctx) + client.Timeout = httpClient.Timeout + return client +} + +// configureTLS returns a TLS Config that will enable mTLS +func configureTLS(tlsConfig *tls.Config, c TLSConfig) *tls.Config { + clientTLSCert, err := tls.LoadX509KeyPair(c.CertificateFile, c.PrivateKeyFile) + if err != nil { + logr.Errorf("[client.configureTLS] Failed to load certificate: %s", err.Error()) + return nil + } + tlsConfig.Certificates = []tls.Certificate{clientTLSCert} + tlsConfig.Renegotiation = tls.RenegotiateNever + renegotiationSupport := map[string]tls.RenegotiationSupport{ + "once": tls.RenegotiateOnceAsClient, + "freely": tls.RenegotiateFreelyAsClient, + "never": tls.RenegotiateNever, + } + if val, ok := renegotiationSupport[c.RenegotiationSupport]; ok { + tlsConfig.Renegotiation = val + } + return tlsConfig +} diff --git a/client/config_test.go b/client/config_test.go new file mode 100644 index 00000000..3f6043fb --- /dev/null +++ b/client/config_test.go @@ -0,0 +1,171 @@ +package client + +import ( + "net/http" + "net/url" + "testing" + "time" +) + +func TestConfig_getHTTPClient(t *testing.T) { + insecureConfig := &Config{Insecure: true} + insecureConfig.ValidateAndSetDefaults() + insecureClient := insecureConfig.getHTTPClient() + if !(insecureClient.Transport).(*http.Transport).TLSClientConfig.InsecureSkipVerify { + t.Error("expected Config.Insecure set to true to cause the HTTP client to skip certificate verification") + } + if insecureClient.Timeout != defaultTimeout { + t.Error("expected Config.Timeout to default the HTTP client to a timeout of 10s") + } + request, _ := http.NewRequest("GET", "", nil) + if err := insecureClient.CheckRedirect(request, nil); err != nil { + t.Error("expected Config.IgnoreRedirect set to false to cause the HTTP client's CheckRedirect to return nil") + } + + secureConfig := &Config{IgnoreRedirect: true, Timeout: 5 * time.Second} + secureConfig.ValidateAndSetDefaults() + secureClient := secureConfig.getHTTPClient() + if (secureClient.Transport).(*http.Transport).TLSClientConfig.InsecureSkipVerify { + t.Error("expected Config.Insecure set to false to cause the HTTP client to not skip certificate verification") + } + if secureClient.Timeout != 5*time.Second { + t.Error("expected Config.Timeout to cause the HTTP client to have a timeout of 5s") + } + request, _ = http.NewRequest("GET", "", nil) + if err := secureClient.CheckRedirect(request, nil); err != http.ErrUseLastResponse { + t.Error("expected Config.IgnoreRedirect set to true to cause the HTTP client's CheckRedirect to return http.ErrUseLastResponse") + } +} + +func TestConfig_ValidateAndSetDefaults_withCustomDNSResolver(t *testing.T) { + type args struct { + dnsResolver string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "with-valid-resolver", + args: args{ + dnsResolver: "tcp://1.1.1.1:53", + }, + wantErr: false, + }, + { + name: "with-invalid-resolver-port", + args: args{ + dnsResolver: "tcp://127.0.0.1:99999", + }, + wantErr: true, + }, + { + name: "with-invalid-resolver-format", + args: args{ + dnsResolver: "foobar", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := &Config{ + DNSResolver: tt.args.dnsResolver, + } + err := cfg.ValidateAndSetDefaults() + if (err != nil) != tt.wantErr { + t.Errorf("ValidateAndSetDefaults() error=%v, wantErr=%v", err, tt.wantErr) + } + }) + } +} + +func TestConfig_getHTTPClient_withCustomProxyURL(t *testing.T) { + proxyURL := "http://proxy.example.com:8080" + cfg := &Config{ + ProxyURL: proxyURL, + } + cfg.ValidateAndSetDefaults() + client := cfg.getHTTPClient() + transport := client.Transport.(*http.Transport) + if transport.Proxy == nil { + t.Errorf("expected Config.ProxyURL to set the HTTP client's proxy to %s", proxyURL) + } + req := &http.Request{ + URL: &url.URL{ + Scheme: "http", + Host: "www.example.com", + }, + } + expectProxyURL, err := transport.Proxy(req) + if err != nil { + t.Errorf("can't proxy the request %s", proxyURL) + } + if proxyURL != expectProxyURL.String() { + t.Errorf("expected Config.ProxyURL to set the HTTP client's proxy to %s", proxyURL) + } +} + +func TestConfig_TlsIsValid(t *testing.T) { + tests := []struct { + name string + cfg *Config + expectedErr bool + }{ + { + name: "good-tls-config", + cfg: &Config{TLS: &TLSConfig{CertificateFile: "../testdata/cert.pem", PrivateKeyFile: "../testdata/cert.key"}}, + expectedErr: false, + }, + { + name: "missing-certificate-file", + cfg: &Config{TLS: &TLSConfig{CertificateFile: "doesnotexist", PrivateKeyFile: "../testdata/cert.key"}}, + expectedErr: true, + }, + { + name: "bad-certificate-file", + cfg: &Config{TLS: &TLSConfig{CertificateFile: "../testdata/badcert.pem", PrivateKeyFile: "../testdata/cert.key"}}, + expectedErr: true, + }, + { + name: "no-certificate-file", + cfg: &Config{TLS: &TLSConfig{CertificateFile: "", PrivateKeyFile: "../testdata/cert.key"}}, + expectedErr: true, + }, + { + name: "missing-private-key-file", + cfg: &Config{TLS: &TLSConfig{CertificateFile: "../testdata/cert.pem", PrivateKeyFile: "doesnotexist"}}, + expectedErr: true, + }, + { + name: "no-private-key-file", + cfg: &Config{TLS: &TLSConfig{CertificateFile: "../testdata/cert.pem", PrivateKeyFile: ""}}, + expectedErr: true, + }, + { + name: "bad-private-key-file", + cfg: &Config{TLS: &TLSConfig{CertificateFile: "../testdata/cert.pem", PrivateKeyFile: "../testdata/badcert.key"}}, + expectedErr: true, + }, + { + name: "bad-certificate-and-private-key-file", + cfg: &Config{TLS: &TLSConfig{CertificateFile: "../testdata/badcert.pem", PrivateKeyFile: "../testdata/badcert.key"}}, + expectedErr: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.cfg.TLS.isValid() + if (err != nil) != test.expectedErr { + t.Errorf("expected the existence of an error to be %v, got %v", test.expectedErr, err) + return + } + if !test.expectedErr { + if test.cfg.TLS.isValid() != nil { + t.Error("cfg.TLS.isValid() returned an error even though no error was expected") + } + } + }) + } +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 00000000..61e1b128 --- /dev/null +++ b/config/config.go @@ -0,0 +1,473 @@ +package config + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "strings" + "time" + + "github.com/TwiN/deepmerge" + "github.com/TwiN/gatus/v5/alerting" + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/alerting/provider" + "github.com/TwiN/gatus/v5/config/connectivity" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/config/maintenance" + "github.com/TwiN/gatus/v5/config/remote" + "github.com/TwiN/gatus/v5/config/ui" + "github.com/TwiN/gatus/v5/config/web" + "github.com/TwiN/gatus/v5/security" + "github.com/TwiN/gatus/v5/storage" + "github.com/TwiN/logr" + "github.com/gofiber/fiber/v2/log" + "gopkg.in/yaml.v3" +) + +const ( + // DefaultConfigurationFilePath is the default path that will be used to search for the configuration file + // if a custom path isn't configured through the GATUS_CONFIG_PATH environment variable + DefaultConfigurationFilePath = "config/config.yaml" + + // DefaultFallbackConfigurationFilePath is the default fallback path that will be used to search for the + // configuration file if DefaultConfigurationFilePath didn't work + DefaultFallbackConfigurationFilePath = "config/config.yml" +) + +var ( + // ErrNoEndpointInConfig is an error returned when a configuration file or directory has no endpoints configured + ErrNoEndpointInConfig = errors.New("configuration should contain at least 1 endpoint") + + // ErrConfigFileNotFound is an error returned when a configuration file could not be found + ErrConfigFileNotFound = errors.New("configuration file not found") + + // ErrInvalidSecurityConfig is an error returned when the security configuration is invalid + ErrInvalidSecurityConfig = errors.New("invalid security configuration") + + // errEarlyReturn is returned to break out of a loop from a callback early + errEarlyReturn = errors.New("early escape") +) + +// Config is the main configuration structure +type Config struct { + // Debug Whether to enable debug logs + // Deprecated: Use the GATUS_LOG_LEVEL environment variable instead + Debug bool `yaml:"debug,omitempty"` + + // Metrics Whether to expose metrics at /metrics + Metrics bool `yaml:"metrics,omitempty"` + + // SkipInvalidConfigUpdate Whether to make the application ignore invalid configuration + // if the configuration file is updated while the application is running + SkipInvalidConfigUpdate bool `yaml:"skip-invalid-config-update,omitempty"` + + // DisableMonitoringLock Whether to disable the monitoring lock + // The monitoring lock is what prevents multiple endpoints from being processed at the same time. + // Disabling this may lead to inaccurate response times + DisableMonitoringLock bool `yaml:"disable-monitoring-lock,omitempty"` + + // Security is the configuration for securing access to Gatus + Security *security.Config `yaml:"security,omitempty"` + + // Alerting is the configuration for alerting providers + Alerting *alerting.Config `yaml:"alerting,omitempty"` + + // Endpoints is the list of endpoints to monitor + Endpoints []*endpoint.Endpoint `yaml:"endpoints,omitempty"` + + // ExternalEndpoints is the list of all external endpoints + ExternalEndpoints []*endpoint.ExternalEndpoint `yaml:"external-endpoints,omitempty"` + + // Storage is the configuration for how the data is stored + Storage *storage.Config `yaml:"storage,omitempty"` + + // Web is the web configuration for the application + Web *web.Config `yaml:"web,omitempty"` + + // UI is the configuration for the UI + UI *ui.Config `yaml:"ui,omitempty"` + + // Maintenance is the configuration for creating a maintenance window in which no alerts are sent + Maintenance *maintenance.Config `yaml:"maintenance,omitempty"` + + // Remote is the configuration for remote Gatus instances + // WARNING: This is in ALPHA and may change or be completely removed in the future + Remote *remote.Config `yaml:"remote,omitempty"` + + // Connectivity is the configuration for connectivity + Connectivity *connectivity.Config `yaml:"connectivity,omitempty"` + + configPath string // path to the file or directory from which config was loaded + lastFileModTime time.Time // last modification time +} + +func (config *Config) GetEndpointByKey(key string) *endpoint.Endpoint { + for i := 0; i < len(config.Endpoints); i++ { + ep := config.Endpoints[i] + if ep.Key() == key { + return ep + } + } + return nil +} + +func (config *Config) GetExternalEndpointByKey(key string) *endpoint.ExternalEndpoint { + for i := 0; i < len(config.ExternalEndpoints); i++ { + ee := config.ExternalEndpoints[i] + if ee.Key() == key { + return ee + } + } + return nil +} + +// HasLoadedConfigurationBeenModified returns whether one of the file that the +// configuration has been loaded from has been modified since it was last read +func (config *Config) HasLoadedConfigurationBeenModified() bool { + lastMod := config.lastFileModTime.Unix() + fileInfo, err := os.Stat(config.configPath) + if err != nil { + return false + } + if fileInfo.IsDir() { + err = walkConfigDir(config.configPath, func(path string, d fs.DirEntry, err error) error { + if info, err := d.Info(); err == nil && lastMod < info.ModTime().Unix() { + return errEarlyReturn + } + return nil + }) + return errors.Is(err, errEarlyReturn) + } + return !fileInfo.ModTime().IsZero() && config.lastFileModTime.Unix() < fileInfo.ModTime().Unix() +} + +// UpdateLastFileModTime refreshes Config.lastFileModTime +func (config *Config) UpdateLastFileModTime() { + config.lastFileModTime = time.Now() +} + +// LoadConfiguration loads the full configuration composed of the main configuration file +// and all composed configuration files +func LoadConfiguration(configPath string) (*Config, error) { + var configBytes []byte + var fileInfo os.FileInfo + var usedConfigPath string + // Figure out what config path we'll use (either configPath or the default config path) + for _, configurationPath := range []string{configPath, DefaultConfigurationFilePath, DefaultFallbackConfigurationFilePath} { + if len(configurationPath) == 0 { + continue + } + var err error + fileInfo, err = os.Stat(configurationPath) + if err != nil { + continue + } + usedConfigPath = configurationPath + break + } + if len(usedConfigPath) == 0 { + return nil, ErrConfigFileNotFound + } + var config *Config + if fileInfo.IsDir() { + err := walkConfigDir(configPath, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return fmt.Errorf("error walking path %s: %w", path, err) + } + if strings.Contains(path, "..") { + logr.Warnf("[config.LoadConfiguration] Ignoring configuration from %s", path) + return nil + } + logr.Infof("[config.LoadConfiguration] Reading configuration from %s", path) + data, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("error reading configuration from file %s: %w", path, err) + } + configBytes, err = deepmerge.YAML(configBytes, data) + return err + }) + if err != nil { + return nil, fmt.Errorf("error reading configuration from directory %s: %w", usedConfigPath, err) + } + } else { + logr.Infof("[config.LoadConfiguration] Reading configuration from configFile=%s", usedConfigPath) + if data, err := os.ReadFile(usedConfigPath); err != nil { + return nil, fmt.Errorf("error reading configuration from directory %s: %w", usedConfigPath, err) + } else { + configBytes = data + } + } + if len(configBytes) == 0 { + return nil, ErrConfigFileNotFound + } + config, err := parseAndValidateConfigBytes(configBytes) + if err != nil { + return nil, fmt.Errorf("error parsing config: %w", err) + } + config.configPath = usedConfigPath + config.UpdateLastFileModTime() + return config, nil +} + +// walkConfigDir is a wrapper for filepath.WalkDir that strips directories and non-config files +func walkConfigDir(path string, fn fs.WalkDirFunc) error { + if len(path) == 0 { + // If the user didn't provide a directory, we'll just use the default config file, so we can return nil now. + return nil + } + return filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return nil + } + if d == nil || d.IsDir() { + return nil + } + ext := filepath.Ext(path) + if ext != ".yml" && ext != ".yaml" { + return nil + } + return fn(path, d, err) + }) +} + +// parseAndValidateConfigBytes parses a Gatus configuration file into a Config struct and validates its parameters +func parseAndValidateConfigBytes(yamlBytes []byte) (config *Config, err error) { + // Replace $$ with __GATUS_LITERAL_DOLLAR_SIGN__ to prevent os.ExpandEnv from treating "$$" as if it was an + // environment variable. This allows Gatus to support literal "$" in the configuration file. + yamlBytes = []byte(strings.ReplaceAll(string(yamlBytes), "$$", "__GATUS_LITERAL_DOLLAR_SIGN__")) + // Expand environment variables + yamlBytes = []byte(os.ExpandEnv(string(yamlBytes))) + // Replace __GATUS_LITERAL_DOLLAR_SIGN__ with "$" to restore the literal "$" in the configuration file + yamlBytes = []byte(strings.ReplaceAll(string(yamlBytes), "__GATUS_LITERAL_DOLLAR_SIGN__", "$")) + // Parse configuration file + if err = yaml.Unmarshal(yamlBytes, &config); err != nil { + return + } + // Check if the configuration file at least has endpoints configured + if config == nil || config.Endpoints == nil || len(config.Endpoints) == 0 { + err = ErrNoEndpointInConfig + } else { + // XXX: Remove this in v6.0.0 + if config.Debug { + logr.Warn("WARNING: The 'debug' configuration has been deprecated and will be removed in v6.0.0") + logr.Warn("WARNING: Please use the GATUS_LOG_LEVEL environment variable instead") + } + // XXX: End of v6.0.0 removals + validateAlertingConfig(config.Alerting, config.Endpoints, config.ExternalEndpoints) + if err := validateSecurityConfig(config); err != nil { + return nil, err + } + if err := validateEndpointsConfig(config); err != nil { + return nil, err + } + if err := validateWebConfig(config); err != nil { + return nil, err + } + if err := validateUIConfig(config); err != nil { + return nil, err + } + if err := validateMaintenanceConfig(config); err != nil { + return nil, err + } + if err := validateStorageConfig(config); err != nil { + return nil, err + } + if err := validateRemoteConfig(config); err != nil { + return nil, err + } + if err := validateConnectivityConfig(config); err != nil { + return nil, err + } + } + return +} + +func validateConnectivityConfig(config *Config) error { + if config.Connectivity != nil { + return config.Connectivity.ValidateAndSetDefaults() + } + return nil +} + +func validateRemoteConfig(config *Config) error { + if config.Remote != nil { + if err := config.Remote.ValidateAndSetDefaults(); err != nil { + return err + } + } + return nil +} + +func validateStorageConfig(config *Config) error { + if config.Storage == nil { + config.Storage = &storage.Config{ + Type: storage.TypeMemory, + } + } else { + if err := config.Storage.ValidateAndSetDefaults(); err != nil { + return err + } + } + return nil +} + +func validateMaintenanceConfig(config *Config) error { + if config.Maintenance == nil { + config.Maintenance = maintenance.GetDefaultConfig() + } else { + if err := config.Maintenance.ValidateAndSetDefaults(); err != nil { + return err + } + } + return nil +} + +func validateUIConfig(config *Config) error { + if config.UI == nil { + config.UI = ui.GetDefaultConfig() + } else { + if err := config.UI.ValidateAndSetDefaults(); err != nil { + return err + } + } + return nil +} + +func validateWebConfig(config *Config) error { + if config.Web == nil { + config.Web = web.GetDefaultConfig() + } else { + return config.Web.ValidateAndSetDefaults() + } + return nil +} + +func validateEndpointsConfig(config *Config) error { + duplicateValidationMap := make(map[string]bool) + // Validate endpoints + for _, ep := range config.Endpoints { + logr.Debugf("[config.validateEndpointsConfig] Validating endpoint with key %s", ep.Key()) + if endpointKey := ep.Key(); duplicateValidationMap[endpointKey] { + return fmt.Errorf("invalid endpoint %s: name and group combination must be unique", ep.Key()) + } else { + duplicateValidationMap[endpointKey] = true + } + if err := ep.ValidateAndSetDefaults(); err != nil { + return fmt.Errorf("invalid endpoint %s: %w", ep.Key(), err) + } + } + logr.Infof("[config.validateEndpointsConfig] Validated %d endpoints", len(config.Endpoints)) + // Validate external endpoints + for _, ee := range config.ExternalEndpoints { + logr.Debugf("[config.validateEndpointsConfig] Validating external endpoint '%s'", ee.Name) + if endpointKey := ee.Key(); duplicateValidationMap[endpointKey] { + return fmt.Errorf("invalid external endpoint %s: name and group combination must be unique", ee.Key()) + } else { + duplicateValidationMap[endpointKey] = true + } + if err := ee.ValidateAndSetDefaults(); err != nil { + return fmt.Errorf("invalid external endpoint %s: %w", ee.Key(), err) + } + } + logr.Infof("[config.validateEndpointsConfig] Validated %d external endpoints", len(config.ExternalEndpoints)) + return nil +} + +func validateSecurityConfig(config *Config) error { + if config.Security != nil { + if config.Security.IsValid() { + logr.Debug("[config.validateSecurityConfig] Basic security configuration has been validated") + } else { + // If there was an attempt to configure security, then it must mean that some confidential or private + // data are exposed. As a result, we'll force a panic because it's better to be safe than sorry. + return ErrInvalidSecurityConfig + } + } + return nil +} + +// validateAlertingConfig validates the alerting configuration +// Note that the alerting configuration has to be validated before the endpoint configuration, because the default alert +// returned by provider.AlertProvider.GetDefaultAlert() must be parsed before endpoint.Endpoint.ValidateAndSetDefaults() +// sets the default alert values when none are set. +func validateAlertingConfig(alertingConfig *alerting.Config, endpoints []*endpoint.Endpoint, externalEndpoints []*endpoint.ExternalEndpoint) { + if alertingConfig == nil { + logr.Info("[config.validateAlertingConfig] Alerting is not configured") + return + } + alertTypes := []alert.Type{ + alert.TypeAWSSES, + alert.TypeCustom, + alert.TypeDiscord, + alert.TypeEmail, + alert.TypeGitHub, + alert.TypeGitLab, + alert.TypeGitea, + alert.TypeGoogleChat, + alert.TypeGotify, + alert.TypeJetBrainsSpace, + alert.TypeMatrix, + alert.TypeMattermost, + alert.TypeMessagebird, + alert.TypeNtfy, + alert.TypeOpsgenie, + alert.TypePagerDuty, + alert.TypePushover, + alert.TypeSlack, + alert.TypeTeams, + alert.TypeTeamsWorkflows, + alert.TypeTelegram, + alert.TypeTwilio, + alert.TypeZulip, + alert.TypeIncidentIO, + } + var validProviders, invalidProviders []alert.Type + for _, alertType := range alertTypes { + alertProvider := alertingConfig.GetAlertingProviderByAlertType(alertType) + if alertProvider != nil { + if err := alertProvider.Validate(); err == nil { + // Parse alerts with the provider's default alert + if alertProvider.GetDefaultAlert() != nil { + for _, ep := range endpoints { + for alertIndex, endpointAlert := range ep.Alerts { + if alertType == endpointAlert.Type { + logr.Debugf("[config.validateAlertingConfig] Parsing alert %d with default alert for provider=%s in endpoint with key=%s", alertIndex, alertType, ep.Key()) + provider.MergeProviderDefaultAlertIntoEndpointAlert(alertProvider.GetDefaultAlert(), endpointAlert) + // Validate the endpoint alert's overrides, if applicable + if len(endpointAlert.ProviderOverride) > 0 { + if err = alertProvider.ValidateOverrides(ep.Group, endpointAlert); err != nil { + log.Warnf("[config.validateAlertingConfig] endpoint with key=%s has invalid overrides for provider=%s: %s", ep.Key(), alertType, err.Error()) + } + } + } + } + } + for _, ee := range externalEndpoints { + for alertIndex, endpointAlert := range ee.Alerts { + if alertType == endpointAlert.Type { + logr.Debugf("[config.validateAlertingConfig] Parsing alert %d with default alert for provider=%s in endpoint with key=%s", alertIndex, alertType, ee.Key()) + provider.MergeProviderDefaultAlertIntoEndpointAlert(alertProvider.GetDefaultAlert(), endpointAlert) + // Validate the endpoint alert's overrides, if applicable + if len(endpointAlert.ProviderOverride) > 0 { + if err = alertProvider.ValidateOverrides(ee.Group, endpointAlert); err != nil { + log.Warnf("[config.validateAlertingConfig] endpoint with key=%s has invalid overrides for provider=%s: %s", ee.Key(), alertType, err.Error()) + } + } + } + } + } + } + validProviders = append(validProviders, alertType) + } else { + logr.Warnf("[config.validateAlertingConfig] Ignoring provider=%s due to error=%s", alertType, err.Error()) + invalidProviders = append(invalidProviders, alertType) + alertingConfig.SetAlertingProviderToNil(alertProvider) + } + } else { + invalidProviders = append(invalidProviders, alertType) + } + } + logr.Infof("[config.validateAlertingConfig] configuredProviders=%s; ignoredProviders=%s", validProviders, invalidProviders) +} diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 00000000..8ed3ddf1 --- /dev/null +++ b/config/config_test.go @@ -0,0 +1,1940 @@ +package config + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "testing" + "time" + + "github.com/TwiN/gatus/v5/alerting" + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/alerting/provider" + "github.com/TwiN/gatus/v5/alerting/provider/awsses" + "github.com/TwiN/gatus/v5/alerting/provider/custom" + "github.com/TwiN/gatus/v5/alerting/provider/discord" + "github.com/TwiN/gatus/v5/alerting/provider/email" + "github.com/TwiN/gatus/v5/alerting/provider/gitea" + "github.com/TwiN/gatus/v5/alerting/provider/github" + "github.com/TwiN/gatus/v5/alerting/provider/gitlab" + "github.com/TwiN/gatus/v5/alerting/provider/googlechat" + "github.com/TwiN/gatus/v5/alerting/provider/gotify" + "github.com/TwiN/gatus/v5/alerting/provider/jetbrainsspace" + "github.com/TwiN/gatus/v5/alerting/provider/matrix" + "github.com/TwiN/gatus/v5/alerting/provider/mattermost" + "github.com/TwiN/gatus/v5/alerting/provider/messagebird" + "github.com/TwiN/gatus/v5/alerting/provider/ntfy" + "github.com/TwiN/gatus/v5/alerting/provider/opsgenie" + "github.com/TwiN/gatus/v5/alerting/provider/pagerduty" + "github.com/TwiN/gatus/v5/alerting/provider/pushover" + "github.com/TwiN/gatus/v5/alerting/provider/slack" + "github.com/TwiN/gatus/v5/alerting/provider/teams" + "github.com/TwiN/gatus/v5/alerting/provider/teamsworkflows" + "github.com/TwiN/gatus/v5/alerting/provider/telegram" + "github.com/TwiN/gatus/v5/alerting/provider/twilio" + "github.com/TwiN/gatus/v5/alerting/provider/zulip" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/config/web" + "github.com/TwiN/gatus/v5/storage" + "gopkg.in/yaml.v3" +) + +func TestLoadConfiguration(t *testing.T) { + yes := true + dir := t.TempDir() + scenarios := []struct { + name string + configPath string // value to pass as the configPath parameter in LoadConfiguration + pathAndFiles map[string]string // files to create in dir + expectedConfig *Config + expectedError error + }{ + { + name: "empty-config-file", + configPath: filepath.Join(dir, "config.yaml"), + pathAndFiles: map[string]string{ + "config.yaml": "", + }, + expectedError: ErrConfigFileNotFound, + }, + { + name: "config-file-that-does-not-exist", + configPath: filepath.Join(dir, "config.yaml"), + expectedError: ErrConfigFileNotFound, + }, + { + name: "config-file-with-endpoint-that-has-no-url", + configPath: filepath.Join(dir, "config.yaml"), + pathAndFiles: map[string]string{ + "config.yaml": ` +endpoints: + - name: website`, + }, + expectedError: endpoint.ErrEndpointWithNoURL, + }, + { + name: "config-file-with-endpoint-that-has-no-conditions", + configPath: filepath.Join(dir, "config.yaml"), + pathAndFiles: map[string]string{ + "config.yaml": ` +endpoints: + - name: website + url: https://twin.sh/health`, + }, + expectedError: endpoint.ErrEndpointWithNoCondition, + }, + { + name: "config-file", + configPath: filepath.Join(dir, "config.yaml"), + pathAndFiles: map[string]string{ + "config.yaml": ` +endpoints: + - name: website + url: https://twin.sh/health + conditions: + - "[STATUS] == 200"`, + }, + expectedConfig: &Config{ + Endpoints: []*endpoint.Endpoint{ + { + Name: "website", + URL: "https://twin.sh/health", + Conditions: []endpoint.Condition{"[STATUS] == 200"}, + }, + }, + }, + }, + { + name: "empty-dir", + configPath: dir, + pathAndFiles: map[string]string{}, + expectedError: ErrConfigFileNotFound, + }, + { + name: "dir-with-empty-config-file", + configPath: dir, + pathAndFiles: map[string]string{ + "config.yaml": "", + }, + expectedError: ErrNoEndpointInConfig, + }, + { + name: "dir-with-two-config-files", + configPath: dir, + pathAndFiles: map[string]string{ + "config.yaml": `endpoints: + - name: one + url: https://example.com + conditions: + - "[CONNECTED] == true" + - "[STATUS] == 200" + + - name: two + url: https://example.org + conditions: + - "len([BODY]) > 0"`, + "config.yml": `endpoints: + - name: three + url: https://twin.sh/health + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP"`, + }, + expectedConfig: &Config{ + Endpoints: []*endpoint.Endpoint{ + { + Name: "one", + URL: "https://example.com", + Conditions: []endpoint.Condition{"[CONNECTED] == true", "[STATUS] == 200"}, + }, + { + Name: "two", + URL: "https://example.org", + Conditions: []endpoint.Condition{"len([BODY]) > 0"}, + }, + { + Name: "three", + URL: "https://twin.sh/health", + Conditions: []endpoint.Condition{"[STATUS] == 200", "[BODY].status == UP"}, + }, + }, + }, + }, + { + name: "dir-with-2-config-files-deep-merge-with-map-slice-and-primitive", + configPath: dir, + pathAndFiles: map[string]string{ + "a.yaml": ` +metrics: true + +alerting: + slack: + webhook-url: https://hooks.slack.com/services/xxx/yyy/zzz + default-alert: + enabled: true + +endpoints: + - name: example + url: https://example.org + interval: 5s + conditions: + - "[STATUS] == 200"`, + "b.yaml": ` + +alerting: + discord: + webhook-url: https://discord.com/api/webhooks/xxx/yyy + +external-endpoints: + - name: ext-ep-test + token: "potato" + alerts: + - type: slack + +endpoints: + - name: frontend + url: https://example.com + conditions: + - "[STATUS] == 200"`, + }, + expectedConfig: &Config{ + Metrics: true, + Alerting: &alerting.Config{ + Discord: &discord.AlertProvider{DefaultConfig: discord.Config{WebhookURL: "https://discord.com/api/webhooks/xxx/yyy"}}, + Slack: &slack.AlertProvider{DefaultConfig: slack.Config{WebhookURL: "https://hooks.slack.com/services/xxx/yyy/zzz"}, DefaultAlert: &alert.Alert{Enabled: &yes}}, + }, + ExternalEndpoints: []*endpoint.ExternalEndpoint{ + { + Name: "ext-ep-test", + Token: "potato", + Alerts: []*alert.Alert{ + { + Type: alert.TypeSlack, + FailureThreshold: 3, + SuccessThreshold: 2, + }, + }, + }, + }, + Endpoints: []*endpoint.Endpoint{ + { + Name: "example", + URL: "https://example.org", + Interval: 5 * time.Second, + Conditions: []endpoint.Condition{"[STATUS] == 200"}, + }, + { + Name: "frontend", + URL: "https://example.com", + Conditions: []endpoint.Condition{"[STATUS] == 200"}, + }, + }, + }, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + for path, content := range scenario.pathAndFiles { + if err := os.WriteFile(filepath.Join(dir, path), []byte(content), 0644); err != nil { + t.Fatalf("[%s] failed to write file: %v", scenario.name, err) + } + } + defer func(pathAndFiles map[string]string) { + for path := range pathAndFiles { + _ = os.Remove(filepath.Join(dir, path)) + } + }(scenario.pathAndFiles) + config, err := LoadConfiguration(scenario.configPath) + if !errors.Is(err, scenario.expectedError) { + t.Errorf("[%s] expected error %v, got %v", scenario.name, scenario.expectedError, err) + return + } else if err != nil && errors.Is(err, scenario.expectedError) { + return + } + // parse the expected output so that expectations are closer to reality (under the right circumstances, even I can be poetic) + expectedConfigAsYAML, _ := yaml.Marshal(scenario.expectedConfig) + expectedConfigAfterBeingParsedAndValidated, err := parseAndValidateConfigBytes(expectedConfigAsYAML) + if err != nil { + t.Fatalf("[%s] failed to parse expected config: %v", scenario.name, err) + } + // Marshal em' before comparing em' so that we don't have to deal with formatting and ordering + actualConfigAsYAML, err := yaml.Marshal(config) + if err != nil { + t.Fatalf("[%s] failed to marshal actual config: %v", scenario.name, err) + } + expectedConfigAfterBeingParsedAndValidatedAsYAML, _ := yaml.Marshal(expectedConfigAfterBeingParsedAndValidated) + if string(actualConfigAsYAML) != string(expectedConfigAfterBeingParsedAndValidatedAsYAML) { + t.Errorf("[%s] expected config %s, got %s", scenario.name, string(expectedConfigAfterBeingParsedAndValidatedAsYAML), string(actualConfigAsYAML)) + } + }) + } +} + +func TestConfig_HasLoadedConfigurationBeenModified(t *testing.T) { + t.Parallel() + dir := t.TempDir() + + configFilePath := filepath.Join(dir, "config.yaml") + _ = os.WriteFile(configFilePath, []byte(`endpoints: + - name: website + url: https://twin.sh/health + conditions: + - "[STATUS] == 200" +`), 0644) + + t.Run("config-file-as-config-path", func(t *testing.T) { + config, err := LoadConfiguration(configFilePath) + if err != nil { + t.Fatalf("failed to load configuration: %v", err) + } + if config.HasLoadedConfigurationBeenModified() { + t.Errorf("expected config.HasLoadedConfigurationBeenModified() to return false because nothing has happened since it was created") + } + time.Sleep(time.Second) // Because the file mod time only has second precision, we have to wait for a second + // Update the config file + if err = os.WriteFile(filepath.Join(dir, "config.yaml"), []byte(`endpoints: + - name: website + url: https://twin.sh/health + conditions: + - "[STATUS] == 200"`), 0644); err != nil { + t.Fatalf("failed to overwrite config file: %v", err) + } + if !config.HasLoadedConfigurationBeenModified() { + t.Errorf("expected config.HasLoadedConfigurationBeenModified() to return true because a new file has been added in the directory") + } + }) + t.Run("config-directory-as-config-path", func(t *testing.T) { + config, err := LoadConfiguration(dir) + if err != nil { + t.Fatalf("failed to load configuration: %v", err) + } + if config.HasLoadedConfigurationBeenModified() { + t.Errorf("expected config.HasLoadedConfigurationBeenModified() to return false because nothing has happened since it was created") + } + time.Sleep(time.Second) // Because the file mod time only has second precision, we have to wait for a second + // Update the config file + if err = os.WriteFile(filepath.Join(dir, "metrics.yaml"), []byte(`metrics: true`), 0644); err != nil { + t.Fatalf("failed to overwrite config file: %v", err) + } + if !config.HasLoadedConfigurationBeenModified() { + t.Errorf("expected config.HasLoadedConfigurationBeenModified() to return true because a new file has been added in the directory") + } + }) +} + +func TestParseAndValidateConfigBytes(t *testing.T) { + file := t.TempDir() + "/test.db" + config, err := parseAndValidateConfigBytes([]byte(fmt.Sprintf(` +storage: + type: sqlite + path: %s + +maintenance: + enabled: true + start: 00:00 + duration: 4h + every: [Monday, Thursday] + +ui: + title: T + header: H + link: https://example.org + buttons: + - name: "Home" + link: "https://example.org" + - name: "Status page" + link: "https://status.example.org" + +external-endpoints: + - name: ext-ep-test + group: core + token: "potato" + +endpoints: + - name: website + url: https://twin.sh/health + interval: 15s + conditions: + - "[STATUS] == 200" + + - name: github + url: https://api.github.com/healthz + client: + insecure: true + ignore-redirect: true + timeout: 5s + conditions: + - "[STATUS] != 400" + - "[STATUS] != 500" + + - name: example + url: https://example.com/ + interval: 30m + client: + insecure: true + conditions: + - "[STATUS] == 200" +`, file))) + if err != nil { + t.Error("expected no error, got", err.Error()) + } + if config == nil { + t.Fatal("Config shouldn't have been nil") + } + if config.Storage == nil || config.Storage.Path != file || config.Storage.Type != storage.TypeSQLite { + t.Error("expected storage to be set to sqlite, got", config.Storage) + } + if config.UI == nil || config.UI.Title != "T" || config.UI.Header != "H" || config.UI.Link != "https://example.org" || len(config.UI.Buttons) != 2 || config.UI.Buttons[0].Name != "Home" || config.UI.Buttons[0].Link != "https://example.org" || config.UI.Buttons[1].Name != "Status page" || config.UI.Buttons[1].Link != "https://status.example.org" { + t.Error("expected ui to be set to T, H, https://example.org, 2 buttons, Home and Status page, got", config.UI) + } + if mc := config.Maintenance; mc == nil || mc.Start != "00:00" || !mc.IsEnabled() || mc.Duration != 4*time.Hour || len(mc.Every) != 2 { + t.Error("Expected Config.Maintenance to be configured properly") + } + if len(config.ExternalEndpoints) != 1 { + t.Error("Should have returned one external endpoint") + } + if config.ExternalEndpoints[0].Name != "ext-ep-test" { + t.Errorf("Name should have been %s", "ext-ep-test") + } + if config.ExternalEndpoints[0].Group != "core" { + t.Errorf("Group should have been %s", "core") + } + if config.ExternalEndpoints[0].Token != "potato" { + t.Errorf("Token should have been %s", "potato") + } + + if len(config.Endpoints) != 3 { + t.Error("Should have returned two endpoints") + } + if config.Endpoints[0].URL != "https://twin.sh/health" { + t.Errorf("URL should have been %s", "https://twin.sh/health") + } + if config.Endpoints[0].Method != "GET" { + t.Errorf("Method should have been %s (default)", "GET") + } + if config.Endpoints[0].Interval != 15*time.Second { + t.Errorf("Interval should have been %s", 15*time.Second) + } + if config.Endpoints[0].ClientConfig.Insecure != client.GetDefaultConfig().Insecure { + t.Errorf("ClientConfig.Insecure should have been %v, got %v", true, config.Endpoints[0].ClientConfig.Insecure) + } + if config.Endpoints[0].ClientConfig.IgnoreRedirect != client.GetDefaultConfig().IgnoreRedirect { + t.Errorf("ClientConfig.IgnoreRedirect should have been %v, got %v", true, config.Endpoints[0].ClientConfig.IgnoreRedirect) + } + if config.Endpoints[0].ClientConfig.Timeout != client.GetDefaultConfig().Timeout { + t.Errorf("ClientConfig.Timeout should have been %v, got %v", client.GetDefaultConfig().Timeout, config.Endpoints[0].ClientConfig.Timeout) + } + if len(config.Endpoints[0].Conditions) != 1 { + t.Errorf("There should have been %d conditions", 1) + } + if config.Endpoints[1].URL != "https://api.github.com/healthz" { + t.Errorf("URL should have been %s", "https://api.github.com/healthz") + } + if config.Endpoints[1].Method != "GET" { + t.Errorf("Method should have been %s (default)", "GET") + } + if config.Endpoints[1].Interval != 60*time.Second { + t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second) + } + if !config.Endpoints[1].ClientConfig.Insecure { + t.Errorf("ClientConfig.Insecure should have been %v, got %v", true, config.Endpoints[1].ClientConfig.Insecure) + } + if !config.Endpoints[1].ClientConfig.IgnoreRedirect { + t.Errorf("ClientConfig.IgnoreRedirect should have been %v, got %v", true, config.Endpoints[1].ClientConfig.IgnoreRedirect) + } + if config.Endpoints[1].ClientConfig.Timeout != 5*time.Second { + t.Errorf("ClientConfig.Timeout should have been %v, got %v", 5*time.Second, config.Endpoints[1].ClientConfig.Timeout) + } + if len(config.Endpoints[1].Conditions) != 2 { + t.Errorf("There should have been %d conditions", 2) + } + if config.Endpoints[2].URL != "https://example.com/" { + t.Errorf("URL should have been %s", "https://example.com/") + } + if config.Endpoints[2].Method != "GET" { + t.Errorf("Method should have been %s (default)", "GET") + } + if config.Endpoints[2].Interval != 30*time.Minute { + t.Errorf("Interval should have been %s, because it is the default value", 30*time.Minute) + } + if !config.Endpoints[2].ClientConfig.Insecure { + t.Errorf("ClientConfig.Insecure should have been %v, got %v", true, config.Endpoints[2].ClientConfig.Insecure) + } + if config.Endpoints[2].ClientConfig.IgnoreRedirect { + t.Errorf("ClientConfig.IgnoreRedirect should have been %v by default, got %v", false, config.Endpoints[2].ClientConfig.IgnoreRedirect) + } + if config.Endpoints[2].ClientConfig.Timeout != 10*time.Second { + t.Errorf("ClientConfig.Timeout should have been %v by default, got %v", 10*time.Second, config.Endpoints[2].ClientConfig.Timeout) + } + if len(config.Endpoints[2].Conditions) != 1 { + t.Errorf("There should have been %d conditions", 1) + } +} + +func TestParseAndValidateConfigBytesDefault(t *testing.T) { + config, err := parseAndValidateConfigBytes([]byte(` +endpoints: + - name: website + url: https://twin.sh/health + conditions: + - "[STATUS] == 200" +`)) + if err != nil { + t.Error("expected no error, got", err.Error()) + } + if config == nil { + t.Fatal("DefaultConfig shouldn't have been nil") + } + if config.Metrics { + t.Error("Metrics should've been false by default") + } + if config.Web.Address != web.DefaultAddress { + t.Errorf("Bind address should have been %s, because it is the default value", web.DefaultAddress) + } + if config.Web.Port != web.DefaultPort { + t.Errorf("Port should have been %d, because it is the default value", web.DefaultPort) + } + if config.Endpoints[0].URL != "https://twin.sh/health" { + t.Errorf("URL should have been %s", "https://twin.sh/health") + } + if config.Endpoints[0].Interval != 60*time.Second { + t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second) + } + if config.Endpoints[0].ClientConfig.Insecure != client.GetDefaultConfig().Insecure { + t.Errorf("ClientConfig.Insecure should have been %v by default, got %v", true, config.Endpoints[0].ClientConfig.Insecure) + } + if config.Endpoints[0].ClientConfig.IgnoreRedirect != client.GetDefaultConfig().IgnoreRedirect { + t.Errorf("ClientConfig.IgnoreRedirect should have been %v by default, got %v", true, config.Endpoints[0].ClientConfig.IgnoreRedirect) + } + if config.Endpoints[0].ClientConfig.Timeout != client.GetDefaultConfig().Timeout { + t.Errorf("ClientConfig.Timeout should have been %v by default, got %v", client.GetDefaultConfig().Timeout, config.Endpoints[0].ClientConfig.Timeout) + } +} + +func TestParseAndValidateConfigBytesWithAddress(t *testing.T) { + config, err := parseAndValidateConfigBytes([]byte(` +web: + address: 127.0.0.1 +endpoints: + - name: website + url: https://twin.sh/actuator/health + conditions: + - "[STATUS] == 200" +`)) + if err != nil { + t.Error("expected no error, got", err.Error()) + } + if config == nil { + t.Fatal("Config shouldn't have been nil") + } + if config.Metrics { + t.Error("Metrics should've been false by default") + } + if config.Endpoints[0].URL != "https://twin.sh/actuator/health" { + t.Errorf("URL should have been %s", "https://twin.sh/actuator/health") + } + if config.Endpoints[0].Interval != 60*time.Second { + t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second) + } + if config.Web.Address != "127.0.0.1" { + t.Errorf("Bind address should have been %s, because it is specified in config", "127.0.0.1") + } + if config.Web.Port != web.DefaultPort { + t.Errorf("Port should have been %d, because it is the default value", web.DefaultPort) + } +} + +func TestParseAndValidateConfigBytesWithPort(t *testing.T) { + config, err := parseAndValidateConfigBytes([]byte(` +web: + port: 12345 +endpoints: + - name: website + url: https://twin.sh/health + conditions: + - "[STATUS] == 200" +`)) + if err != nil { + t.Error("expected no error, got", err.Error()) + } + if config == nil { + t.Fatal("Config shouldn't have been nil") + } + if config.Metrics { + t.Error("Metrics should've been false by default") + } + if config.Endpoints[0].URL != "https://twin.sh/health" { + t.Errorf("URL should have been %s", "https://twin.sh/health") + } + if config.Endpoints[0].Interval != 60*time.Second { + t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second) + } + if config.Web.Address != web.DefaultAddress { + t.Errorf("Bind address should have been %s, because it is the default value", web.DefaultAddress) + } + if config.Web.Port != 12345 { + t.Errorf("Port should have been %d, because it is specified in config", 12345) + } +} + +func TestParseAndValidateConfigBytesWithPortAndHost(t *testing.T) { + config, err := parseAndValidateConfigBytes([]byte(` +web: + port: 12345 + address: 127.0.0.1 +endpoints: + - name: website + url: https://twin.sh/health + conditions: + - "[STATUS] == 200" +`)) + if err != nil { + t.Error("expected no error, got", err.Error()) + } + if config == nil { + t.Fatal("Config shouldn't have been nil") + } + if config.Metrics { + t.Error("Metrics should've been false by default") + } + if config.Endpoints[0].URL != "https://twin.sh/health" { + t.Errorf("URL should have been %s", "https://twin.sh/health") + } + if config.Endpoints[0].Interval != 60*time.Second { + t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second) + } + if config.Web.Address != "127.0.0.1" { + t.Errorf("Bind address should have been %s, because it is specified in config", "127.0.0.1") + } + if config.Web.Port != 12345 { + t.Errorf("Port should have been %d, because it is specified in config", 12345) + } +} + +func TestParseAndValidateConfigBytesWithInvalidPort(t *testing.T) { + _, err := parseAndValidateConfigBytes([]byte(` +web: + port: 65536 + address: 127.0.0.1 +endpoints: + - name: website + url: https://twin.sh/health + conditions: + - "[STATUS] == 200" +`)) + if err == nil { + t.Fatal("Should've returned an error because the configuration specifies an invalid port value") + } +} + +func TestParseAndValidateConfigBytesWithMetricsAndCustomUserAgentHeader(t *testing.T) { + config, err := parseAndValidateConfigBytes([]byte(` +metrics: true +endpoints: + - name: website + url: https://twin.sh/health + headers: + User-Agent: Test/2.0 + conditions: + - "[STATUS] == 200" +`)) + if err != nil { + t.Error("expected no error, got", err.Error()) + } + if config == nil { + t.Fatal("Config shouldn't have been nil") + } + if !config.Metrics { + t.Error("Metrics should have been true") + } + if config.Endpoints[0].URL != "https://twin.sh/health" { + t.Errorf("URL should have been %s", "https://twin.sh/health") + } + if config.Endpoints[0].Interval != 60*time.Second { + t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second) + } + if config.Web.Address != web.DefaultAddress { + t.Errorf("Bind address should have been %s, because it is the default value", web.DefaultAddress) + } + if config.Web.Port != web.DefaultPort { + t.Errorf("Port should have been %d, because it is the default value", web.DefaultPort) + } + if userAgent := config.Endpoints[0].Headers["User-Agent"]; userAgent != "Test/2.0" { + t.Errorf("User-Agent should've been %s, got %s", "Test/2.0", userAgent) + } +} + +func TestParseAndValidateConfigBytesWithMetricsAndHostAndPort(t *testing.T) { + config, err := parseAndValidateConfigBytes([]byte(` +metrics: true +web: + address: 192.168.0.1 + port: 9090 +endpoints: + - name: website + url: https://twin.sh/health + conditions: + - "[STATUS] == 200" +`)) + if err != nil { + t.Error("expected no error, got", err.Error()) + } + if config == nil { + t.Fatal("Config shouldn't have been nil") + } + if !config.Metrics { + t.Error("Metrics should have been true") + } + if config.Web.Address != "192.168.0.1" { + t.Errorf("Bind address should have been %s, because it is the default value", "192.168.0.1") + } + if config.Web.Port != 9090 { + t.Errorf("Port should have been %d, because it is specified in config", 9090) + } + if config.Endpoints[0].URL != "https://twin.sh/health" { + t.Errorf("URL should have been %s", "https://twin.sh/health") + } + if config.Endpoints[0].Interval != 60*time.Second { + t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second) + } + if userAgent := config.Endpoints[0].Headers["User-Agent"]; userAgent != endpoint.GatusUserAgent { + t.Errorf("User-Agent should've been %s because it's the default value, got %s", endpoint.GatusUserAgent, userAgent) + } +} + +func TestParseAndValidateBadConfigBytes(t *testing.T) { + _, err := parseAndValidateConfigBytes([]byte(` +badconfig: + - asdsa: w0w + usadasdrl: asdxzczxc + asdas: + - soup +`)) + if err == nil { + t.Error("An error should've been returned") + } + if err != ErrNoEndpointInConfig { + t.Error("The error returned should have been of type ErrNoEndpointInConfig") + } +} + +func TestParseAndValidateConfigBytesWithAlerting(t *testing.T) { + config, err := parseAndValidateConfigBytes([]byte(` +alerting: + slack: + webhook-url: "http://example.com" + discord: + webhook-url: "http://example.org" + pagerduty: + integration-key: "00000000000000000000000000000000" + pushover: + application-token: "000000000000000000000000000000" + user-key: "000000000000000000000000000000" + mattermost: + webhook-url: "http://example.com" + client: + insecure: true + messagebird: + access-key: "1" + originator: "31619191918" + recipients: "31619191919" + telegram: + token: 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11 + id: 0123456789 + twilio: + sid: "1234" + token: "5678" + from: "+1-234-567-8901" + to: "+1-234-567-8901" + teams: + webhook-url: "http://example.com" + jetbrainsspace: + project: "foo" + channel-id: "bar" + token: "baz" + +endpoints: + - name: website + url: https://twin.sh/health + alerts: + - type: slack + - type: pagerduty + failure-threshold: 7 + success-threshold: 5 + description: "Healthcheck failed 7 times in a row" + - type: mattermost + - type: messagebird + enabled: false + - type: discord + failure-threshold: 10 + - type: telegram + enabled: true + - type: twilio + failure-threshold: 12 + success-threshold: 15 + - type: teams + - type: pushover + - type: jetbrainsspace + conditions: + - "[STATUS] == 200" +`)) + if err != nil { + t.Error("expected no error, got", err.Error()) + } + if config == nil { + t.Fatal("Config shouldn't have been nil") + } + // Alerting providers + if config.Alerting == nil { + t.Fatal("config.Alerting shouldn't have been nil") + } + if config.Alerting.Slack == nil || config.Alerting.Slack.Validate() != nil { + t.Fatal("Slack alerting config should've been valid") + } + // Endpoints + if len(config.Endpoints) != 1 { + t.Error("There should've been 1 endpoint") + } + if config.Endpoints[0].URL != "https://twin.sh/health" { + t.Errorf("URL should have been %s", "https://twin.sh/health") + } + if config.Endpoints[0].Interval != 60*time.Second { + t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second) + } + if len(config.Endpoints[0].Alerts) != 10 { + t.Fatal("There should've been 10 alerts configured") + } + + if config.Endpoints[0].Alerts[0].Type != alert.TypeSlack { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypeSlack, config.Endpoints[0].Alerts[0].Type) + } + if !config.Endpoints[0].Alerts[0].IsEnabled() { + t.Error("The alert should've been enabled") + } + if config.Endpoints[0].Alerts[0].FailureThreshold != 3 { + t.Errorf("The default failure threshold of the alert should've been %d, but it was %d", 3, config.Endpoints[0].Alerts[0].FailureThreshold) + } + if config.Endpoints[0].Alerts[0].SuccessThreshold != 2 { + t.Errorf("The default success threshold of the alert should've been %d, but it was %d", 2, config.Endpoints[0].Alerts[0].SuccessThreshold) + } + + if config.Endpoints[0].Alerts[1].Type != alert.TypePagerDuty { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypePagerDuty, config.Endpoints[0].Alerts[1].Type) + } + if config.Endpoints[0].Alerts[1].GetDescription() != "Healthcheck failed 7 times in a row" { + t.Errorf("The description of the alert should've been %s, but it was %s", "Healthcheck failed 7 times in a row", config.Endpoints[0].Alerts[1].GetDescription()) + } + if config.Endpoints[0].Alerts[1].FailureThreshold != 7 { + t.Errorf("The failure threshold of the alert should've been %d, but it was %d", 7, config.Endpoints[0].Alerts[1].FailureThreshold) + } + if config.Endpoints[0].Alerts[1].SuccessThreshold != 5 { + t.Errorf("The success threshold of the alert should've been %d, but it was %d", 5, config.Endpoints[0].Alerts[1].SuccessThreshold) + } + + if config.Endpoints[0].Alerts[2].Type != alert.TypeMattermost { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypeMattermost, config.Endpoints[0].Alerts[2].Type) + } + if !config.Endpoints[0].Alerts[2].IsEnabled() { + t.Error("The alert should've been enabled") + } + if config.Endpoints[0].Alerts[2].FailureThreshold != 3 { + t.Errorf("The default failure threshold of the alert should've been %d, but it was %d", 3, config.Endpoints[0].Alerts[2].FailureThreshold) + } + if config.Endpoints[0].Alerts[2].SuccessThreshold != 2 { + t.Errorf("The default success threshold of the alert should've been %d, but it was %d", 2, config.Endpoints[0].Alerts[2].SuccessThreshold) + } + + if config.Endpoints[0].Alerts[3].Type != alert.TypeMessagebird { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypeMessagebird, config.Endpoints[0].Alerts[3].Type) + } + if config.Endpoints[0].Alerts[3].IsEnabled() { + t.Error("The alert should've been disabled") + } + + if config.Endpoints[0].Alerts[4].Type != alert.TypeDiscord { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypeDiscord, config.Endpoints[0].Alerts[4].Type) + } + if !config.Endpoints[0].Alerts[4].IsEnabled() { + t.Error("The alert should've been enabled") + } + if config.Endpoints[0].Alerts[4].FailureThreshold != 10 { + t.Errorf("The failure threshold of the alert should've been %d, but it was %d", 10, config.Endpoints[0].Alerts[4].FailureThreshold) + } + if config.Endpoints[0].Alerts[4].SuccessThreshold != 2 { + t.Errorf("The default success threshold of the alert should've been %d, but it was %d", 2, config.Endpoints[0].Alerts[4].SuccessThreshold) + } + + if config.Endpoints[0].Alerts[5].Type != alert.TypeTelegram { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypeTelegram, config.Endpoints[0].Alerts[5].Type) + } + if !config.Endpoints[0].Alerts[5].IsEnabled() { + t.Error("The alert should've been enabled") + } + if config.Endpoints[0].Alerts[5].FailureThreshold != 3 { + t.Errorf("The default failure threshold of the alert should've been %d, but it was %d", 3, config.Endpoints[0].Alerts[5].FailureThreshold) + } + if config.Endpoints[0].Alerts[5].SuccessThreshold != 2 { + t.Errorf("The default success threshold of the alert should've been %d, but it was %d", 2, config.Endpoints[0].Alerts[5].SuccessThreshold) + } + + if config.Endpoints[0].Alerts[6].Type != alert.TypeTwilio { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypeTwilio, config.Endpoints[0].Alerts[6].Type) + } + if !config.Endpoints[0].Alerts[6].IsEnabled() { + t.Error("The alert should've been enabled") + } + if config.Endpoints[0].Alerts[6].FailureThreshold != 12 { + t.Errorf("The default failure threshold of the alert should've been %d, but it was %d", 12, config.Endpoints[0].Alerts[6].FailureThreshold) + } + if config.Endpoints[0].Alerts[6].SuccessThreshold != 15 { + t.Errorf("The default success threshold of the alert should've been %d, but it was %d", 15, config.Endpoints[0].Alerts[6].SuccessThreshold) + } + + if config.Endpoints[0].Alerts[7].Type != alert.TypeTeams { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypeTeams, config.Endpoints[0].Alerts[7].Type) + } + if !config.Endpoints[0].Alerts[7].IsEnabled() { + t.Error("The alert should've been enabled") + } + if config.Endpoints[0].Alerts[7].FailureThreshold != 3 { + t.Errorf("The default failure threshold of the alert should've been %d, but it was %d", 3, config.Endpoints[0].Alerts[7].FailureThreshold) + } + if config.Endpoints[0].Alerts[7].SuccessThreshold != 2 { + t.Errorf("The default success threshold of the alert should've been %d, but it was %d", 2, config.Endpoints[0].Alerts[7].SuccessThreshold) + } + + if config.Endpoints[0].Alerts[8].Type != alert.TypePushover { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypePushover, config.Endpoints[0].Alerts[8].Type) + } + if !config.Endpoints[0].Alerts[8].IsEnabled() { + t.Error("The alert should've been enabled") + } + if config.Endpoints[0].Alerts[9].Type != alert.TypeJetBrainsSpace { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypeJetBrainsSpace, config.Endpoints[0].Alerts[9].Type) + } + if !config.Endpoints[0].Alerts[9].IsEnabled() { + t.Error("The alert should've been enabled") + } +} + +func TestParseAndValidateConfigBytesWithAlertingAndDefaultAlert(t *testing.T) { + config, err := parseAndValidateConfigBytes([]byte(` +alerting: + slack: + webhook-url: "http://example.com" + default-alert: + enabled: true + discord: + webhook-url: "http://example.org" + default-alert: + enabled: true + failure-threshold: 10 + success-threshold: 15 + pagerduty: + integration-key: "00000000000000000000000000000000" + default-alert: + enabled: true + description: default description + failure-threshold: 7 + success-threshold: 5 + pushover: + application-token: "000000000000000000000000000000" + user-key: "000000000000000000000000000000" + default-alert: + enabled: true + description: default description + failure-threshold: 5 + success-threshold: 3 + mattermost: + webhook-url: "http://example.com" + default-alert: + enabled: true + messagebird: + access-key: "1" + originator: "31619191918" + recipients: "31619191919" + default-alert: + enabled: false + send-on-resolved: true + telegram: + token: 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11 + id: 0123456789 + default-alert: + enabled: true + twilio: + sid: "1234" + token: "5678" + from: "+1-234-567-8901" + to: "+1-234-567-8901" + default-alert: + enabled: true + failure-threshold: 12 + success-threshold: 15 + teams: + webhook-url: "http://example.com" + default-alert: + enabled: true + jetbrainsspace: + project: "foo" + channel-id: "bar" + token: "baz" + default-alert: + enabled: true + failure-threshold: 5 + success-threshold: 3 + email: + from: "from@example.com" + username: "from@example.com" + password: "hunter2" + host: "mail.example.com" + port: 587 + to: "recipient1@example.com,recipient2@example.com" + client: + insecure: false + default-alert: + enabled: true + gotify: + server-url: "https://gotify.example" + token: "**************" + default-alert: + enabled: true + +external-endpoints: + - name: ext-ep-test + group: core + token: potato + alerts: + - type: discord + +endpoints: + - name: website + url: https://twin.sh/health + alerts: + - type: slack + - type: pagerduty + - type: mattermost + - type: messagebird + - type: discord + success-threshold: 8 # test endpoint alert override + - type: telegram + - type: twilio + - type: teams + - type: pushover + - type: jetbrainsspace + - type: email + - type: gotify + conditions: + - "[STATUS] == 200" +`)) + if err != nil { + t.Error("expected no error, got", err.Error()) + } + if config == nil { + t.Fatal("Config shouldn't have been nil") + } + if config.Metrics { + t.Error("Metrics should've been false by default") + } + // Alerting providers + if config.Alerting == nil { + t.Fatal("config.Alerting shouldn't have been nil") + } + + if config.Alerting.Slack == nil || config.Alerting.Slack.Validate() != nil { + t.Fatal("Slack alerting config should've been valid") + } + if config.Alerting.Slack.GetDefaultAlert() == nil { + t.Fatal("Slack.GetDefaultAlert() shouldn't have returned nil") + } + if config.Alerting.Slack.DefaultConfig.WebhookURL != "http://example.com" { + t.Errorf("Slack webhook should've been %s, but was %s", "http://example.com", config.Alerting.Slack.DefaultConfig.WebhookURL) + } + + if config.Alerting.PagerDuty == nil || config.Alerting.PagerDuty.Validate() != nil { + t.Fatal("PagerDuty alerting config should've been valid") + } + if config.Alerting.PagerDuty.GetDefaultAlert() == nil { + t.Fatal("PagerDuty.GetDefaultAlert() shouldn't have returned nil") + } + if config.Alerting.PagerDuty.DefaultConfig.IntegrationKey != "00000000000000000000000000000000" { + t.Errorf("PagerDuty integration key should've been %s, but was %s", "00000000000000000000000000000000", config.Alerting.PagerDuty.DefaultConfig.IntegrationKey) + } + + if config.Alerting.Pushover == nil || config.Alerting.Pushover.Validate() != nil { + t.Fatal("Pushover alerting config should've been valid") + } + if config.Alerting.Pushover.GetDefaultAlert() == nil { + t.Fatal("Pushover.GetDefaultAlert() shouldn't have returned nil") + } + if config.Alerting.Pushover.DefaultConfig.ApplicationToken != "000000000000000000000000000000" { + t.Errorf("Pushover application token should've been %s, but was %s", "000000000000000000000000000000", config.Alerting.Pushover.DefaultConfig.ApplicationToken) + } + if config.Alerting.Pushover.DefaultConfig.UserKey != "000000000000000000000000000000" { + t.Errorf("Pushover user key should've been %s, but was %s", "000000000000000000000000000000", config.Alerting.Pushover.DefaultConfig.UserKey) + } + + if config.Alerting.Mattermost == nil || config.Alerting.Mattermost.Validate() != nil { + t.Fatal("Mattermost alerting config should've been valid") + } + if config.Alerting.Mattermost.GetDefaultAlert() == nil { + t.Fatal("Mattermost.GetDefaultAlert() shouldn't have returned nil") + } + + if config.Alerting.Messagebird == nil || config.Alerting.Messagebird.Validate() != nil { + t.Fatal("Messagebird alerting config should've been valid") + } + if config.Alerting.Messagebird.GetDefaultAlert() == nil { + t.Fatal("Messagebird.GetDefaultAlert() shouldn't have returned nil") + } + if config.Alerting.Messagebird.DefaultConfig.AccessKey != "1" { + t.Errorf("Messagebird access key should've been %s, but was %s", "1", config.Alerting.Messagebird.DefaultConfig.AccessKey) + } + if config.Alerting.Messagebird.DefaultConfig.Originator != "31619191918" { + t.Errorf("Messagebird originator field should've been %s, but was %s", "31619191918", config.Alerting.Messagebird.DefaultConfig.Originator) + } + if config.Alerting.Messagebird.DefaultConfig.Recipients != "31619191919" { + t.Errorf("Messagebird to recipients should've been %s, but was %s", "31619191919", config.Alerting.Messagebird.DefaultConfig.Recipients) + } + + if config.Alerting.Discord == nil || config.Alerting.Discord.Validate() != nil { + t.Fatal("Discord alerting config should've been valid") + } + if config.Alerting.Discord.GetDefaultAlert() == nil { + t.Fatal("Discord.GetDefaultAlert() shouldn't have returned nil") + } + if config.Alerting.Discord.GetDefaultAlert().FailureThreshold != 10 { + t.Errorf("Discord default alert failure threshold should've been %d, but was %d", 10, config.Alerting.Discord.GetDefaultAlert().FailureThreshold) + } + if config.Alerting.Discord.GetDefaultAlert().SuccessThreshold != 15 { + t.Errorf("Discord default alert success threshold should've been %d, but was %d", 15, config.Alerting.Discord.GetDefaultAlert().SuccessThreshold) + } + if config.Alerting.Discord.DefaultConfig.WebhookURL != "http://example.org" { + t.Errorf("Discord webhook should've been %s, but was %s", "http://example.org", config.Alerting.Discord.DefaultConfig.WebhookURL) + } + if config.Alerting.GetAlertingProviderByAlertType(alert.TypeDiscord) != config.Alerting.Discord { + t.Error("expected discord configuration") + } + + if config.Alerting.Telegram == nil || config.Alerting.Telegram.Validate() != nil { + t.Fatal("Telegram alerting config should've been valid") + } + if config.Alerting.Telegram.GetDefaultAlert() == nil { + t.Fatal("Telegram.GetDefaultAlert() shouldn't have returned nil") + } + if config.Alerting.Telegram.DefaultConfig.Token != "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" { + t.Errorf("Telegram token should've been %s, but was %s", "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", config.Alerting.Telegram.DefaultConfig.Token) + } + if config.Alerting.Telegram.DefaultConfig.ID != "0123456789" { + t.Errorf("Telegram ID should've been %s, but was %s", "012345689", config.Alerting.Telegram.DefaultConfig.ID) + } + + if config.Alerting.Twilio == nil || config.Alerting.Twilio.Validate() != nil { + t.Fatal("Twilio alerting config should've been valid") + } + if config.Alerting.Twilio.GetDefaultAlert() == nil { + t.Fatal("Twilio.GetDefaultAlert() shouldn't have returned nil") + } + + if config.Alerting.Teams == nil || config.Alerting.Teams.Validate() != nil { + t.Fatal("Teams alerting config should've been valid") + } + if config.Alerting.Teams.GetDefaultAlert() == nil { + t.Fatal("Teams.GetDefaultAlert() shouldn't have returned nil") + } + + if config.Alerting.JetBrainsSpace == nil || config.Alerting.JetBrainsSpace.Validate() != nil { + t.Fatal("JetBrainsSpace alerting config should've been valid") + } + if config.Alerting.JetBrainsSpace.GetDefaultAlert() == nil { + t.Fatal("JetBrainsSpace.GetDefaultAlert() shouldn't have returned nil") + } + if config.Alerting.JetBrainsSpace.DefaultConfig.Project != "foo" { + t.Errorf("JetBrainsSpace webhook should've been %s, but was %s", "foo", config.Alerting.JetBrainsSpace.DefaultConfig.Project) + } + if config.Alerting.JetBrainsSpace.DefaultConfig.ChannelID != "bar" { + t.Errorf("JetBrainsSpace webhook should've been %s, but was %s", "bar", config.Alerting.JetBrainsSpace.DefaultConfig.ChannelID) + } + if config.Alerting.JetBrainsSpace.DefaultConfig.Token != "baz" { + t.Errorf("JetBrainsSpace webhook should've been %s, but was %s", "baz", config.Alerting.JetBrainsSpace.DefaultConfig.Token) + } + + if config.Alerting.Email == nil || config.Alerting.Email.Validate() != nil { + t.Fatal("Email alerting config should've been valid") + } + if config.Alerting.Email.GetDefaultAlert() == nil { + t.Fatal("Email.GetDefaultAlert() shouldn't have returned nil") + } + if config.Alerting.Email.DefaultConfig.From != "from@example.com" { + t.Errorf("Email from should've been %s, but was %s", "from@example.com", config.Alerting.Email.DefaultConfig.From) + } + if config.Alerting.Email.DefaultConfig.Username != "from@example.com" { + t.Errorf("Email username should've been %s, but was %s", "from@example.com", config.Alerting.Email.DefaultConfig.Username) + } + if config.Alerting.Email.DefaultConfig.Password != "hunter2" { + t.Errorf("Email password should've been %s, but was %s", "hunter2", config.Alerting.Email.DefaultConfig.Password) + } + if config.Alerting.Email.DefaultConfig.Host != "mail.example.com" { + t.Errorf("Email host should've been %s, but was %s", "mail.example.com", config.Alerting.Email.DefaultConfig.Host) + } + if config.Alerting.Email.DefaultConfig.Port != 587 { + t.Errorf("Email port should've been %d, but was %d", 587, config.Alerting.Email.DefaultConfig.Port) + } + if config.Alerting.Email.DefaultConfig.To != "recipient1@example.com,recipient2@example.com" { + t.Errorf("Email to should've been %s, but was %s", "recipient1@example.com,recipient2@example.com", config.Alerting.Email.DefaultConfig.To) + } + if config.Alerting.Email.DefaultConfig.ClientConfig == nil { + t.Fatal("Email client config should've been set") + } + if config.Alerting.Email.DefaultConfig.ClientConfig.Insecure { + t.Error("Email client config should've been secure") + } + + if config.Alerting.Gotify == nil || config.Alerting.Gotify.Validate() != nil { + t.Fatal("Gotify alerting config should've been valid") + } + if config.Alerting.Gotify.GetDefaultAlert() == nil { + t.Fatal("Gotify.GetDefaultAlert() shouldn't have returned nil") + } + if config.Alerting.Gotify.DefaultConfig.ServerURL != "https://gotify.example" { + t.Errorf("Gotify server URL should've been %s, but was %s", "https://gotify.example", config.Alerting.Gotify.DefaultConfig.ServerURL) + } + if config.Alerting.Gotify.DefaultConfig.Token != "**************" { + t.Errorf("Gotify token should've been %s, but was %s", "**************", config.Alerting.Gotify.DefaultConfig.Token) + } + + // External endpoints + if len(config.ExternalEndpoints) != 1 { + t.Error("There should've been 1 external endpoint") + } + if config.ExternalEndpoints[0].Alerts[0].Type != alert.TypeDiscord { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypeDiscord, config.ExternalEndpoints[0].Alerts[0].Type) + } + if !config.ExternalEndpoints[0].Alerts[0].IsEnabled() { + t.Error("The alert should've been enabled") + } + if config.ExternalEndpoints[0].Alerts[0].FailureThreshold != 10 { + t.Errorf("The failure threshold of the alert should've been %d, but it was %d", 10, config.ExternalEndpoints[0].Alerts[0].FailureThreshold) + } + if config.ExternalEndpoints[0].Alerts[0].SuccessThreshold != 15 { + t.Errorf("The default success threshold of the alert should've been %d, but it was %d", 15, config.ExternalEndpoints[0].Alerts[0].SuccessThreshold) + } + + // Endpoints + if len(config.Endpoints) != 1 { + t.Error("There should've been 1 endpoint") + } + if config.Endpoints[0].URL != "https://twin.sh/health" { + t.Errorf("URL should have been %s", "https://twin.sh/health") + } + if config.Endpoints[0].Interval != 60*time.Second { + t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second) + } + if len(config.Endpoints[0].Alerts) != 12 { + t.Fatalf("There should've been 12 alerts configured, got %d", len(config.Endpoints[0].Alerts)) + } + + if config.Endpoints[0].Alerts[0].Type != alert.TypeSlack { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypeSlack, config.Endpoints[0].Alerts[0].Type) + } + if !config.Endpoints[0].Alerts[0].IsEnabled() { + t.Error("The alert should've been enabled") + } + if config.Endpoints[0].Alerts[0].FailureThreshold != 3 { + t.Errorf("The default failure threshold of the alert should've been %d, but it was %d", 3, config.Endpoints[0].Alerts[0].FailureThreshold) + } + if config.Endpoints[0].Alerts[0].SuccessThreshold != 2 { + t.Errorf("The default success threshold of the alert should've been %d, but it was %d", 2, config.Endpoints[0].Alerts[0].SuccessThreshold) + } + + if config.Endpoints[0].Alerts[1].Type != alert.TypePagerDuty { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypePagerDuty, config.Endpoints[0].Alerts[1].Type) + } + if config.Endpoints[0].Alerts[1].GetDescription() != "default description" { + t.Errorf("The description of the alert should've been %s, but it was %s", "default description", config.Endpoints[0].Alerts[1].GetDescription()) + } + if config.Endpoints[0].Alerts[1].FailureThreshold != 7 { + t.Errorf("The failure threshold of the alert should've been %d, but it was %d", 7, config.Endpoints[0].Alerts[1].FailureThreshold) + } + if config.Endpoints[0].Alerts[1].SuccessThreshold != 5 { + t.Errorf("The success threshold of the alert should've been %d, but it was %d", 5, config.Endpoints[0].Alerts[1].SuccessThreshold) + } + + if config.Endpoints[0].Alerts[2].Type != alert.TypeMattermost { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypeMattermost, config.Endpoints[0].Alerts[2].Type) + } + if !config.Endpoints[0].Alerts[2].IsEnabled() { + t.Error("The alert should've been enabled") + } + if config.Endpoints[0].Alerts[2].FailureThreshold != 3 { + t.Errorf("The default failure threshold of the alert should've been %d, but it was %d", 3, config.Endpoints[0].Alerts[2].FailureThreshold) + } + if config.Endpoints[0].Alerts[2].SuccessThreshold != 2 { + t.Errorf("The default success threshold of the alert should've been %d, but it was %d", 2, config.Endpoints[0].Alerts[2].SuccessThreshold) + } + + if config.Endpoints[0].Alerts[3].Type != alert.TypeMessagebird { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypeMessagebird, config.Endpoints[0].Alerts[3].Type) + } + if config.Endpoints[0].Alerts[3].IsEnabled() { + t.Error("The alert should've been disabled") + } + if !config.Endpoints[0].Alerts[3].IsSendingOnResolved() { + t.Error("The alert should be sending on resolve") + } + + if config.Endpoints[0].Alerts[4].Type != alert.TypeDiscord { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypeDiscord, config.Endpoints[0].Alerts[4].Type) + } + if !config.Endpoints[0].Alerts[4].IsEnabled() { + t.Error("The alert should've been enabled") + } + if config.Endpoints[0].Alerts[4].FailureThreshold != 10 { + t.Errorf("The failure threshold of the alert should've been %d, but it was %d", 10, config.Endpoints[0].Alerts[4].FailureThreshold) + } + if config.Endpoints[0].Alerts[4].SuccessThreshold != 8 { + t.Errorf("The default success threshold of the alert should've been %d because it was explicitly overriden, but it was %d", 8, config.Endpoints[0].Alerts[4].SuccessThreshold) + } + + if config.Endpoints[0].Alerts[5].Type != alert.TypeTelegram { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypeTelegram, config.Endpoints[0].Alerts[5].Type) + } + if !config.Endpoints[0].Alerts[5].IsEnabled() { + t.Error("The alert should've been enabled") + } + if config.Endpoints[0].Alerts[5].FailureThreshold != 3 { + t.Errorf("The default failure threshold of the alert should've been %d, but it was %d", 3, config.Endpoints[0].Alerts[5].FailureThreshold) + } + if config.Endpoints[0].Alerts[5].SuccessThreshold != 2 { + t.Errorf("The default success threshold of the alert should've been %d, but it was %d", 2, config.Endpoints[0].Alerts[5].SuccessThreshold) + } + + if config.Endpoints[0].Alerts[6].Type != alert.TypeTwilio { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypeTwilio, config.Endpoints[0].Alerts[6].Type) + } + if !config.Endpoints[0].Alerts[6].IsEnabled() { + t.Error("The alert should've been enabled") + } + if config.Endpoints[0].Alerts[6].FailureThreshold != 12 { + t.Errorf("The default failure threshold of the alert should've been %d, but it was %d", 12, config.Endpoints[0].Alerts[6].FailureThreshold) + } + if config.Endpoints[0].Alerts[6].SuccessThreshold != 15 { + t.Errorf("The default success threshold of the alert should've been %d, but it was %d", 15, config.Endpoints[0].Alerts[6].SuccessThreshold) + } + + if config.Endpoints[0].Alerts[7].Type != alert.TypeTeams { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypeTeams, config.Endpoints[0].Alerts[7].Type) + } + if !config.Endpoints[0].Alerts[7].IsEnabled() { + t.Error("The alert should've been enabled") + } + if config.Endpoints[0].Alerts[7].FailureThreshold != 3 { + t.Errorf("The default failure threshold of the alert should've been %d, but it was %d", 3, config.Endpoints[0].Alerts[7].FailureThreshold) + } + if config.Endpoints[0].Alerts[7].SuccessThreshold != 2 { + t.Errorf("The default success threshold of the alert should've been %d, but it was %d", 2, config.Endpoints[0].Alerts[7].SuccessThreshold) + } + + if config.Endpoints[0].Alerts[8].Type != alert.TypePushover { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypePushover, config.Endpoints[0].Alerts[8].Type) + } + if !config.Endpoints[0].Alerts[8].IsEnabled() { + t.Error("The alert should've been enabled") + } + if config.Endpoints[0].Alerts[8].FailureThreshold != 5 { + t.Errorf("The default failure threshold of the alert should've been %d, but it was %d", 3, config.Endpoints[0].Alerts[8].FailureThreshold) + } + if config.Endpoints[0].Alerts[8].SuccessThreshold != 3 { + t.Errorf("The default success threshold of the alert should've been %d, but it was %d", 2, config.Endpoints[0].Alerts[8].SuccessThreshold) + } + + if config.Endpoints[0].Alerts[9].Type != alert.TypeJetBrainsSpace { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypeJetBrainsSpace, config.Endpoints[0].Alerts[9].Type) + } + if !config.Endpoints[0].Alerts[9].IsEnabled() { + t.Error("The alert should've been enabled") + } + if config.Endpoints[0].Alerts[9].FailureThreshold != 5 { + t.Errorf("The default failure threshold of the alert should've been %d, but it was %d", 5, config.Endpoints[0].Alerts[9].FailureThreshold) + } + if config.Endpoints[0].Alerts[9].SuccessThreshold != 3 { + t.Errorf("The default success threshold of the alert should've been %d, but it was %d", 3, config.Endpoints[0].Alerts[9].SuccessThreshold) + } + + if config.Endpoints[0].Alerts[10].Type != alert.TypeEmail { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypeEmail, config.Endpoints[0].Alerts[10].Type) + } + if !config.Endpoints[0].Alerts[10].IsEnabled() { + t.Error("The alert should've been enabled") + } + if config.Endpoints[0].Alerts[10].FailureThreshold != 3 { + t.Errorf("The default failure threshold of the alert should've been %d, but it was %d", 3, config.Endpoints[0].Alerts[10].FailureThreshold) + } + if config.Endpoints[0].Alerts[10].SuccessThreshold != 2 { + t.Errorf("The default success threshold of the alert should've been %d, but it was %d", 2, config.Endpoints[0].Alerts[10].SuccessThreshold) + } + + if config.Endpoints[0].Alerts[11].Type != alert.TypeGotify { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypeGotify, config.Endpoints[0].Alerts[11].Type) + } + if !config.Endpoints[0].Alerts[11].IsEnabled() { + t.Error("The alert should've been enabled") + } + if config.Endpoints[0].Alerts[11].FailureThreshold != 3 { + t.Errorf("The default failure threshold of the alert should've been %d, but it was %d", 3, config.Endpoints[0].Alerts[11].FailureThreshold) + } + if config.Endpoints[0].Alerts[11].SuccessThreshold != 2 { + t.Errorf("The default success threshold of the alert should've been %d, but it was %d", 2, config.Endpoints[0].Alerts[11].SuccessThreshold) + } +} + +func TestParseAndValidateConfigBytesWithAlertingAndDefaultAlertAndMultipleAlertsOfSameTypeWithOverriddenParameters(t *testing.T) { + config, err := parseAndValidateConfigBytes([]byte(` +alerting: + slack: + webhook-url: "https://example.com" + default-alert: + enabled: true + description: "description" + +endpoints: + - name: website + url: https://twin.sh/health + alerts: + - type: slack + failure-threshold: 10 + - type: slack + failure-threshold: 20 + description: "wow" + - type: slack + enabled: false + failure-threshold: 30 + provider-override: + webhook-url: https://example.com + conditions: + - "[STATUS] == 200" +`)) + if err != nil { + t.Error("expected no error, got", err.Error()) + } + if config == nil { + t.Fatal("Config shouldn't have been nil") + } + // Alerting providers + if config.Alerting == nil { + t.Fatal("config.Alerting shouldn't have been nil") + } + if config.Alerting.Slack == nil || config.Alerting.Slack.Validate() != nil { + t.Fatal("Slack alerting config should've been valid") + } + // Endpoints + if len(config.Endpoints) != 1 { + t.Error("There should've been 2 endpoints") + } + if config.Endpoints[0].Alerts[0].Type != alert.TypeSlack { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypeSlack, config.Endpoints[0].Alerts[0].Type) + } + if config.Endpoints[0].Alerts[1].Type != alert.TypeSlack { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypeSlack, config.Endpoints[0].Alerts[1].Type) + } + if config.Endpoints[0].Alerts[2].Type != alert.TypeSlack { + t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypeSlack, config.Endpoints[0].Alerts[2].Type) + } + if !config.Endpoints[0].Alerts[0].IsEnabled() { + t.Error("The alert should've been enabled") + } + if !config.Endpoints[0].Alerts[1].IsEnabled() { + t.Error("The alert should've been enabled") + } + if config.Endpoints[0].Alerts[2].IsEnabled() { + t.Error("The alert should've been disabled") + } + if config.Endpoints[0].Alerts[0].GetDescription() != "description" { + t.Errorf("The description of the alert should've been %s, but it was %s", "description", config.Endpoints[0].Alerts[0].GetDescription()) + } + if config.Endpoints[0].Alerts[1].GetDescription() != "wow" { + t.Errorf("The description of the alert should've been %s, but it was %s", "description", config.Endpoints[0].Alerts[1].GetDescription()) + } + if config.Endpoints[0].Alerts[2].GetDescription() != "description" { + t.Errorf("The description of the alert should've been %s, but it was %s", "description", config.Endpoints[0].Alerts[2].GetDescription()) + } + if config.Endpoints[0].Alerts[0].FailureThreshold != 10 { + t.Errorf("The failure threshold of the alert should've been %d, but it was %d", 10, config.Endpoints[0].Alerts[0].FailureThreshold) + } + if config.Endpoints[0].Alerts[1].FailureThreshold != 20 { + t.Errorf("The failure threshold of the alert should've been %d, but it was %d", 20, config.Endpoints[0].Alerts[1].FailureThreshold) + } + if config.Endpoints[0].Alerts[2].FailureThreshold != 30 { + t.Errorf("The failure threshold of the alert should've been %d, but it was %d", 30, config.Endpoints[0].Alerts[2].FailureThreshold) + } +} + +func TestParseAndValidateConfigBytesWithInvalidPagerDutyAlertingConfig(t *testing.T) { + config, err := parseAndValidateConfigBytes([]byte(` +alerting: + pagerduty: + integration-key: "INVALID_KEY" +endpoints: + - name: website + url: https://twin.sh/health + alerts: + - type: pagerduty + conditions: + - "[STATUS] == 200" +`)) + if err != nil { + t.Error("expected no error, got", err.Error()) + } + if config == nil { + t.Fatal("Config shouldn't have been nil") + } + if config.Alerting == nil { + t.Fatal("config.Alerting shouldn't have been nil") + } + if config.Alerting.PagerDuty != nil { + t.Fatal("PagerDuty alerting config should've been set to nil, because its IsValid() method returned false and therefore alerting.Config.SetAlertingProviderToNil() should've been called") + } +} + +func TestParseAndValidateConfigBytesWithInvalidPushoverAlertingConfig(t *testing.T) { + config, err := parseAndValidateConfigBytes([]byte(` +alerting: + pushover: + application-token: "INVALID_TOKEN" +endpoints: + - name: website + url: https://twin.sh/health + alerts: + - type: pushover + conditions: + - "[STATUS] == 200" +`)) + if err != nil { + t.Error("expected no error, got", err.Error()) + } + if config == nil { + t.Fatal("Config shouldn't have been nil") + } + if config.Alerting == nil { + t.Fatal("config.Alerting shouldn't have been nil") + } + if config.Alerting.Pushover != nil { + t.Fatal("Pushover alerting config should've been set to nil, because its IsValid() method returned false and therefore alerting.Config.SetAlertingProviderToNil() should've been called") + } +} + +func TestParseAndValidateConfigBytesWithCustomAlertingConfig(t *testing.T) { + config, err := parseAndValidateConfigBytes([]byte(` +alerting: + custom: + url: "https://example.com" + body: | + { + "text": "[ALERT_TRIGGERED_OR_RESOLVED]: [ENDPOINT_NAME] - [ALERT_DESCRIPTION]" + } +endpoints: + - name: website + url: https://twin.sh/health + alerts: + - type: custom + conditions: + - "[STATUS] == 200" +`)) + if err != nil { + t.Error("expected no error, got", err.Error()) + } + if config == nil { + t.Fatal("Config shouldn't have been nil") + } + if config.Alerting == nil { + t.Fatal("config.Alerting shouldn't have been nil") + } + if config.Alerting.Custom == nil { + t.Fatal("Custom alerting config shouldn't have been nil") + } + if err = config.Alerting.Custom.Validate(); err != nil { + t.Fatal("Custom alerting config should've been valid") + } + cfg, _ := config.Alerting.Custom.GetConfig("", &alert.Alert{ProviderOverride: map[string]any{"client": map[string]any{"insecure": true}}}) + if config.Alerting.Custom.GetAlertStatePlaceholderValue(cfg, true) != "RESOLVED" { + t.Fatal("ALERT_TRIGGERED_OR_RESOLVED placeholder value for RESOLVED should've been 'RESOLVED', got", config.Alerting.Custom.GetAlertStatePlaceholderValue(cfg, true)) + } + if config.Alerting.Custom.GetAlertStatePlaceholderValue(cfg, false) != "TRIGGERED" { + t.Fatal("ALERT_TRIGGERED_OR_RESOLVED placeholder value for TRIGGERED should've been 'TRIGGERED', got", config.Alerting.Custom.GetAlertStatePlaceholderValue(cfg, false)) + } + if !cfg.ClientConfig.Insecure { + t.Errorf("ClientConfig.Insecure should have been %v, got %v", true, cfg.ClientConfig.Insecure) + } +} + +func TestParseAndValidateConfigBytesWithCustomAlertingConfigAndCustomPlaceholderValues(t *testing.T) { + config, err := parseAndValidateConfigBytes([]byte(` +alerting: + custom: + placeholders: + ALERT_TRIGGERED_OR_RESOLVED: + TRIGGERED: "partial_outage" + RESOLVED: "operational" + url: "https://example.com" + insecure: true + body: "[ALERT_TRIGGERED_OR_RESOLVED]: [ENDPOINT_NAME] - [ALERT_DESCRIPTION]" +endpoints: + - name: website + url: https://twin.sh/health + alerts: + - type: custom + conditions: + - "[STATUS] == 200" +`)) + if err != nil { + t.Error("expected no error, got", err.Error()) + } + if config == nil { + t.Fatal("DefaultConfig shouldn't have been nil") + } + if config.Alerting == nil { + t.Fatal("config.Alerting shouldn't have been nil") + } + if config.Alerting.Custom == nil { + t.Fatal("Custom alerting config shouldn't have been nil") + } + if err = config.Alerting.Custom.Validate(); err != nil { + t.Fatal("Custom alerting config should've been valid") + } + cfg, _ := config.Alerting.Custom.GetConfig("", &alert.Alert{}) + if config.Alerting.Custom.GetAlertStatePlaceholderValue(cfg, true) != "operational" { + t.Fatal("ALERT_TRIGGERED_OR_RESOLVED placeholder value for RESOLVED should've been 'operational'") + } + if config.Alerting.Custom.GetAlertStatePlaceholderValue(cfg, false) != "partial_outage" { + t.Fatal("ALERT_TRIGGERED_OR_RESOLVED placeholder value for TRIGGERED should've been 'partial_outage'") + } +} + +func TestParseAndValidateConfigBytesWithCustomAlertingConfigAndOneCustomPlaceholderValue(t *testing.T) { + config, err := parseAndValidateConfigBytes([]byte(` +alerting: + custom: + placeholders: + ALERT_TRIGGERED_OR_RESOLVED: + TRIGGERED: "partial_outage" + url: "https://example.com" + body: "[ALERT_TRIGGERED_OR_RESOLVED]: [ENDPOINT_NAME] - [ALERT_DESCRIPTION]" +endpoints: + - name: website + url: https://twin.sh/health + alerts: + - type: custom + conditions: + - "[STATUS] == 200" +`)) + if err != nil { + t.Error("expected no error, got", err.Error()) + } + if config == nil { + t.Fatal("DefaultConfig shouldn't have been nil") + } + if config.Alerting == nil { + t.Fatal("config.Alerting shouldn't have been nil") + } + if config.Alerting.Custom == nil { + t.Fatal("Custom alerting config shouldn't have been nil") + } + if err := config.Alerting.Custom.Validate(); err != nil { + t.Fatal("Custom alerting config should've been valid") + } + cfg, _ := config.Alerting.Custom.GetConfig("", &alert.Alert{}) + if config.Alerting.Custom.GetAlertStatePlaceholderValue(cfg, true) != "RESOLVED" { + t.Fatal("ALERT_TRIGGERED_OR_RESOLVED placeholder value for RESOLVED should've been 'RESOLVED'") + } + if config.Alerting.Custom.GetAlertStatePlaceholderValue(cfg, false) != "partial_outage" { + t.Fatal("ALERT_TRIGGERED_OR_RESOLVED placeholder value for TRIGGERED should've been 'partial_outage'") + } +} + +func TestParseAndValidateConfigBytesWithInvalidEndpointName(t *testing.T) { + _, err := parseAndValidateConfigBytes([]byte(` +endpoints: + - name: "" + url: https://twin.sh/health + conditions: + - "[STATUS] == 200" +`)) + if err == nil { + t.Error("should've returned an error") + } +} + +func TestParseAndValidateConfigBytesWithDuplicateEndpointName(t *testing.T) { + scenarios := []struct { + name string + shouldError bool + config string + }{ + { + name: "same-name-no-group", + shouldError: true, + config: ` +endpoints: + - name: ep1 + url: https://twin.sh/health + conditions: + - "[STATUS] == 200" + - name: ep1 + url: https://twin.sh/health + conditions: + - "[STATUS] == 200"`, + }, + { + name: "same-name-different-group", + shouldError: false, + config: ` +endpoints: + - name: ep1 + url: https://twin.sh/health + conditions: + - "[STATUS] == 200" + - name: ep1 + group: g1 + url: https://twin.sh/health + conditions: + - "[STATUS] == 200"`, + }, + { + name: "same-name-same-group", + shouldError: true, + config: ` +endpoints: + - name: ep1 + group: g1 + url: https://twin.sh/health + conditions: + - "[STATUS] == 200" + - name: ep1 + group: g1 + url: https://twin.sh/health + conditions: + - "[STATUS] == 200"`, + }, + { + name: "same-name-different-endpoint-type", + shouldError: true, + config: ` +external-endpoints: + - name: ep1 + token: "12345678" + +endpoints: + - name: ep1 + url: https://twin.sh/health + conditions: + - "[STATUS] == 200"`, + }, + { + name: "same-name-different-group-different-endpoint-type", + shouldError: false, + config: ` +external-endpoints: + - name: ep1 + group: gr1 + token: "12345678" + +endpoints: + - name: ep1 + url: https://twin.sh/health + conditions: + - "[STATUS] == 200"`, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + _, err := parseAndValidateConfigBytes([]byte(scenario.config)) + if scenario.shouldError && err == nil { + t.Error("should've returned an error") + } else if !scenario.shouldError && err != nil { + t.Error("shouldn't have returned an error") + } + }) + } +} + +func TestParseAndValidateConfigBytesWithInvalidStorageConfig(t *testing.T) { + _, err := parseAndValidateConfigBytes([]byte(` +storage: + type: sqlite +endpoints: + - name: example + url: https://example.org + conditions: + - "[STATUS] == 200" +`)) + if err == nil { + t.Error("should've returned an error, because a file must be specified for a storage of type sqlite") + } +} + +func TestParseAndValidateConfigBytesWithInvalidYAML(t *testing.T) { + _, err := parseAndValidateConfigBytes([]byte(` +storage: + invalid yaml +endpoints: + - name: example + url: https://example.org + conditions: + - "[STATUS] == 200" +`)) + if err == nil { + t.Error("should've returned an error") + } +} + +func TestParseAndValidateConfigBytesWithInvalidSecurityConfig(t *testing.T) { + _, err := parseAndValidateConfigBytes([]byte(` +security: + basic: + username: "admin" + password-sha512: "invalid-sha512-hash" +endpoints: + - name: website + url: https://twin.sh/health + conditions: + - "[STATUS] == 200" +`)) + if err == nil { + t.Error("should've returned an error") + } +} + +func TestParseAndValidateConfigBytesWithValidSecurityConfig(t *testing.T) { + const expectedUsername = "admin" + const expectedPasswordHash = "JDJhJDEwJHRiMnRFakxWazZLdXBzRERQazB1TE8vckRLY05Yb1hSdnoxWU0yQ1FaYXZRSW1McmladDYu" + config, err := parseAndValidateConfigBytes([]byte(fmt.Sprintf(` +security: + basic: + username: "%s" + password-bcrypt-base64: "%s" +endpoints: + - name: website + url: https://twin.sh/health + conditions: + - "[STATUS] == 200" +`, expectedUsername, expectedPasswordHash))) + if err != nil { + t.Error("expected no error, got", err.Error()) + } + if config == nil { + t.Fatal("DefaultConfig shouldn't have been nil") + } + if config.Security == nil { + t.Fatal("config.Security shouldn't have been nil") + } + if !config.Security.IsValid() { + t.Error("Security config should've been valid") + } + if config.Security.Basic == nil { + t.Fatal("config.Security.Basic shouldn't have been nil") + } + if config.Security.Basic.Username != expectedUsername { + t.Errorf("config.Security.Basic.Username should've been %s, but was %s", expectedUsername, config.Security.Basic.Username) + } + if config.Security.Basic.PasswordBcryptHashBase64Encoded != expectedPasswordHash { + t.Errorf("config.Security.Basic.PasswordBcryptHashBase64Encoded should've been %s, but was %s", expectedPasswordHash, config.Security.Basic.PasswordBcryptHashBase64Encoded) + } +} + +func TestParseAndValidateConfigBytesWithLiteralDollarSign(t *testing.T) { + os.Setenv("GATUS_TestParseAndValidateConfigBytesWithLiteralDollarSign", "whatever") + config, err := parseAndValidateConfigBytes([]byte(` +endpoints: + - name: website + url: https://twin.sh/health + conditions: + - "[BODY] == $$GATUS_TestParseAndValidateConfigBytesWithLiteralDollarSign" + - "[BODY] == $GATUS_TestParseAndValidateConfigBytesWithLiteralDollarSign" +`)) + if err != nil { + t.Error("expected no error, got", err.Error()) + } + if config == nil { + t.Fatal("DefaultConfig shouldn't have been nil") + } + if config.Endpoints[0].URL != "https://twin.sh/health" { + t.Errorf("URL should have been %s", "https://twin.sh/health") + } + if config.Endpoints[0].Conditions[0] != "[BODY] == $GATUS_TestParseAndValidateConfigBytesWithLiteralDollarSign" { + t.Errorf("Condition should have been %s", "[BODY] == $GATUS_TestParseAndValidateConfigBytesWithLiteralDollarSign") + } + if config.Endpoints[0].Conditions[1] != "[BODY] == whatever" { + t.Errorf("Condition should have been %s", "[BODY] == whatever") + } +} + +func TestParseAndValidateConfigBytesWithNoEndpoints(t *testing.T) { + _, err := parseAndValidateConfigBytes([]byte(``)) + if !errors.Is(err, ErrNoEndpointInConfig) { + t.Error("The error returned should have been of type ErrNoEndpointInConfig") + } +} + +func TestGetAlertingProviderByAlertType(t *testing.T) { + alertingConfig := &alerting.Config{ + AWSSimpleEmailService: &awsses.AlertProvider{}, + Custom: &custom.AlertProvider{}, + Discord: &discord.AlertProvider{}, + Email: &email.AlertProvider{}, + Gitea: &gitea.AlertProvider{}, + GitHub: &github.AlertProvider{}, + GitLab: &gitlab.AlertProvider{}, + GoogleChat: &googlechat.AlertProvider{}, + Gotify: &gotify.AlertProvider{}, + JetBrainsSpace: &jetbrainsspace.AlertProvider{}, + Matrix: &matrix.AlertProvider{}, + Mattermost: &mattermost.AlertProvider{}, + Messagebird: &messagebird.AlertProvider{}, + Ntfy: &ntfy.AlertProvider{}, + Opsgenie: &opsgenie.AlertProvider{}, + PagerDuty: &pagerduty.AlertProvider{}, + Pushover: &pushover.AlertProvider{}, + Slack: &slack.AlertProvider{}, + Telegram: &telegram.AlertProvider{}, + Teams: &teams.AlertProvider{}, + TeamsWorkflows: &teamsworkflows.AlertProvider{}, + Twilio: &twilio.AlertProvider{}, + Zulip: &zulip.AlertProvider{}, + } + scenarios := []struct { + alertType alert.Type + expected provider.AlertProvider + }{ + {alertType: alert.TypeAWSSES, expected: alertingConfig.AWSSimpleEmailService}, + {alertType: alert.TypeCustom, expected: alertingConfig.Custom}, + {alertType: alert.TypeDiscord, expected: alertingConfig.Discord}, + {alertType: alert.TypeEmail, expected: alertingConfig.Email}, + {alertType: alert.TypeGitea, expected: alertingConfig.Gitea}, + {alertType: alert.TypeGitHub, expected: alertingConfig.GitHub}, + {alertType: alert.TypeGitLab, expected: alertingConfig.GitLab}, + {alertType: alert.TypeGoogleChat, expected: alertingConfig.GoogleChat}, + {alertType: alert.TypeGotify, expected: alertingConfig.Gotify}, + {alertType: alert.TypeJetBrainsSpace, expected: alertingConfig.JetBrainsSpace}, + {alertType: alert.TypeMatrix, expected: alertingConfig.Matrix}, + {alertType: alert.TypeMattermost, expected: alertingConfig.Mattermost}, + {alertType: alert.TypeMessagebird, expected: alertingConfig.Messagebird}, + {alertType: alert.TypeNtfy, expected: alertingConfig.Ntfy}, + {alertType: alert.TypeOpsgenie, expected: alertingConfig.Opsgenie}, + {alertType: alert.TypePagerDuty, expected: alertingConfig.PagerDuty}, + {alertType: alert.TypePushover, expected: alertingConfig.Pushover}, + {alertType: alert.TypeSlack, expected: alertingConfig.Slack}, + {alertType: alert.TypeTelegram, expected: alertingConfig.Telegram}, + {alertType: alert.TypeTeams, expected: alertingConfig.Teams}, + {alertType: alert.TypeTeamsWorkflows, expected: alertingConfig.TeamsWorkflows}, + {alertType: alert.TypeTwilio, expected: alertingConfig.Twilio}, + {alertType: alert.TypeZulip, expected: alertingConfig.Zulip}, + } + for _, scenario := range scenarios { + t.Run(string(scenario.alertType), func(t *testing.T) { + if alertingConfig.GetAlertingProviderByAlertType(scenario.alertType) != scenario.expected { + t.Errorf("expected %s configuration", scenario.alertType) + } + }) + } +} diff --git a/config/connectivity/connectivity.go b/config/connectivity/connectivity.go new file mode 100644 index 00000000..f366415d --- /dev/null +++ b/config/connectivity/connectivity.go @@ -0,0 +1,53 @@ +package connectivity + +import ( + "errors" + "strings" + "time" + + "github.com/TwiN/gatus/v5/client" +) + +var ( + ErrInvalidInterval = errors.New("connectivity.checker.interval must be 5s or higher") + ErrInvalidDNSTarget = errors.New("connectivity.checker.target must be suffixed with :53") +) + +// Config is the configuration for the connectivity checker. +type Config struct { + Checker *Checker `yaml:"checker,omitempty"` +} + +func (c *Config) ValidateAndSetDefaults() error { + if c.Checker != nil { + if c.Checker.Interval == 0 { + c.Checker.Interval = 60 * time.Second + } else if c.Checker.Interval < 5*time.Second { + return ErrInvalidInterval + } + if !strings.HasSuffix(c.Checker.Target, ":53") { + return ErrInvalidDNSTarget + } + } + return nil +} + +// Checker is the configuration for making sure Gatus has access to the internet. +type Checker struct { + Target string `yaml:"target"` // e.g. 1.1.1.1:53 + Interval time.Duration `yaml:"interval,omitempty"` + + isConnected bool + lastCheck time.Time +} + +func (c *Checker) Check() bool { + return client.CanCreateTCPConnection(c.Target, &client.Config{Timeout: 5 * time.Second}) +} + +func (c *Checker) IsConnected() bool { + if now := time.Now(); now.After(c.lastCheck.Add(c.Interval)) { + c.lastCheck, c.isConnected = now, c.Check() + } + return c.isConnected +} diff --git a/config/connectivity/connectivity_test.go b/config/connectivity/connectivity_test.go new file mode 100644 index 00000000..d16520cb --- /dev/null +++ b/config/connectivity/connectivity_test.go @@ -0,0 +1,62 @@ +package connectivity + +import ( + "fmt" + "testing" + "time" +) + +func TestConfig(t *testing.T) { + scenarios := []struct { + name string + cfg *Config + expectedErr error + expectedInterval time.Duration + }{ + { + name: "good-config", + cfg: &Config{Checker: &Checker{Target: "1.1.1.1:53", Interval: 10 * time.Second}}, + expectedInterval: 10 * time.Second, + }, + { + name: "good-config-with-default-interval", + cfg: &Config{Checker: &Checker{Target: "8.8.8.8:53", Interval: 0}}, + expectedInterval: 60 * time.Second, + }, + { + name: "config-with-interval-too-low", + cfg: &Config{Checker: &Checker{Target: "1.1.1.1:53", Interval: 4 * time.Second}}, + expectedErr: ErrInvalidInterval, + }, + { + name: "config-with-invalid-target-due-to-missing-port", + cfg: &Config{Checker: &Checker{Target: "1.1.1.1", Interval: 15 * time.Second}}, + expectedErr: ErrInvalidDNSTarget, + }, + { + name: "config-with-invalid-target-due-to-invalid-dns-port", + cfg: &Config{Checker: &Checker{Target: "1.1.1.1:52", Interval: 15 * time.Second}}, + expectedErr: ErrInvalidDNSTarget, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + err := scenario.cfg.ValidateAndSetDefaults() + if fmt.Sprintf("%s", err) != fmt.Sprintf("%s", scenario.expectedErr) { + t.Errorf("expected error %v, got %v", scenario.expectedErr, err) + } + if err == nil && scenario.expectedErr == nil { + if scenario.cfg.Checker.Interval != scenario.expectedInterval { + t.Errorf("expected interval %v, got %v", scenario.expectedInterval, scenario.cfg.Checker.Interval) + } + } + }) + } +} + +func TestChecker_IsConnected(t *testing.T) { + checker := &Checker{Target: "1.1.1.1:53", Interval: 10 * time.Second} + if !checker.IsConnected() { + t.Error("expected checker.IsConnected() to be true") + } +} diff --git a/config/endpoint/common.go b/config/endpoint/common.go new file mode 100644 index 00000000..f3da8bf8 --- /dev/null +++ b/config/endpoint/common.go @@ -0,0 +1,32 @@ +package endpoint + +import ( + "errors" + "strings" + + "github.com/TwiN/gatus/v5/alerting/alert" +) + +var ( + // ErrEndpointWithNoName is the error with which Gatus will panic if an endpoint is configured with no name + ErrEndpointWithNoName = errors.New("you must specify a name for each endpoint") + + // ErrEndpointWithInvalidNameOrGroup is the error with which Gatus will panic if an endpoint has an invalid character where it shouldn't + ErrEndpointWithInvalidNameOrGroup = errors.New("endpoint name and group must not have \" or \\") +) + +// validateEndpointNameGroupAndAlerts validates the name, group and alerts of an endpoint +func validateEndpointNameGroupAndAlerts(name, group string, alerts []*alert.Alert) error { + if len(name) == 0 { + return ErrEndpointWithNoName + } + if strings.ContainsAny(name, "\"\\") || strings.ContainsAny(group, "\"\\") { + return ErrEndpointWithInvalidNameOrGroup + } + for _, endpointAlert := range alerts { + if err := endpointAlert.ValidateAndSetDefaults(); err != nil { + return err + } + } + return nil +} diff --git a/config/endpoint/common_test.go b/config/endpoint/common_test.go new file mode 100644 index 00000000..ab4fc9ef --- /dev/null +++ b/config/endpoint/common_test.go @@ -0,0 +1,51 @@ +package endpoint + +import ( + "errors" + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" +) + +func TestValidateEndpointNameGroupAndAlerts(t *testing.T) { + scenarios := []struct { + name string + group string + alerts []*alert.Alert + expectedErr error + }{ + { + name: "n", + group: "g", + alerts: []*alert.Alert{{Type: "slack"}}, + }, + { + name: "n", + alerts: []*alert.Alert{{Type: "slack"}}, + }, + { + group: "g", + alerts: []*alert.Alert{{Type: "slack"}}, + expectedErr: ErrEndpointWithNoName, + }, + { + name: "\"", + alerts: []*alert.Alert{{Type: "slack"}}, + expectedErr: ErrEndpointWithInvalidNameOrGroup, + }, + { + name: "n", + group: "\\", + alerts: []*alert.Alert{{Type: "slack"}}, + expectedErr: ErrEndpointWithInvalidNameOrGroup, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + err := validateEndpointNameGroupAndAlerts(scenario.name, scenario.group, scenario.alerts) + if !errors.Is(err, scenario.expectedErr) { + t.Errorf("expected error to be %v but got %v", scenario.expectedErr, err) + } + }) + } +} diff --git a/config/endpoint/condition.go b/config/endpoint/condition.go new file mode 100644 index 00000000..678ad5e2 --- /dev/null +++ b/config/endpoint/condition.go @@ -0,0 +1,364 @@ +package endpoint + +import ( + "errors" + "fmt" + "strconv" + "strings" + "time" + + "github.com/TwiN/gatus/v5/jsonpath" + "github.com/TwiN/gatus/v5/pattern" +) + +// Placeholders +const ( + // StatusPlaceholder is a placeholder for a HTTP status. + // + // Values that could replace the placeholder: 200, 404, 500, ... + StatusPlaceholder = "[STATUS]" + + // IPPlaceholder is a placeholder for an IP. + // + // Values that could replace the placeholder: 127.0.0.1, 10.0.0.1, ... + IPPlaceholder = "[IP]" + + // DNSRCodePlaceholder is a placeholder for DNS_RCODE + // + // Values that could replace the placeholder: NOERROR, FORMERR, SERVFAIL, NXDOMAIN, NOTIMP, REFUSED + DNSRCodePlaceholder = "[DNS_RCODE]" + + // ResponseTimePlaceholder is a placeholder for the request response time, in milliseconds. + // + // Values that could replace the placeholder: 1, 500, 1000, ... + ResponseTimePlaceholder = "[RESPONSE_TIME]" + + // BodyPlaceholder is a placeholder for the Body of the response + // + // Values that could replace the placeholder: {}, {"data":{"name":"john"}}, ... + BodyPlaceholder = "[BODY]" + + // ConnectedPlaceholder is a placeholder for whether a connection was successfully established. + // + // Values that could replace the placeholder: true, false + ConnectedPlaceholder = "[CONNECTED]" + + // CertificateExpirationPlaceholder is a placeholder for the duration before certificate expiration, in milliseconds. + // + // Values that could replace the placeholder: 4461677039 (~52 days) + CertificateExpirationPlaceholder = "[CERTIFICATE_EXPIRATION]" + + // DomainExpirationPlaceholder is a placeholder for the duration before the domain expires, in milliseconds. + DomainExpirationPlaceholder = "[DOMAIN_EXPIRATION]" +) + +// Functions +const ( + // LengthFunctionPrefix is the prefix for the length function + // + // Usage: len([BODY].articles) == 10, len([BODY].name) > 5 + LengthFunctionPrefix = "len(" + + // HasFunctionPrefix is the prefix for the has function + // + // Usage: has([BODY].errors) == true + HasFunctionPrefix = "has(" + + // PatternFunctionPrefix is the prefix for the pattern function + // + // Usage: [IP] == pat(192.168.*.*) + PatternFunctionPrefix = "pat(" + + // AnyFunctionPrefix is the prefix for the any function + // + // Usage: [IP] == any(1.1.1.1, 1.0.0.1) + AnyFunctionPrefix = "any(" + + // FunctionSuffix is the suffix for all functions + FunctionSuffix = ")" +) + +// Other constants +const ( + // InvalidConditionElementSuffix is the suffix that will be appended to an invalid condition + InvalidConditionElementSuffix = "(INVALID)" + + // maximumLengthBeforeTruncatingWhenComparedWithPattern is the maximum length an element being compared to a + // pattern can have. + // + // This is only used for aesthetic purposes; it does not influence whether the condition evaluation results in a + // success or a failure + maximumLengthBeforeTruncatingWhenComparedWithPattern = 25 +) + +// Condition is a condition that needs to be met in order for an Endpoint to be considered healthy. +type Condition string + +// Validate checks if the Condition is valid +func (c Condition) Validate() error { + r := &Result{} + c.evaluate(r, false) + if len(r.Errors) != 0 { + return errors.New(r.Errors[0]) + } + return nil +} + +// evaluate the Condition with the Result of the health check +func (c Condition) evaluate(result *Result, dontResolveFailedConditions bool) bool { + condition := string(c) + success := false + conditionToDisplay := condition + if strings.Contains(condition, " == ") { + parameters, resolvedParameters := sanitizeAndResolve(strings.Split(condition, " == "), result) + success = isEqual(resolvedParameters[0], resolvedParameters[1]) + if !success && !dontResolveFailedConditions { + conditionToDisplay = prettify(parameters, resolvedParameters, "==") + } + } else if strings.Contains(condition, " != ") { + parameters, resolvedParameters := sanitizeAndResolve(strings.Split(condition, " != "), result) + success = !isEqual(resolvedParameters[0], resolvedParameters[1]) + if !success && !dontResolveFailedConditions { + conditionToDisplay = prettify(parameters, resolvedParameters, "!=") + } + } else if strings.Contains(condition, " <= ") { + parameters, resolvedParameters := sanitizeAndResolveNumerical(strings.Split(condition, " <= "), result) + success = resolvedParameters[0] <= resolvedParameters[1] + if !success && !dontResolveFailedConditions { + conditionToDisplay = prettifyNumericalParameters(parameters, resolvedParameters, "<=") + } + } else if strings.Contains(condition, " >= ") { + parameters, resolvedParameters := sanitizeAndResolveNumerical(strings.Split(condition, " >= "), result) + success = resolvedParameters[0] >= resolvedParameters[1] + if !success && !dontResolveFailedConditions { + conditionToDisplay = prettifyNumericalParameters(parameters, resolvedParameters, ">=") + } + } else if strings.Contains(condition, " > ") { + parameters, resolvedParameters := sanitizeAndResolveNumerical(strings.Split(condition, " > "), result) + success = resolvedParameters[0] > resolvedParameters[1] + if !success && !dontResolveFailedConditions { + conditionToDisplay = prettifyNumericalParameters(parameters, resolvedParameters, ">") + } + } else if strings.Contains(condition, " < ") { + parameters, resolvedParameters := sanitizeAndResolveNumerical(strings.Split(condition, " < "), result) + success = resolvedParameters[0] < resolvedParameters[1] + if !success && !dontResolveFailedConditions { + conditionToDisplay = prettifyNumericalParameters(parameters, resolvedParameters, "<") + } + } else { + result.AddError(fmt.Sprintf("invalid condition: %s", condition)) + return false + } + if !success { + //logr.Debugf("[Condition.evaluate] Condition '%s' did not succeed because '%s' is false", condition, condition) + } + result.ConditionResults = append(result.ConditionResults, &ConditionResult{Condition: conditionToDisplay, Success: success}) + return success +} + +// hasBodyPlaceholder checks whether the condition has a BodyPlaceholder +// Used for determining whether the response body should be read or not +func (c Condition) hasBodyPlaceholder() bool { + return strings.Contains(string(c), BodyPlaceholder) +} + +// hasDomainExpirationPlaceholder checks whether the condition has a DomainExpirationPlaceholder +// Used for determining whether a whois operation is necessary +func (c Condition) hasDomainExpirationPlaceholder() bool { + return strings.Contains(string(c), DomainExpirationPlaceholder) +} + +// hasIPPlaceholder checks whether the condition has an IPPlaceholder +// Used for determining whether an IP lookup is necessary +func (c Condition) hasIPPlaceholder() bool { + return strings.Contains(string(c), IPPlaceholder) +} + +// isEqual compares two strings. +// +// Supports the "pat" and the "any" functions. +// i.e. if one of the parameters starts with PatternFunctionPrefix and ends with FunctionSuffix, it will be treated like +// a pattern. +func isEqual(first, second string) bool { + firstHasFunctionSuffix := strings.HasSuffix(first, FunctionSuffix) + secondHasFunctionSuffix := strings.HasSuffix(second, FunctionSuffix) + if firstHasFunctionSuffix || secondHasFunctionSuffix { + var isFirstPattern, isSecondPattern bool + if strings.HasPrefix(first, PatternFunctionPrefix) && firstHasFunctionSuffix { + isFirstPattern = true + first = strings.TrimSuffix(strings.TrimPrefix(first, PatternFunctionPrefix), FunctionSuffix) + } + if strings.HasPrefix(second, PatternFunctionPrefix) && secondHasFunctionSuffix { + isSecondPattern = true + second = strings.TrimSuffix(strings.TrimPrefix(second, PatternFunctionPrefix), FunctionSuffix) + } + if isFirstPattern && !isSecondPattern { + return pattern.Match(first, second) + } else if !isFirstPattern && isSecondPattern { + return pattern.Match(second, first) + } + var isFirstAny, isSecondAny bool + if strings.HasPrefix(first, AnyFunctionPrefix) && firstHasFunctionSuffix { + isFirstAny = true + first = strings.TrimSuffix(strings.TrimPrefix(first, AnyFunctionPrefix), FunctionSuffix) + } + if strings.HasPrefix(second, AnyFunctionPrefix) && secondHasFunctionSuffix { + isSecondAny = true + second = strings.TrimSuffix(strings.TrimPrefix(second, AnyFunctionPrefix), FunctionSuffix) + } + if isFirstAny && !isSecondAny { + options := strings.Split(first, ",") + for _, option := range options { + if strings.TrimSpace(option) == second { + return true + } + } + return false + } else if !isFirstAny && isSecondAny { + options := strings.Split(second, ",") + for _, option := range options { + if strings.TrimSpace(option) == first { + return true + } + } + return false + } + } + + // test if inputs are integers + firstInt, err1 := strconv.ParseInt(first, 0, 64) + secondInt, err2 := strconv.ParseInt(second, 0, 64) + if err1 == nil && err2 == nil { + return firstInt == secondInt + } + + return first == second +} + +// sanitizeAndResolve sanitizes and resolves a list of elements and returns the list of parameters as well as a list +// of resolved parameters +func sanitizeAndResolve(elements []string, result *Result) ([]string, []string) { + parameters := make([]string, len(elements)) + resolvedParameters := make([]string, len(elements)) + body := strings.TrimSpace(string(result.Body)) + for i, element := range elements { + element = strings.TrimSpace(element) + parameters[i] = element + switch strings.ToUpper(element) { + case StatusPlaceholder: + element = strconv.Itoa(result.HTTPStatus) + case IPPlaceholder: + element = result.IP + case ResponseTimePlaceholder: + element = strconv.Itoa(int(result.Duration.Milliseconds())) + case BodyPlaceholder: + element = body + case DNSRCodePlaceholder: + element = result.DNSRCode + case ConnectedPlaceholder: + element = strconv.FormatBool(result.Connected) + case CertificateExpirationPlaceholder: + element = strconv.FormatInt(result.CertificateExpiration.Milliseconds(), 10) + case DomainExpirationPlaceholder: + element = strconv.FormatInt(result.DomainExpiration.Milliseconds(), 10) + default: + // if contains the BodyPlaceholder, then evaluate json path + if strings.Contains(element, BodyPlaceholder) { + checkingForLength := false + checkingForExistence := false + if strings.HasPrefix(element, LengthFunctionPrefix) && strings.HasSuffix(element, FunctionSuffix) { + checkingForLength = true + element = strings.TrimSuffix(strings.TrimPrefix(element, LengthFunctionPrefix), FunctionSuffix) + } + if strings.HasPrefix(element, HasFunctionPrefix) && strings.HasSuffix(element, FunctionSuffix) { + checkingForExistence = true + element = strings.TrimSuffix(strings.TrimPrefix(element, HasFunctionPrefix), FunctionSuffix) + } + resolvedElement, resolvedElementLength, err := jsonpath.Eval(strings.TrimPrefix(strings.TrimPrefix(element, BodyPlaceholder), "."), result.Body) + if checkingForExistence { + if err != nil { + element = "false" + } else { + element = "true" + } + } else { + if err != nil { + if err.Error() != "unexpected end of JSON input" { + result.AddError(err.Error()) + } + if checkingForLength { + element = LengthFunctionPrefix + element + FunctionSuffix + " " + InvalidConditionElementSuffix + } else { + element = element + " " + InvalidConditionElementSuffix + } + } else { + if checkingForLength { + element = strconv.Itoa(resolvedElementLength) + } else { + element = resolvedElement + } + } + } + } + } + resolvedParameters[i] = element + } + return parameters, resolvedParameters +} + +func sanitizeAndResolveNumerical(list []string, result *Result) (parameters []string, resolvedNumericalParameters []int64) { + parameters, resolvedParameters := sanitizeAndResolve(list, result) + for _, element := range resolvedParameters { + if duration, err := time.ParseDuration(element); duration != 0 && err == nil { + // If the string is a duration, convert it to milliseconds + resolvedNumericalParameters = append(resolvedNumericalParameters, duration.Milliseconds()) + } else if number, err := strconv.ParseInt(element, 0, 64); err != nil { + // It's not an int, so we'll check if it's a float + if f, err := strconv.ParseFloat(element, 64); err == nil { + // It's a float, but we'll convert it to an int. We're losing precision here, but it's better than + // just returning 0. + resolvedNumericalParameters = append(resolvedNumericalParameters, int64(f)) + } else { + // Default to 0 if the string couldn't be converted to an integer or a float + resolvedNumericalParameters = append(resolvedNumericalParameters, 0) + } + } else { + resolvedNumericalParameters = append(resolvedNumericalParameters, number) + } + } + return parameters, resolvedNumericalParameters +} + +func prettifyNumericalParameters(parameters []string, resolvedParameters []int64, operator string) string { + return prettify(parameters, []string{strconv.Itoa(int(resolvedParameters[0])), strconv.Itoa(int(resolvedParameters[1]))}, operator) +} + +// prettify returns a string representation of a condition with its parameters resolved between parentheses +func prettify(parameters []string, resolvedParameters []string, operator string) string { + // Since, in the event of an invalid path, the resolvedParameters also contain the condition itself, + // we'll return the resolvedParameters as-is. + if strings.HasSuffix(resolvedParameters[0], InvalidConditionElementSuffix) || strings.HasSuffix(resolvedParameters[1], InvalidConditionElementSuffix) { + return resolvedParameters[0] + " " + operator + " " + resolvedParameters[1] + } + // If using the pattern function, truncate the parameter it's being compared to if said parameter is long enough + if strings.HasPrefix(parameters[0], PatternFunctionPrefix) && strings.HasSuffix(parameters[0], FunctionSuffix) && len(resolvedParameters[1]) > maximumLengthBeforeTruncatingWhenComparedWithPattern { + resolvedParameters[1] = fmt.Sprintf("%.25s...(truncated)", resolvedParameters[1]) + } + if strings.HasPrefix(parameters[1], PatternFunctionPrefix) && strings.HasSuffix(parameters[1], FunctionSuffix) && len(resolvedParameters[0]) > maximumLengthBeforeTruncatingWhenComparedWithPattern { + resolvedParameters[0] = fmt.Sprintf("%.25s...(truncated)", resolvedParameters[0]) + } + // First element is a placeholder + if parameters[0] != resolvedParameters[0] && parameters[1] == resolvedParameters[1] { + return parameters[0] + " (" + resolvedParameters[0] + ") " + operator + " " + parameters[1] + } + // Second element is a placeholder + if parameters[0] == resolvedParameters[0] && parameters[1] != resolvedParameters[1] { + return parameters[0] + " " + operator + " " + parameters[1] + " (" + resolvedParameters[1] + ")" + } + // Both elements are placeholders...? + if parameters[0] != resolvedParameters[0] && parameters[1] != resolvedParameters[1] { + return parameters[0] + " (" + resolvedParameters[0] + ") " + operator + " " + parameters[1] + " (" + resolvedParameters[1] + ")" + } + // Neither elements are placeholders + return parameters[0] + " " + operator + " " + parameters[1] +} diff --git a/config/endpoint/condition_bench_test.go b/config/endpoint/condition_bench_test.go new file mode 100644 index 00000000..82061f69 --- /dev/null +++ b/config/endpoint/condition_bench_test.go @@ -0,0 +1,86 @@ +package endpoint + +import ( + "testing" +) + +func BenchmarkCondition_evaluateWithBodyStringAny(b *testing.B) { + condition := Condition("[BODY].name == any(john.doe, jane.doe)") + for n := 0; n < b.N; n++ { + result := &Result{Body: []byte("{\"name\": \"john.doe\"}")} + condition.evaluate(result, false) + } + b.ReportAllocs() +} + +func BenchmarkCondition_evaluateWithBodyStringAnyFailure(b *testing.B) { + condition := Condition("[BODY].name == any(john.doe, jane.doe)") + for n := 0; n < b.N; n++ { + result := &Result{Body: []byte("{\"name\": \"bob.doe\"}")} + condition.evaluate(result, false) + } + b.ReportAllocs() +} + +func BenchmarkCondition_evaluateWithBodyString(b *testing.B) { + condition := Condition("[BODY].name == john.doe") + for n := 0; n < b.N; n++ { + result := &Result{Body: []byte("{\"name\": \"john.doe\"}")} + condition.evaluate(result, false) + } + b.ReportAllocs() +} + +func BenchmarkCondition_evaluateWithBodyStringFailure(b *testing.B) { + condition := Condition("[BODY].name == john.doe") + for n := 0; n < b.N; n++ { + result := &Result{Body: []byte("{\"name\": \"bob.doe\"}")} + condition.evaluate(result, false) + } + b.ReportAllocs() +} + +func BenchmarkCondition_evaluateWithBodyStringFailureInvalidPath(b *testing.B) { + condition := Condition("[BODY].user.name == bob.doe") + for n := 0; n < b.N; n++ { + result := &Result{Body: []byte("{\"name\": \"bob.doe\"}")} + condition.evaluate(result, false) + } + b.ReportAllocs() +} + +func BenchmarkCondition_evaluateWithBodyStringLen(b *testing.B) { + condition := Condition("len([BODY].name) == 8") + for n := 0; n < b.N; n++ { + result := &Result{Body: []byte("{\"name\": \"john.doe\"}")} + condition.evaluate(result, false) + } + b.ReportAllocs() +} + +func BenchmarkCondition_evaluateWithBodyStringLenFailure(b *testing.B) { + condition := Condition("len([BODY].name) == 8") + for n := 0; n < b.N; n++ { + result := &Result{Body: []byte("{\"name\": \"bob.doe\"}")} + condition.evaluate(result, false) + } + b.ReportAllocs() +} + +func BenchmarkCondition_evaluateWithStatus(b *testing.B) { + condition := Condition("[STATUS] == 200") + for n := 0; n < b.N; n++ { + result := &Result{HTTPStatus: 200} + condition.evaluate(result, false) + } + b.ReportAllocs() +} + +func BenchmarkCondition_evaluateWithStatusFailure(b *testing.B) { + condition := Condition("[STATUS] == 200") + for n := 0; n < b.N; n++ { + result := &Result{HTTPStatus: 400} + condition.evaluate(result, false) + } + b.ReportAllocs() +} diff --git a/config/endpoint/condition_result.go b/config/endpoint/condition_result.go new file mode 100644 index 00000000..00af8620 --- /dev/null +++ b/config/endpoint/condition_result.go @@ -0,0 +1,10 @@ +package endpoint + +// ConditionResult result of a Condition +type ConditionResult struct { + // Condition that was evaluated + Condition string `json:"condition"` + + // Success whether the condition was met (successful) or not (failed) + Success bool `json:"success"` +} diff --git a/config/endpoint/condition_test.go b/config/endpoint/condition_test.go new file mode 100644 index 00000000..3e98912f --- /dev/null +++ b/config/endpoint/condition_test.go @@ -0,0 +1,779 @@ +package endpoint + +import ( + "errors" + "fmt" + "strconv" + "testing" + "time" +) + +func TestCondition_Validate(t *testing.T) { + scenarios := []struct { + condition Condition + expectedErr error + }{ + {condition: "[STATUS] == 200", expectedErr: nil}, + {condition: "[STATUS] != 200", expectedErr: nil}, + {condition: "[STATUS] <= 200", expectedErr: nil}, + {condition: "[STATUS] >= 200", expectedErr: nil}, + {condition: "[STATUS] < 200", expectedErr: nil}, + {condition: "[STATUS] > 200", expectedErr: nil}, + {condition: "[STATUS] == any(200, 201, 202, 203)", expectedErr: nil}, + {condition: "[STATUS] == [BODY].status", expectedErr: nil}, + {condition: "[CONNECTED] == true", expectedErr: nil}, + {condition: "[RESPONSE_TIME] < 500", expectedErr: nil}, + {condition: "[IP] == 127.0.0.1", expectedErr: nil}, + {condition: "[BODY] == 1", expectedErr: nil}, + {condition: "[BODY].test == wat", expectedErr: nil}, + {condition: "[BODY].test.wat == wat", expectedErr: nil}, + {condition: "[BODY].age == [BODY].id", expectedErr: nil}, + {condition: "[BODY].users[0].id == 1", expectedErr: nil}, + {condition: "len([BODY].users) == 100", expectedErr: nil}, + {condition: "len([BODY].data) < 5", expectedErr: nil}, + {condition: "has([BODY].errors) == false", expectedErr: nil}, + {condition: "has([BODY].users[0].name) == true", expectedErr: nil}, + {condition: "[BODY].name == pat(john*)", expectedErr: nil}, + {condition: "[CERTIFICATE_EXPIRATION] > 48h", expectedErr: nil}, + {condition: "[DOMAIN_EXPIRATION] > 720h", expectedErr: nil}, + {condition: "raw == raw", expectedErr: nil}, + {condition: "[STATUS] ? 201", expectedErr: errors.New("invalid condition: [STATUS] ? 201")}, + {condition: "[STATUS]==201", expectedErr: errors.New("invalid condition: [STATUS]==201")}, + {condition: "[STATUS] = = 201", expectedErr: errors.New("invalid condition: [STATUS] = = 201")}, + {condition: "[STATUS] ==", expectedErr: errors.New("invalid condition: [STATUS] ==")}, + {condition: "[STATUS]", expectedErr: errors.New("invalid condition: [STATUS]")}, + // FIXME: Should return an error, but doesn't because jsonpath isn't evaluated due to body being empty in Condition.Validate() + //{condition: "len([BODY].users == 100", expectedErr: nil}, + } + for _, scenario := range scenarios { + t.Run(string(scenario.condition), func(t *testing.T) { + if err := scenario.condition.Validate(); fmt.Sprint(err) != fmt.Sprint(scenario.expectedErr) { + t.Errorf("expected err %v, got %v", scenario.expectedErr, err) + } + }) + } +} + +func TestCondition_evaluate(t *testing.T) { + scenarios := []struct { + Name string + Condition Condition + Result *Result + DontResolveFailedConditions bool + ExpectedSuccess bool + ExpectedOutput string + }{ + { + Name: "ip", + Condition: Condition("[IP] == 127.0.0.1"), + Result: &Result{IP: "127.0.0.1"}, + ExpectedSuccess: true, + ExpectedOutput: "[IP] == 127.0.0.1", + }, + { + Name: "status", + Condition: Condition("[STATUS] == 200"), + Result: &Result{HTTPStatus: 200}, + ExpectedSuccess: true, + ExpectedOutput: "[STATUS] == 200", + }, + { + Name: "status-failure", + Condition: Condition("[STATUS] == 200"), + Result: &Result{HTTPStatus: 500}, + ExpectedSuccess: false, + ExpectedOutput: "[STATUS] (500) == 200", + }, + { + Name: "status-using-less-than", + Condition: Condition("[STATUS] < 300"), + Result: &Result{HTTPStatus: 201}, + ExpectedSuccess: true, + ExpectedOutput: "[STATUS] < 300", + }, + { + Name: "status-using-less-than-failure", + Condition: Condition("[STATUS] < 300"), + Result: &Result{HTTPStatus: 404}, + ExpectedSuccess: false, + ExpectedOutput: "[STATUS] (404) < 300", + }, + { + Name: "response-time-using-less-than", + Condition: Condition("[RESPONSE_TIME] < 500"), + Result: &Result{Duration: 50 * time.Millisecond}, + ExpectedSuccess: true, + ExpectedOutput: "[RESPONSE_TIME] < 500", + }, + { + Name: "response-time-using-less-than-with-duration", + Condition: Condition("[RESPONSE_TIME] < 1s"), + Result: &Result{Duration: 50 * time.Millisecond}, + ExpectedSuccess: true, + ExpectedOutput: "[RESPONSE_TIME] < 1s", + }, + { + Name: "response-time-using-less-than-invalid", + Condition: Condition("[RESPONSE_TIME] < potato"), + Result: &Result{Duration: 50 * time.Millisecond}, + ExpectedSuccess: false, + ExpectedOutput: "[RESPONSE_TIME] (50) < potato (0)", // Non-numerical values automatically resolve to 0 + }, + { + Name: "response-time-using-greater-than", + Condition: Condition("[RESPONSE_TIME] > 500"), + Result: &Result{Duration: 750 * time.Millisecond}, + ExpectedSuccess: true, + ExpectedOutput: "[RESPONSE_TIME] > 500", + }, + { + Name: "response-time-using-greater-than-with-duration", + Condition: Condition("[RESPONSE_TIME] > 1s"), + Result: &Result{Duration: 2 * time.Second}, + ExpectedSuccess: true, + ExpectedOutput: "[RESPONSE_TIME] > 1s", + }, + { + Name: "response-time-using-greater-than-or-equal-to-equal", + Condition: Condition("[RESPONSE_TIME] >= 500"), + Result: &Result{Duration: 500 * time.Millisecond}, + ExpectedSuccess: true, + ExpectedOutput: "[RESPONSE_TIME] >= 500", + }, + { + Name: "response-time-using-greater-than-or-equal-to-greater", + Condition: Condition("[RESPONSE_TIME] >= 500"), + Result: &Result{Duration: 499 * time.Millisecond}, + ExpectedSuccess: false, + ExpectedOutput: "[RESPONSE_TIME] (499) >= 500", + }, + { + Name: "response-time-using-greater-than-or-equal-to-failure", + Condition: Condition("[RESPONSE_TIME] >= 500"), + Result: &Result{Duration: 499 * time.Millisecond}, + ExpectedSuccess: false, + ExpectedOutput: "[RESPONSE_TIME] (499) >= 500", + }, + { + Name: "response-time-using-less-than-or-equal-to-equal", + Condition: Condition("[RESPONSE_TIME] <= 500"), + Result: &Result{Duration: 500 * time.Millisecond}, + ExpectedSuccess: true, + ExpectedOutput: "[RESPONSE_TIME] <= 500", + }, + { + Name: "response-time-using-less-than-or-equal-to-less", + Condition: Condition("[RESPONSE_TIME] <= 500"), + Result: &Result{Duration: 25 * time.Millisecond}, + ExpectedSuccess: true, + ExpectedOutput: "[RESPONSE_TIME] <= 500", + }, + { + Name: "response-time-using-less-than-or-equal-to-failure", + Condition: Condition("[RESPONSE_TIME] <= 500"), + Result: &Result{Duration: 750 * time.Millisecond}, + ExpectedSuccess: false, + ExpectedOutput: "[RESPONSE_TIME] (750) <= 500", + }, + { + Name: "body", + Condition: Condition("[BODY] == test"), + Result: &Result{Body: []byte("test")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY] == test", + }, + { + Name: "body-numerical-equal", + Condition: Condition("[BODY] == 123"), + Result: &Result{Body: []byte("123")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY] == 123", + }, + { + Name: "body-numerical-less-than", + Condition: Condition("[BODY] < 124"), + Result: &Result{Body: []byte("123")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY] < 124", + }, + { + Name: "body-numerical-greater-than", + Condition: Condition("[BODY] > 122"), + Result: &Result{Body: []byte("123")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY] > 122", + }, + { + Name: "body-numerical-greater-than-failure", + Condition: Condition("[BODY] > 123"), + Result: &Result{Body: []byte("100")}, + ExpectedSuccess: false, + ExpectedOutput: "[BODY] (100) > 123", + }, + { + Name: "body-jsonpath", + Condition: Condition("[BODY].status == UP"), + Result: &Result{Body: []byte("{\"status\":\"UP\"}")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY].status == UP", + }, + { + Name: "body-jsonpath-complex", + Condition: Condition("[BODY].data.name == john"), + Result: &Result{Body: []byte("{\"data\": {\"id\": 1, \"name\": \"john\"}}")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY].data.name == john", + }, + { + Name: "body-jsonpath-complex-invalid", + Condition: Condition("[BODY].data.name == john"), + Result: &Result{Body: []byte("{\"data\": {\"id\": 1}}")}, + ExpectedSuccess: false, + ExpectedOutput: "[BODY].data.name (INVALID) == john", + }, + { + Name: "body-jsonpath-complex-len-invalid", + Condition: Condition("len([BODY].data.name) == john"), + Result: &Result{Body: []byte("{\"data\": {\"id\": 1}}")}, + ExpectedSuccess: false, + ExpectedOutput: "len([BODY].data.name) (INVALID) == john", + }, + { + Name: "body-jsonpath-double-placeholder", + Condition: Condition("[BODY].user.firstName != [BODY].user.lastName"), + Result: &Result{Body: []byte("{\"user\": {\"firstName\": \"john\", \"lastName\": \"doe\"}}")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY].user.firstName != [BODY].user.lastName", + }, + { + Name: "body-jsonpath-double-placeholder-failure", + Condition: Condition("[BODY].user.firstName == [BODY].user.lastName"), + Result: &Result{Body: []byte("{\"user\": {\"firstName\": \"john\", \"lastName\": \"doe\"}}")}, + ExpectedSuccess: false, + ExpectedOutput: "[BODY].user.firstName (john) == [BODY].user.lastName (doe)", + }, + { + Name: "body-jsonpath-when-body-is-array", + Condition: Condition("[BODY][0].id == 1"), + Result: &Result{Body: []byte("[{\"id\": 1}, {\"id\": 2}]")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY][0].id == 1", + }, + { + Name: "body-jsonpath-when-body-has-null-parameter", + Condition: Condition("[BODY].data == OK"), + Result: &Result{Body: []byte(`{"data": null}"`)}, + ExpectedSuccess: false, + ExpectedOutput: "[BODY].data (INVALID) == OK", + }, + { + Name: "body-jsonpath-when-body-has-array-with-null", + Condition: Condition("[BODY].items[0] == OK"), + Result: &Result{Body: []byte(`{"items": [null, null]}"`)}, + ExpectedSuccess: false, + ExpectedOutput: "[BODY].items[0] (INVALID) == OK", + }, + { + Name: "body-jsonpath-when-body-is-null", + Condition: Condition("[BODY].data == OK"), + Result: &Result{Body: []byte(`null`)}, + ExpectedSuccess: false, + ExpectedOutput: "[BODY].data (INVALID) == OK", + }, + { + Name: "body-jsonpath-when-body-is-array-but-actual-body-is-not", + Condition: Condition("[BODY][0].name == test"), + Result: &Result{Body: []byte("{\"statusCode\": 500, \"message\": \"Internal Server Error\"}")}, + ExpectedSuccess: false, + ExpectedOutput: "[BODY][0].name (INVALID) == test", + }, + { + Name: "body-jsonpath-complex-int", + Condition: Condition("[BODY].data.id == 1"), + Result: &Result{Body: []byte("{\"data\": {\"id\": 1}}")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY].data.id == 1", + }, + { + Name: "body-jsonpath-complex-array-int", + Condition: Condition("[BODY].data[1].id == 2"), + Result: &Result{Body: []byte("{\"data\": [{\"id\": 1}, {\"id\": 2}, {\"id\": 3}]}")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY].data[1].id == 2", + }, + { + Name: "body-jsonpath-complex-int-using-greater-than", + Condition: Condition("[BODY].data.id > 0"), + Result: &Result{Body: []byte("{\"data\": {\"id\": 1}}")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY].data.id > 0", + }, + { + Name: "body-jsonpath-hexadecimal-int-using-greater-than", + Condition: Condition("[BODY].data > 0"), + Result: &Result{Body: []byte("{\"data\": \"0x1\"}")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY].data > 0", + }, + { + Name: "body-jsonpath-hexadecimal-int-using-equal-to-0x1", + Condition: Condition("[BODY].data == 1"), + Result: &Result{Body: []byte("{\"data\": \"0x1\"}")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY].data == 1", + }, + { + Name: "body-jsonpath-hexadecimal-int-using-equals", + Condition: Condition("[BODY].data == 0x1"), + Result: &Result{Body: []byte("{\"data\": \"0x1\"}")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY].data == 0x1", + }, + { + Name: "body-jsonpath-hexadecimal-int-using-equal-to-0x2", + Condition: Condition("[BODY].data == 2"), + Result: &Result{Body: []byte("{\"data\": \"0x2\"}")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY].data == 2", + }, + { + Name: "body-jsonpath-hexadecimal-int-using-equal-to-0xF", + Condition: Condition("[BODY].data == 15"), + Result: &Result{Body: []byte("{\"data\": \"0xF\"}")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY].data == 15", + }, + { + Name: "body-jsonpath-hexadecimal-int-using-equal-to-0xC0ff33", + Condition: Condition("[BODY].data == 12648243"), + Result: &Result{Body: []byte("{\"data\": \"0xC0ff33\"}")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY].data == 12648243", + }, + { + Name: "body-jsonpath-hexadecimal-int-len", + Condition: Condition("len([BODY].data) == 3"), + Result: &Result{Body: []byte("{\"data\": \"0x1\"}")}, + ExpectedSuccess: true, + ExpectedOutput: "len([BODY].data) == 3", + }, + { + Name: "body-jsonpath-hexadecimal-int-greater", + Condition: Condition("[BODY].data >= 1"), + Result: &Result{Body: []byte("{\"data\": \"0x01\"}")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY].data >= 1", + }, + { + Name: "body-jsonpath-hexadecimal-int-0x01-len", + Condition: Condition("len([BODY].data) == 4"), + Result: &Result{Body: []byte("{\"data\": \"0x01\"}")}, + ExpectedSuccess: true, + ExpectedOutput: "len([BODY].data) == 4", + }, + { + Name: "body-jsonpath-octal-int-using-greater-than", + Condition: Condition("[BODY].data > 0"), + Result: &Result{Body: []byte("{\"data\": \"0o1\"}")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY].data > 0", + }, + { + Name: "body-jsonpath-octal-int-using-equal", + Condition: Condition("[BODY].data == 2"), + Result: &Result{Body: []byte("{\"data\": \"0o2\"}")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY].data == 2", + }, + { + Name: "body-jsonpath-octal-int-using-equals", + Condition: Condition("[BODY].data == 0o2"), + Result: &Result{Body: []byte("{\"data\": \"0o2\"}")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY].data == 0o2", + }, + { + Name: "body-jsonpath-binary-int-using-greater-than", + Condition: Condition("[BODY].data > 0"), + Result: &Result{Body: []byte("{\"data\": \"0b1\"}")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY].data > 0", + }, + { + Name: "body-jsonpath-binary-int-using-equal", + Condition: Condition("[BODY].data == 2"), + Result: &Result{Body: []byte("{\"data\": \"0b0010\"}")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY].data == 2", + }, + { + Name: "body-jsonpath-binary-int-using-equals", + Condition: Condition("[BODY].data == 0b10"), + Result: &Result{Body: []byte("{\"data\": \"0b0010\"}")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY].data == 0b10", + }, + { + Name: "body-jsonpath-complex-int-using-greater-than-failure", + Condition: Condition("[BODY].data.id > 5"), + Result: &Result{Body: []byte("{\"data\": {\"id\": 1}}")}, + ExpectedSuccess: false, + ExpectedOutput: "[BODY].data.id (1) > 5", + }, + { + Name: "body-jsonpath-float-using-greater-than-issue433", // As of v5.3.1, Gatus will convert a float to an int. We're losing precision, but it's better than just returning 0 + Condition: Condition("[BODY].balance > 100"), + Result: &Result{Body: []byte(`{"balance": "123.40000000000005"}`)}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY].balance > 100", + }, + { + Name: "body-jsonpath-complex-int-using-less-than", + Condition: Condition("[BODY].data.id < 5"), + Result: &Result{Body: []byte("{\"data\": {\"id\": 2}}")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY].data.id < 5", + }, + { + Name: "body-jsonpath-complex-int-using-less-than-failure", + Condition: Condition("[BODY].data.id < 5"), + Result: &Result{Body: []byte("{\"data\": {\"id\": 10}}")}, + ExpectedSuccess: false, + ExpectedOutput: "[BODY].data.id (10) < 5", + }, + { + Name: "connected", + Condition: Condition("[CONNECTED] == true"), + Result: &Result{Connected: true}, + ExpectedSuccess: true, + ExpectedOutput: "[CONNECTED] == true", + }, + { + Name: "connected-failure", + Condition: Condition("[CONNECTED] == true"), + Result: &Result{Connected: false}, + ExpectedSuccess: false, + ExpectedOutput: "[CONNECTED] (false) == true", + }, + { + Name: "certificate-expiration-not-set", + Condition: Condition("[CERTIFICATE_EXPIRATION] == 0"), + Result: &Result{}, + ExpectedSuccess: true, + ExpectedOutput: "[CERTIFICATE_EXPIRATION] == 0", + }, + { + Name: "certificate-expiration-greater-than-numerical", + Condition: Condition("[CERTIFICATE_EXPIRATION] > " + strconv.FormatInt((time.Hour*24*28).Milliseconds(), 10)), + Result: &Result{CertificateExpiration: time.Hour * 24 * 60}, + ExpectedSuccess: true, + ExpectedOutput: "[CERTIFICATE_EXPIRATION] > 2419200000", + }, + { + Name: "certificate-expiration-greater-than-numerical-failure", + Condition: Condition("[CERTIFICATE_EXPIRATION] > " + strconv.FormatInt((time.Hour*24*28).Milliseconds(), 10)), + Result: &Result{CertificateExpiration: time.Hour * 24 * 14}, + ExpectedSuccess: false, + ExpectedOutput: "[CERTIFICATE_EXPIRATION] (1209600000) > 2419200000", + }, + { + Name: "certificate-expiration-greater-than-duration", + Condition: Condition("[CERTIFICATE_EXPIRATION] > 12h"), + Result: &Result{CertificateExpiration: 24 * time.Hour}, + ExpectedSuccess: true, + ExpectedOutput: "[CERTIFICATE_EXPIRATION] > 12h", + }, + { + Name: "certificate-expiration-greater-than-duration", + Condition: Condition("[CERTIFICATE_EXPIRATION] > 48h"), + Result: &Result{CertificateExpiration: 24 * time.Hour}, + ExpectedSuccess: false, + ExpectedOutput: "[CERTIFICATE_EXPIRATION] (86400000) > 48h (172800000)", + }, + { + Name: "no-placeholders", + Condition: Condition("1 == 2"), + Result: &Result{}, + ExpectedSuccess: false, + ExpectedOutput: "1 == 2", + }, + /////////////// + // Functions // + /////////////// + // len + { + Name: "len-body-jsonpath-complex", + Condition: Condition("len([BODY].data.name) == 4"), + Result: &Result{Body: []byte("{\"data\": {\"name\": \"john\"}}")}, + ExpectedSuccess: true, + ExpectedOutput: "len([BODY].data.name) == 4", + }, + { + Name: "len-body-array", + Condition: Condition("len([BODY]) == 3"), + Result: &Result{Body: []byte("[{\"id\": 1}, {\"id\": 2}, {\"id\": 3}]")}, + ExpectedSuccess: true, + ExpectedOutput: "len([BODY]) == 3", + }, + { + Name: "len-body-keyed-array", + Condition: Condition("len([BODY].data) == 3"), + Result: &Result{Body: []byte("{\"data\": [{\"id\": 1}, {\"id\": 2}, {\"id\": 3}]}")}, + ExpectedSuccess: true, + ExpectedOutput: "len([BODY].data) == 3", + }, + { + Name: "len-body-array-invalid", + Condition: Condition("len([BODY].data) == 8"), + Result: &Result{Body: []byte("{\"name\": \"john.doe\"}")}, + ExpectedSuccess: false, + ExpectedOutput: "len([BODY].data) (INVALID) == 8", + }, + { + Name: "len-body-string", + Condition: Condition("len([BODY]) == 8"), + Result: &Result{Body: []byte("john.doe")}, + ExpectedSuccess: true, + ExpectedOutput: "len([BODY]) == 8", + }, + { + Name: "len-body-keyed-string", + Condition: Condition("len([BODY].name) == 8"), + Result: &Result{Body: []byte("{\"name\": \"john.doe\"}")}, + ExpectedSuccess: true, + ExpectedOutput: "len([BODY].name) == 8", + }, + { + Name: "len-body-keyed-int", + Condition: Condition("len([BODY].age) == 2"), + Result: &Result{Body: []byte(`{"age":18}`)}, + ExpectedSuccess: true, + ExpectedOutput: "len([BODY].age) == 2", + }, + { + Name: "len-body-keyed-bool", + Condition: Condition("len([BODY].adult) == 4"), + Result: &Result{Body: []byte(`{"adult":true}`)}, + ExpectedSuccess: true, + ExpectedOutput: "len([BODY].adult) == 4", + }, + { + Name: "len-body-object-inside-array", + Condition: Condition("len([BODY][0]) == 23"), + Result: &Result{Body: []byte(`[{"age":18,"adult":true}]`)}, + ExpectedSuccess: true, + ExpectedOutput: "len([BODY][0]) == 23", + }, + { + Name: "len-body-object-keyed-int-inside-array", + Condition: Condition("len([BODY][0].age) == 2"), + Result: &Result{Body: []byte(`[{"age":18,"adult":true}]`)}, + ExpectedSuccess: true, + ExpectedOutput: "len([BODY][0].age) == 2", + }, + { + Name: "len-body-keyed-bool-inside-array", + Condition: Condition("len([BODY][0].adult) == 4"), + Result: &Result{Body: []byte(`[{"age":18,"adult":true}]`)}, + ExpectedSuccess: true, + ExpectedOutput: "len([BODY][0].adult) == 4", + }, + { + Name: "len-body-object", + Condition: Condition("len([BODY]) == 20"), + Result: &Result{Body: []byte("{\"name\": \"john.doe\"}")}, + ExpectedSuccess: true, + ExpectedOutput: "len([BODY]) == 20", + }, + // pat + { + Name: "pat-body-1", + Condition: Condition("[BODY] == pat(*john*)"), + Result: &Result{Body: []byte("{\"name\": \"john.doe\"}")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY] == pat(*john*)", + }, + { + Name: "pat-body-2", + Condition: Condition("[BODY].name == pat(john*)"), + Result: &Result{Body: []byte("{\"name\": \"john.doe\"}")}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY].name == pat(john*)", + }, + { + Name: "pat-body-failure", + Condition: Condition("[BODY].name == pat(bob*)"), + Result: &Result{Body: []byte("{\"name\": \"john.doe\"}")}, + ExpectedSuccess: false, + ExpectedOutput: "[BODY].name (john.doe) == pat(bob*)", + }, + { + Name: "pat-body-html", + Condition: Condition("[BODY] == pat(*
john.doe
*)"), + Result: &Result{Body: []byte(`
john.doe
`)}, + ExpectedSuccess: true, + ExpectedOutput: "[BODY] == pat(*
john.doe
*)", + }, + { + Name: "pat-body-html-failure", + Condition: Condition("[BODY] == pat(*
john.doe
*)"), + Result: &Result{Body: []byte(`
jane.doe
`)}, + ExpectedSuccess: false, + ExpectedOutput: "[BODY] (john.doe*)", + }, + { + Name: "pat-body-html-failure-alt", + Condition: Condition("pat(*
john.doe
*) == [BODY]"), + Result: &Result{Body: []byte(`
jane.doe
`)}, + ExpectedSuccess: false, + ExpectedOutput: "pat(*
john.doe
*) == [BODY] ( '") + + // ErrInvalidEndpointIntervalForDomainExpirationPlaceholder is the error with which Gatus will panic if an endpoint + // has both an interval smaller than 5 minutes and a condition with DomainExpirationPlaceholder. + // This is because the free whois service we are using should not be abused, especially considering the fact that + // the data takes a while to be updated. + ErrInvalidEndpointIntervalForDomainExpirationPlaceholder = errors.New("the minimum interval for an endpoint with a condition using the " + DomainExpirationPlaceholder + " placeholder is 300s (5m)") +) + +// Endpoint is the configuration of a service to be monitored +type Endpoint struct { + // Enabled defines whether to enable the monitoring of the endpoint + Enabled *bool `yaml:"enabled,omitempty"` + + // Name of the endpoint. Can be anything. + Name string `yaml:"name"` + + // Group the endpoint is a part of. Used for grouping multiple endpoints together on the front end. + Group string `yaml:"group,omitempty"` + + // URL to send the request to + URL string `yaml:"url"` + + // Method of the request made to the url of the endpoint + Method string `yaml:"method,omitempty"` + + // Body of the request + Body string `yaml:"body,omitempty"` + + // GraphQL is whether to wrap the body in a query param ({"query":"$body"}) + GraphQL bool `yaml:"graphql,omitempty"` + + // Headers of the request + Headers map[string]string `yaml:"headers,omitempty"` + + // Interval is the duration to wait between every status check + Interval time.Duration `yaml:"interval,omitempty"` + + // Conditions used to determine the health of the endpoint + Conditions []Condition `yaml:"conditions"` + + // Alerts is the alerting configuration for the endpoint in case of failure + Alerts []*alert.Alert `yaml:"alerts,omitempty"` + + // MaintenanceWindow is the configuration for per-endpoint maintenance windows + MaintenanceWindows []*maintenance.Config `yaml:"maintenance-windows,omitempty"` + + // DNSConfig is the configuration for DNS monitoring + DNSConfig *dns.Config `yaml:"dns,omitempty"` + + // SSH is the configuration for SSH monitoring + SSHConfig *sshconfig.Config `yaml:"ssh,omitempty"` + + // ClientConfig is the configuration of the client used to communicate with the endpoint's target + ClientConfig *client.Config `yaml:"client,omitempty"` + + // UIConfig is the configuration for the UI + UIConfig *ui.Config `yaml:"ui,omitempty"` + + // NumberOfFailuresInARow is the number of unsuccessful evaluations in a row + NumberOfFailuresInARow int `yaml:"-"` + + // NumberOfSuccessesInARow is the number of successful evaluations in a row + NumberOfSuccessesInARow int `yaml:"-"` +} + +// IsEnabled returns whether the endpoint is enabled or not +func (e *Endpoint) IsEnabled() bool { + if e.Enabled == nil { + return true + } + return *e.Enabled +} + +// Type returns the endpoint type +func (e *Endpoint) Type() Type { + switch { + case e.DNSConfig != nil: + return TypeDNS + case strings.HasPrefix(e.URL, "tcp://"): + return TypeTCP + case strings.HasPrefix(e.URL, "sctp://"): + return TypeSCTP + case strings.HasPrefix(e.URL, "udp://"): + return TypeUDP + case strings.HasPrefix(e.URL, "icmp://"): + return TypeICMP + case strings.HasPrefix(e.URL, "starttls://"): + return TypeSTARTTLS + case strings.HasPrefix(e.URL, "tls://"): + return TypeTLS + case strings.HasPrefix(e.URL, "http://") || strings.HasPrefix(e.URL, "https://"): + return TypeHTTP + case strings.HasPrefix(e.URL, "ws://") || strings.HasPrefix(e.URL, "wss://"): + return TypeWS + case strings.HasPrefix(e.URL, "ssh://"): + return TypeSSH + default: + return TypeUNKNOWN + } +} + +// ValidateAndSetDefaults validates the endpoint's configuration and sets the default value of args that have one +func (e *Endpoint) ValidateAndSetDefaults() error { + if err := validateEndpointNameGroupAndAlerts(e.Name, e.Group, e.Alerts); err != nil { + return err + } + if len(e.URL) == 0 { + return ErrEndpointWithNoURL + } + if e.ClientConfig == nil { + e.ClientConfig = client.GetDefaultConfig() + } else { + if err := e.ClientConfig.ValidateAndSetDefaults(); err != nil { + return err + } + } + if e.UIConfig == nil { + e.UIConfig = ui.GetDefaultConfig() + } else { + if err := e.UIConfig.ValidateAndSetDefaults(); err != nil { + return err + } + } + if e.Interval == 0 { + e.Interval = 1 * time.Minute + } + if len(e.Method) == 0 { + e.Method = http.MethodGet + } + if len(e.Headers) == 0 { + e.Headers = make(map[string]string) + } + // Automatically add user agent header if there isn't one specified in the endpoint configuration + if _, userAgentHeaderExists := e.Headers[UserAgentHeader]; !userAgentHeaderExists { + e.Headers[UserAgentHeader] = GatusUserAgent + } + // Automatically add "Content-Type: application/json" header if there's no Content-Type set + // and endpoint.GraphQL is set to true + if _, contentTypeHeaderExists := e.Headers[ContentTypeHeader]; !contentTypeHeaderExists && e.GraphQL { + e.Headers[ContentTypeHeader] = "application/json" + } + if len(e.Conditions) == 0 { + return ErrEndpointWithNoCondition + } + for _, c := range e.Conditions { + if e.Interval < 5*time.Minute && c.hasDomainExpirationPlaceholder() { + return ErrInvalidEndpointIntervalForDomainExpirationPlaceholder + } + if err := c.Validate(); err != nil { + return fmt.Errorf("%v: %w", ErrInvalidConditionFormat, err) + } + } + if e.DNSConfig != nil { + return e.DNSConfig.ValidateAndSetDefault() + } + if e.SSHConfig != nil { + return e.SSHConfig.Validate() + } + if e.Type() == TypeUNKNOWN { + return ErrUnknownEndpointType + } + for _, maintenanceWindow := range e.MaintenanceWindows { + if err := maintenanceWindow.ValidateAndSetDefaults(); err != nil { + return err + } + } + // Make sure that the request can be created + _, err := http.NewRequest(e.Method, e.URL, bytes.NewBuffer([]byte(e.Body))) + if err != nil { + return err + } + return nil +} + +// DisplayName returns an identifier made up of the Name and, if not empty, the Group. +func (e *Endpoint) DisplayName() string { + if len(e.Group) > 0 { + return e.Group + "/" + e.Name + } + return e.Name +} + +// Key returns the unique key for the Endpoint +func (e *Endpoint) Key() string { + return ConvertGroupAndEndpointNameToKey(e.Group, e.Name) +} + +// Close HTTP connections between watchdog and endpoints to avoid dangling socket file descriptors +// on configuration reload. +// More context on https://github.com/TwiN/gatus/issues/536 +func (e *Endpoint) Close() { + if e.Type() == TypeHTTP { + client.GetHTTPClient(e.ClientConfig).CloseIdleConnections() + } +} + +// EvaluateHealth sends a request to the endpoint's URL and evaluates the conditions of the endpoint. +func (e *Endpoint) EvaluateHealth() *Result { + result := &Result{Success: true, Errors: []string{}} + // Parse or extract hostname from URL + if e.DNSConfig != nil { + result.Hostname = strings.TrimSuffix(e.URL, ":53") + } else { + urlObject, err := url.Parse(e.URL) + if err != nil { + result.AddError(err.Error()) + } else { + result.Hostname = urlObject.Hostname() + result.port = urlObject.Port() + } + } + // Retrieve IP if necessary + if e.needsToRetrieveIP() { + e.getIP(result) + } + // Retrieve domain expiration if necessary + if e.needsToRetrieveDomainExpiration() && len(result.Hostname) > 0 { + var err error + if result.DomainExpiration, err = client.GetDomainExpiration(result.Hostname); err != nil { + result.AddError(err.Error()) + } + } + // Call the endpoint (if there's no errors) + if len(result.Errors) == 0 { + e.call(result) + } else { + result.Success = false + } + // Evaluate the conditions + for _, condition := range e.Conditions { + success := condition.evaluate(result, e.UIConfig.DontResolveFailedConditions) + if !success { + result.Success = false + } + } + result.Timestamp = time.Now() + // Clean up parameters that we don't need to keep in the results + if e.UIConfig.HideURL { + for errIdx, errorString := range result.Errors { + result.Errors[errIdx] = strings.ReplaceAll(errorString, e.URL, "") + } + } + if e.UIConfig.HideHostname { + for errIdx, errorString := range result.Errors { + result.Errors[errIdx] = strings.ReplaceAll(errorString, result.Hostname, "") + } + result.Hostname = "" // remove it from the result so it doesn't get exposed + } + if e.UIConfig.HidePort && len(result.port) > 0 { + for errIdx, errorString := range result.Errors { + result.Errors[errIdx] = strings.ReplaceAll(errorString, result.port, "") + } + result.port = "" + } + if e.UIConfig.HideConditions { + result.ConditionResults = nil + } + return result +} + +func (e *Endpoint) getIP(result *Result) { + if ips, err := net.LookupIP(result.Hostname); err != nil { + result.AddError(err.Error()) + return + } else { + result.IP = ips[0].String() + } +} + +func (e *Endpoint) call(result *Result) { + var request *http.Request + var response *http.Response + var err error + var certificate *x509.Certificate + endpointType := e.Type() + if endpointType == TypeHTTP { + request = e.buildHTTPRequest() + } + startTime := time.Now() + if endpointType == TypeDNS { + result.Connected, result.DNSRCode, result.Body, err = client.QueryDNS(e.DNSConfig.QueryType, e.DNSConfig.QueryName, e.URL) + if err != nil { + result.AddError(err.Error()) + return + } + result.Duration = time.Since(startTime) + } else if endpointType == TypeSTARTTLS || endpointType == TypeTLS { + if endpointType == TypeSTARTTLS { + result.Connected, certificate, err = client.CanPerformStartTLS(strings.TrimPrefix(e.URL, "starttls://"), e.ClientConfig) + } else { + result.Connected, certificate, err = client.CanPerformTLS(strings.TrimPrefix(e.URL, "tls://"), e.ClientConfig) + } + if err != nil { + result.AddError(err.Error()) + return + } + result.Duration = time.Since(startTime) + result.CertificateExpiration = time.Until(certificate.NotAfter) + } else if endpointType == TypeTCP { + result.Connected = client.CanCreateTCPConnection(strings.TrimPrefix(e.URL, "tcp://"), e.ClientConfig) + result.Duration = time.Since(startTime) + } else if endpointType == TypeUDP { + result.Connected = client.CanCreateUDPConnection(strings.TrimPrefix(e.URL, "udp://"), e.ClientConfig) + result.Duration = time.Since(startTime) + } else if endpointType == TypeSCTP { + result.Connected = client.CanCreateSCTPConnection(strings.TrimPrefix(e.URL, "sctp://"), e.ClientConfig) + result.Duration = time.Since(startTime) + } else if endpointType == TypeICMP { + result.Connected, result.Duration = client.Ping(strings.TrimPrefix(e.URL, "icmp://"), e.ClientConfig) + } else if endpointType == TypeWS { + result.Connected, result.Body, err = client.QueryWebSocket(e.URL, e.Body, e.ClientConfig) + if err != nil { + result.AddError(err.Error()) + return + } + result.Duration = time.Since(startTime) + } else if endpointType == TypeSSH { + // If there's no username/password specified, attempt to validate just the SSH banner + if len(e.SSHConfig.Username) == 0 && len(e.SSHConfig.Password) == 0 { + result.Connected, result.HTTPStatus, err = + client.CheckSSHBanner(strings.TrimPrefix(e.URL, "ssh://"), e.ClientConfig) + if err != nil { + result.AddError(err.Error()) + return + } + result.Success = result.Connected + result.Duration = time.Since(startTime) + return + } + var cli *ssh.Client + result.Connected, cli, err = client.CanCreateSSHConnection(strings.TrimPrefix(e.URL, "ssh://"), e.SSHConfig.Username, e.SSHConfig.Password, e.ClientConfig) + if err != nil { + result.AddError(err.Error()) + return + } + result.Success, result.HTTPStatus, err = client.ExecuteSSHCommand(cli, e.Body, e.ClientConfig) + if err != nil { + result.AddError(err.Error()) + return + } + result.Duration = time.Since(startTime) + } else { + response, err = client.GetHTTPClient(e.ClientConfig).Do(request) + result.Duration = time.Since(startTime) + if err != nil { + result.AddError(err.Error()) + return + } + defer response.Body.Close() + if response.TLS != nil && len(response.TLS.PeerCertificates) > 0 { + certificate = response.TLS.PeerCertificates[0] + result.CertificateExpiration = time.Until(certificate.NotAfter) + } + result.HTTPStatus = response.StatusCode + result.Connected = response.StatusCode > 0 + // Only read the Body if there's a condition that uses the BodyPlaceholder + if e.needsToReadBody() { + result.Body, err = io.ReadAll(response.Body) + if err != nil { + result.AddError("error reading response body:" + err.Error()) + } + } + } +} + +func (e *Endpoint) buildHTTPRequest() *http.Request { + var bodyBuffer *bytes.Buffer + if e.GraphQL { + graphQlBody := map[string]string{ + "query": e.Body, + } + body, _ := json.Marshal(graphQlBody) + bodyBuffer = bytes.NewBuffer(body) + } else { + bodyBuffer = bytes.NewBuffer([]byte(e.Body)) + } + request, _ := http.NewRequest(e.Method, e.URL, bodyBuffer) + for k, v := range e.Headers { + request.Header.Set(k, v) + if k == HostHeader { + request.Host = v + } + } + return request +} + +// needsToReadBody checks if there's any condition that requires the response Body to be read +func (e *Endpoint) needsToReadBody() bool { + for _, condition := range e.Conditions { + if condition.hasBodyPlaceholder() { + return true + } + } + return false +} + +// needsToRetrieveDomainExpiration checks if there's any condition that requires a whois query to be performed +func (e *Endpoint) needsToRetrieveDomainExpiration() bool { + for _, condition := range e.Conditions { + if condition.hasDomainExpirationPlaceholder() { + return true + } + } + return false +} + +// needsToRetrieveIP checks if there's any condition that requires an IP lookup +func (e *Endpoint) needsToRetrieveIP() bool { + for _, condition := range e.Conditions { + if condition.hasIPPlaceholder() { + return true + } + } + return false +} diff --git a/config/endpoint/endpoint_test.go b/config/endpoint/endpoint_test.go new file mode 100644 index 00000000..4d65ff90 --- /dev/null +++ b/config/endpoint/endpoint_test.go @@ -0,0 +1,934 @@ +package endpoint + +import ( + "bytes" + "crypto/tls" + "crypto/x509" + "errors" + "io" + "net/http" + "strings" + "testing" + "time" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/config/endpoint/dns" + "github.com/TwiN/gatus/v5/config/endpoint/ssh" + "github.com/TwiN/gatus/v5/config/endpoint/ui" + "github.com/TwiN/gatus/v5/config/maintenance" + "github.com/TwiN/gatus/v5/test" +) + +func TestEndpoint(t *testing.T) { + defer client.InjectHTTPClient(nil) + scenarios := []struct { + Name string + Endpoint Endpoint + ExpectedResult *Result + MockRoundTripper test.MockRoundTripper + }{ + { + Name: "success", + Endpoint: Endpoint{ + Name: "website-health", + URL: "https://twin.sh/health", + Conditions: []Condition{"[STATUS] == 200", "[BODY].status == UP", "[CERTIFICATE_EXPIRATION] > 24h"}, + }, + ExpectedResult: &Result{ + Success: true, + Connected: true, + Hostname: "twin.sh", + ConditionResults: []*ConditionResult{ + {Condition: "[STATUS] == 200", Success: true}, + {Condition: "[BODY].status == UP", Success: true}, + {Condition: "[CERTIFICATE_EXPIRATION] > 24h", Success: true}, + }, + DomainExpiration: 0, // Because there's no [DOMAIN_EXPIRATION] condition, this is not resolved, so it should be 0. + }, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewBufferString(`{"status": "UP"}`)), + TLS: &tls.ConnectionState{PeerCertificates: []*x509.Certificate{{NotAfter: time.Now().Add(9999 * time.Hour)}}}, + } + }), + }, + { + Name: "failed-body-condition", + Endpoint: Endpoint{ + Name: "website-health", + URL: "https://twin.sh/health", + Conditions: []Condition{"[STATUS] == 200", "[BODY].status == UP"}, + }, + ExpectedResult: &Result{ + Success: false, + Connected: true, + Hostname: "twin.sh", + ConditionResults: []*ConditionResult{ + {Condition: "[STATUS] == 200", Success: true}, + {Condition: "[BODY].status (DOWN) == UP", Success: false}, + }, + DomainExpiration: 0, // Because there's no [DOMAIN_EXPIRATION] condition, this is not resolved, so it should be 0. + }, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewBufferString(`{"status": "DOWN"}`))} + }), + }, + { + Name: "failed-status-condition", + Endpoint: Endpoint{ + Name: "website-health", + URL: "https://twin.sh/health", + Conditions: []Condition{"[STATUS] == 200"}, + }, + ExpectedResult: &Result{ + Success: false, + Connected: true, + Hostname: "twin.sh", + ConditionResults: []*ConditionResult{ + {Condition: "[STATUS] (502) == 200", Success: false}, + }, + DomainExpiration: 0, // Because there's no [DOMAIN_EXPIRATION] condition, this is not resolved, so it should be 0. + }, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusBadGateway, Body: http.NoBody} + }), + }, + { + Name: "failed-status-condition-with-hidden-conditions", + Endpoint: Endpoint{ + Name: "website-health", + URL: "https://twin.sh/health", + Conditions: []Condition{"[STATUS] == 200"}, + UIConfig: &ui.Config{HideConditions: true}, + }, + ExpectedResult: &Result{ + Success: false, + Connected: true, + Hostname: "twin.sh", + ConditionResults: []*ConditionResult{}, // Because UIConfig.HideConditions is true, the condition results should not be shown. + DomainExpiration: 0, // Because there's no [DOMAIN_EXPIRATION] condition, this is not resolved, so it should be 0. + }, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusBadGateway, Body: http.NoBody} + }), + }, + { + Name: "condition-with-failed-certificate-expiration", + Endpoint: Endpoint{ + Name: "website-health", + URL: "https://twin.sh/health", + Conditions: []Condition{"[CERTIFICATE_EXPIRATION] > 100h"}, + UIConfig: &ui.Config{DontResolveFailedConditions: true}, + }, + ExpectedResult: &Result{ + Success: false, + Connected: true, + Hostname: "twin.sh", + ConditionResults: []*ConditionResult{ + // Because UIConfig.DontResolveFailedConditions is true, the values in the condition should not be resolved + {Condition: "[CERTIFICATE_EXPIRATION] > 100h", Success: false}, + }, + DomainExpiration: 0, // Because there's no [DOMAIN_EXPIRATION] condition, this is not resolved, so it should be 0. + }, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{ + StatusCode: http.StatusOK, + Body: http.NoBody, + TLS: &tls.ConnectionState{PeerCertificates: []*x509.Certificate{{NotAfter: time.Now().Add(5 * time.Hour)}}}, + } + }), + }, + { + Name: "domain-expiration", + Endpoint: Endpoint{ + Name: "website-health", + URL: "https://twin.sh/health", + Conditions: []Condition{"[DOMAIN_EXPIRATION] > 100h"}, + Interval: 5 * time.Minute, + }, + ExpectedResult: &Result{ + Success: true, + Connected: true, + Hostname: "twin.sh", + ConditionResults: []*ConditionResult{ + {Condition: "[DOMAIN_EXPIRATION] > 100h", Success: true}, + }, + DomainExpiration: 999999 * time.Hour, // Note that this test only checks if it's non-zero. + }, + MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + }, + { + Name: "endpoint-that-will-time-out-and-hidden-hostname", + Endpoint: Endpoint{ + Name: "endpoint-that-will-time-out", + URL: "https://twin.sh:9999/health", + Conditions: []Condition{"[CONNECTED] == true"}, + UIConfig: &ui.Config{HideHostname: true, HidePort: true}, + ClientConfig: &client.Config{Timeout: time.Millisecond}, + }, + ExpectedResult: &Result{ + Success: false, + Connected: false, + Hostname: "", // Because Endpoint.UIConfig.HideHostname is true, this should be empty. + ConditionResults: []*ConditionResult{ + {Condition: "[CONNECTED] (false) == true", Success: false}, + }, + // Because there's no [DOMAIN_EXPIRATION] condition, this is not resolved, so it should be 0. + DomainExpiration: 0, + // Because Endpoint.UIConfig.HideHostname is true, the hostname should be replaced by . + Errors: []string{`Get "https://:/health": context deadline exceeded (Client.Timeout exceeded while awaiting headers)`}, + }, + MockRoundTripper: nil, + }, + { + Name: "endpoint-that-will-time-out-and-hidden-url", + Endpoint: Endpoint{ + Name: "endpoint-that-will-time-out", + URL: "https://twin.sh/health", + Conditions: []Condition{"[CONNECTED] == true"}, + UIConfig: &ui.Config{HideURL: true}, + ClientConfig: &client.Config{Timeout: time.Millisecond}, + }, + ExpectedResult: &Result{ + Success: false, + Connected: false, + Hostname: "twin.sh", + ConditionResults: []*ConditionResult{ + {Condition: "[CONNECTED] (false) == true", Success: false}, + }, + // Because there's no [DOMAIN_EXPIRATION] condition, this is not resolved, so it should be 0. + DomainExpiration: 0, + // Because Endpoint.UIConfig.HideURL is true, the URL should be replaced by . + Errors: []string{`Get "": context deadline exceeded (Client.Timeout exceeded while awaiting headers)`}, + }, + MockRoundTripper: nil, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + if scenario.MockRoundTripper != nil { + mockClient := &http.Client{Transport: scenario.MockRoundTripper} + if scenario.Endpoint.ClientConfig != nil && scenario.Endpoint.ClientConfig.Timeout > 0 { + mockClient.Timeout = scenario.Endpoint.ClientConfig.Timeout + } + client.InjectHTTPClient(mockClient) + } else { + client.InjectHTTPClient(nil) + } + err := scenario.Endpoint.ValidateAndSetDefaults() + if err != nil { + t.Error("did not expect an error, got", err) + } + result := scenario.Endpoint.EvaluateHealth() + if result.Success != scenario.ExpectedResult.Success { + t.Errorf("Expected success to be %v, got %v", scenario.ExpectedResult.Success, result.Success) + } + if result.Connected != scenario.ExpectedResult.Connected { + t.Errorf("Expected connected to be %v, got %v", scenario.ExpectedResult.Connected, result.Connected) + } + if result.Hostname != scenario.ExpectedResult.Hostname { + t.Errorf("Expected hostname to be %v, got %v", scenario.ExpectedResult.Hostname, result.Hostname) + } + if len(result.ConditionResults) != len(scenario.ExpectedResult.ConditionResults) { + t.Errorf("Expected %v condition results, got %v", len(scenario.ExpectedResult.ConditionResults), len(result.ConditionResults)) + } else { + for i, conditionResult := range result.ConditionResults { + if conditionResult.Condition != scenario.ExpectedResult.ConditionResults[i].Condition { + t.Errorf("Expected condition to be %v, got %v", scenario.ExpectedResult.ConditionResults[i].Condition, conditionResult.Condition) + } + if conditionResult.Success != scenario.ExpectedResult.ConditionResults[i].Success { + t.Errorf("Expected success of condition '%s' to be %v, got %v", conditionResult.Condition, scenario.ExpectedResult.ConditionResults[i].Success, conditionResult.Success) + } + } + } + if len(result.Errors) != len(scenario.ExpectedResult.Errors) { + t.Errorf("Expected %v errors, got %v", len(scenario.ExpectedResult.Errors), len(result.Errors)) + } else { + for i, err := range result.Errors { + if err != scenario.ExpectedResult.Errors[i] { + t.Errorf("Expected error to be %v, got %v", scenario.ExpectedResult.Errors[i], err) + } + } + } + if result.DomainExpiration != scenario.ExpectedResult.DomainExpiration { + // Note that DomainExpiration is only resolved if there's a condition with the DomainExpirationPlaceholder in it. + // In other words, if there's no condition with [DOMAIN_EXPIRATION] in it, the DomainExpiration field will be 0. + // Because this is a live call, mocking it would be too much of a pain, so we're just going to check if + // the actual value is non-zero when the expected result is non-zero. + if scenario.ExpectedResult.DomainExpiration.Hours() > 0 && !(result.DomainExpiration.Hours() > 0) { + t.Errorf("Expected domain expiration to be non-zero, got %v", result.DomainExpiration) + } + } + }) + } +} + +func TestEndpoint_IsEnabled(t *testing.T) { + if !(&Endpoint{Enabled: nil}).IsEnabled() { + t.Error("endpoint.IsEnabled() should've returned true, because Enabled was set to nil") + } + if value := false; (&Endpoint{Enabled: &value}).IsEnabled() { + t.Error("endpoint.IsEnabled() should've returned false, because Enabled was set to false") + } + if value := true; !(&Endpoint{Enabled: &value}).IsEnabled() { + t.Error("Endpoint.IsEnabled() should've returned true, because Enabled was set to true") + } +} + +func TestEndpoint_Type(t *testing.T) { + type args struct { + URL string + DNS *dns.Config + SSH *ssh.Config + } + tests := []struct { + args args + want Type + }{ + { + args: args{ + URL: "8.8.8.8", + DNS: &dns.Config{ + QueryType: "A", + QueryName: "example.com", + }, + }, + want: TypeDNS, + }, + { + args: args{ + URL: "tcp://127.0.0.1:6379", + }, + want: TypeTCP, + }, + { + args: args{ + URL: "icmp://example.com", + }, + want: TypeICMP, + }, + { + args: args{ + URL: "sctp://example.com", + }, + want: TypeSCTP, + }, + { + args: args{ + URL: "udp://example.com", + }, + want: TypeUDP, + }, + { + args: args{ + URL: "starttls://smtp.gmail.com:587", + }, + want: TypeSTARTTLS, + }, + { + args: args{ + URL: "tls://example.com:443", + }, + want: TypeTLS, + }, + { + args: args{ + URL: "https://twin.sh/health", + }, + want: TypeHTTP, + }, + { + args: args{ + URL: "wss://example.com/", + }, + want: TypeWS, + }, + { + args: args{ + URL: "ws://example.com/", + }, + want: TypeWS, + }, + { + args: args{ + URL: "ssh://example.com:22", + SSH: &ssh.Config{ + Username: "root", + Password: "password", + }, + }, + want: TypeSSH, + }, + { + args: args{ + URL: "invalid://example.org", + }, + want: TypeUNKNOWN, + }, + { + args: args{ + URL: "no-scheme", + }, + want: TypeUNKNOWN, + }, + } + for _, tt := range tests { + t.Run(string(tt.want), func(t *testing.T) { + endpoint := Endpoint{ + URL: tt.args.URL, + DNSConfig: tt.args.DNS, + } + if got := endpoint.Type(); got != tt.want { + t.Errorf("Endpoint.Type() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestEndpoint_ValidateAndSetDefaults(t *testing.T) { + endpoint := Endpoint{ + Name: "website-health", + URL: "https://twin.sh/health", + Conditions: []Condition{Condition("[STATUS] == 200")}, + Alerts: []*alert.Alert{{Type: alert.TypePagerDuty}}, + MaintenanceWindows: []*maintenance.Config{{Start: "03:50", Duration: 4 * time.Hour}}, + } + if err := endpoint.ValidateAndSetDefaults(); err != nil { + t.Errorf("Expected no error, got %v", err) + } + if endpoint.ClientConfig == nil { + t.Error("client configuration should've been set to the default configuration") + } else { + if endpoint.ClientConfig.Insecure != client.GetDefaultConfig().Insecure { + t.Errorf("Default client configuration should've set Insecure to %v, got %v", client.GetDefaultConfig().Insecure, endpoint.ClientConfig.Insecure) + } + if endpoint.ClientConfig.IgnoreRedirect != client.GetDefaultConfig().IgnoreRedirect { + t.Errorf("Default client configuration should've set IgnoreRedirect to %v, got %v", client.GetDefaultConfig().IgnoreRedirect, endpoint.ClientConfig.IgnoreRedirect) + } + if endpoint.ClientConfig.Timeout != client.GetDefaultConfig().Timeout { + t.Errorf("Default client configuration should've set Timeout to %v, got %v", client.GetDefaultConfig().Timeout, endpoint.ClientConfig.Timeout) + } + } + if endpoint.Method != "GET" { + t.Error("Endpoint method should've defaulted to GET") + } + if endpoint.Interval != time.Minute { + t.Error("Endpoint interval should've defaulted to 1 minute") + } + if endpoint.Headers == nil { + t.Error("Endpoint headers should've defaulted to an empty map") + } + if len(endpoint.Alerts) != 1 { + t.Error("Endpoint should've had 1 alert") + } + if !endpoint.Alerts[0].IsEnabled() { + t.Error("Endpoint alert should've defaulted to true") + } + if endpoint.Alerts[0].SuccessThreshold != 2 { + t.Error("Endpoint alert should've defaulted to a success threshold of 2") + } + if endpoint.Alerts[0].FailureThreshold != 3 { + t.Error("Endpoint alert should've defaulted to a failure threshold of 3") + } + if len(endpoint.MaintenanceWindows) != 1 { + t.Error("Endpoint should've had 1 maintenance window") + } + if !endpoint.MaintenanceWindows[0].IsEnabled() { + t.Error("Endpoint maintenance should've defaulted to true") + } + if endpoint.MaintenanceWindows[0].Timezone != "UTC" { + t.Error("Endpoint maintenance should've defaulted to UTC") + } +} + +func TestEndpoint_ValidateAndSetDefaultsWithInvalidCondition(t *testing.T) { + endpoint := Endpoint{ + Name: "invalid-condition", + URL: "https://twin.sh/health", + Conditions: []Condition{"[STATUS] invalid 200"}, + } + if err := endpoint.ValidateAndSetDefaults(); err == nil { + t.Error("endpoint validation should've returned an error, but didn't") + } +} + +func TestEndpoint_ValidateAndSetDefaultsWithClientConfig(t *testing.T) { + endpoint := Endpoint{ + Name: "website-health", + URL: "https://twin.sh/health", + Conditions: []Condition{Condition("[STATUS] == 200")}, + ClientConfig: &client.Config{ + Insecure: true, + IgnoreRedirect: true, + Timeout: 0, + }, + } + err := endpoint.ValidateAndSetDefaults() + if err != nil { + t.Fatal("did not expect an error, got", err) + } + if endpoint.ClientConfig == nil { + t.Error("client configuration should've been set to the default configuration") + } else { + if !endpoint.ClientConfig.Insecure { + t.Error("endpoint.ClientConfig.Insecure should've been set to true") + } + if !endpoint.ClientConfig.IgnoreRedirect { + t.Error("endpoint.ClientConfig.IgnoreRedirect should've been set to true") + } + if endpoint.ClientConfig.Timeout != client.GetDefaultConfig().Timeout { + t.Error("endpoint.ClientConfig.Timeout should've been set to 10s, because the timeout value entered is not set or invalid") + } + } +} + +func TestEndpoint_ValidateAndSetDefaultsWithDNS(t *testing.T) { + endpoint := &Endpoint{ + Name: "dns-test", + URL: "https://example.com", + DNSConfig: &dns.Config{ + QueryType: "A", + QueryName: "example.com", + }, + Conditions: []Condition{Condition("[DNS_RCODE] == NOERROR")}, + } + err := endpoint.ValidateAndSetDefaults() + if err != nil { + t.Error("did not expect an error, got", err) + } + if endpoint.DNSConfig.QueryName != "example.com." { + t.Error("Endpoint.dns.query-name should be formatted with . suffix") + } +} + +func TestEndpoint_ValidateAndSetDefaultsWithSSH(t *testing.T) { + scenarios := []struct { + name string + username string + password string + expectedErr error + }{ + { + name: "fail when has no user", + username: "", + password: "password", + expectedErr: ssh.ErrEndpointWithoutSSHUsername, + }, + { + name: "fail when has no password", + username: "username", + password: "", + expectedErr: ssh.ErrEndpointWithoutSSHPassword, + }, + { + name: "success when all fields are set", + username: "username", + password: "password", + expectedErr: nil, + }, + } + + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + endpoint := &Endpoint{ + Name: "ssh-test", + URL: "https://example.com", + SSHConfig: &ssh.Config{ + Username: scenario.username, + Password: scenario.password, + }, + Conditions: []Condition{Condition("[STATUS] == 0")}, + } + err := endpoint.ValidateAndSetDefaults() + if !errors.Is(err, scenario.expectedErr) { + t.Errorf("expected error %v, got %v", scenario.expectedErr, err) + } + }) + } +} + +func TestEndpoint_ValidateAndSetDefaultsWithSimpleErrors(t *testing.T) { + scenarios := []struct { + endpoint *Endpoint + expectedErr error + }{ + { + endpoint: &Endpoint{ + Name: "", + URL: "https://example.com", + Conditions: []Condition{Condition("[STATUS] == 200")}, + }, + expectedErr: ErrEndpointWithNoName, + }, + { + endpoint: &Endpoint{ + Name: "endpoint-with-no-url", + URL: "", + Conditions: []Condition{Condition("[STATUS] == 200")}, + }, + expectedErr: ErrEndpointWithNoURL, + }, + { + endpoint: &Endpoint{ + Name: "endpoint-with-no-conditions", + URL: "https://example.com", + Conditions: nil, + }, + expectedErr: ErrEndpointWithNoCondition, + }, + { + endpoint: &Endpoint{ + Name: "domain-expiration-with-bad-interval", + URL: "https://example.com", + Interval: time.Minute, + Conditions: []Condition{Condition("[DOMAIN_EXPIRATION] > 720h")}, + }, + expectedErr: ErrInvalidEndpointIntervalForDomainExpirationPlaceholder, + }, + { + endpoint: &Endpoint{ + Name: "domain-expiration-with-good-interval", + URL: "https://example.com", + Interval: 5 * time.Minute, + Conditions: []Condition{Condition("[DOMAIN_EXPIRATION] > 720h")}, + }, + expectedErr: nil, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.endpoint.Name, func(t *testing.T) { + if err := scenario.endpoint.ValidateAndSetDefaults(); err != scenario.expectedErr { + t.Errorf("Expected error %v, got %v", scenario.expectedErr, err) + } + }) + } +} + +func TestEndpoint_buildHTTPRequest(t *testing.T) { + condition := Condition("[STATUS] == 200") + endpoint := Endpoint{ + Name: "website-health", + URL: "https://twin.sh/health", + Conditions: []Condition{condition}, + } + err := endpoint.ValidateAndSetDefaults() + if err != nil { + t.Fatal("did not expect an error, got", err) + } + request := endpoint.buildHTTPRequest() + if request.Method != "GET" { + t.Error("request.Method should've been GET, but was", request.Method) + } + if request.Host != "twin.sh" { + t.Error("request.Host should've been twin.sh, but was", request.Host) + } + if userAgent := request.Header.Get("User-Agent"); userAgent != GatusUserAgent { + t.Errorf("request.Header.Get(User-Agent) should've been %s, but was %s", GatusUserAgent, userAgent) + } +} + +func TestEndpoint_buildHTTPRequestWithCustomUserAgent(t *testing.T) { + condition := Condition("[STATUS] == 200") + endpoint := Endpoint{ + Name: "website-health", + URL: "https://twin.sh/health", + Conditions: []Condition{condition}, + Headers: map[string]string{ + "User-Agent": "Test/2.0", + }, + } + err := endpoint.ValidateAndSetDefaults() + if err != nil { + t.Fatal("did not expect an error, got", err) + } + request := endpoint.buildHTTPRequest() + if request.Method != "GET" { + t.Error("request.Method should've been GET, but was", request.Method) + } + if request.Host != "twin.sh" { + t.Error("request.Host should've been twin.sh, but was", request.Host) + } + if userAgent := request.Header.Get("User-Agent"); userAgent != "Test/2.0" { + t.Errorf("request.Header.Get(User-Agent) should've been %s, but was %s", "Test/2.0", userAgent) + } +} + +func TestEndpoint_buildHTTPRequestWithHostHeader(t *testing.T) { + condition := Condition("[STATUS] == 200") + endpoint := Endpoint{ + Name: "website-health", + URL: "https://twin.sh/health", + Method: "POST", + Conditions: []Condition{condition}, + Headers: map[string]string{ + "Host": "example.com", + }, + } + err := endpoint.ValidateAndSetDefaults() + if err != nil { + t.Fatal("did not expect an error, got", err) + } + request := endpoint.buildHTTPRequest() + if request.Method != "POST" { + t.Error("request.Method should've been POST, but was", request.Method) + } + if request.Host != "example.com" { + t.Error("request.Host should've been example.com, but was", request.Host) + } +} + +func TestEndpoint_buildHTTPRequestWithGraphQLEnabled(t *testing.T) { + condition := Condition("[STATUS] == 200") + endpoint := Endpoint{ + Name: "website-graphql", + URL: "https://twin.sh/graphql", + Method: "POST", + Conditions: []Condition{condition}, + GraphQL: true, + Body: `{ + users(gender: "female") { + id + name + gender + avatar + } +}`, + } + err := endpoint.ValidateAndSetDefaults() + if err != nil { + t.Fatal("did not expect an error, got", err) + } + request := endpoint.buildHTTPRequest() + if request.Method != "POST" { + t.Error("request.Method should've been POST, but was", request.Method) + } + if contentType := request.Header.Get(ContentTypeHeader); contentType != "application/json" { + t.Error("request.Header.Content-Type should've been application/json, but was", contentType) + } + body, _ := io.ReadAll(request.Body) + if !strings.HasPrefix(string(body), "{\"query\":") { + t.Error("request.body should've started with '{\"query\":', but it didn't:", string(body)) + } +} + +func TestIntegrationEvaluateHealth(t *testing.T) { + condition := Condition("[STATUS] == 200") + bodyCondition := Condition("[BODY].status == UP") + endpoint := Endpoint{ + Name: "website-health", + URL: "https://twin.sh/health", + Conditions: []Condition{condition, bodyCondition}, + } + err := endpoint.ValidateAndSetDefaults() + if err != nil { + t.Fatal("did not expect an error, got", err) + } + result := endpoint.EvaluateHealth() + if !result.ConditionResults[0].Success { + t.Errorf("Condition '%s' should have been a success", condition) + } + if !result.Connected { + t.Error("Because the connection has been established, result.Connected should've been true") + } + if !result.Success { + t.Error("Because all conditions passed, this should have been a success") + } + if result.Hostname != "twin.sh" { + t.Error("result.Hostname should've been twin.sh, but was", result.Hostname) + } +} + +func TestIntegrationEvaluateHealthWithErrorAndHideURL(t *testing.T) { + endpoint := Endpoint{ + Name: "invalid-url", + URL: "https://httpstat.us/200?sleep=100", + Conditions: []Condition{Condition("[STATUS] == 200")}, + ClientConfig: &client.Config{ + Timeout: 1 * time.Millisecond, + }, + UIConfig: &ui.Config{ + HideURL: true, + }, + } + err := endpoint.ValidateAndSetDefaults() + if err != nil { + t.Fatal("did not expect an error, got", err) + } + result := endpoint.EvaluateHealth() + if result.Success { + t.Error("Because one of the conditions was invalid, result.Success should have been false") + } + if len(result.Errors) == 0 { + t.Error("There should've been an error") + } + if !strings.Contains(result.Errors[0], "") || strings.Contains(result.Errors[0], endpoint.URL) { + t.Error("result.Errors[0] should've had the URL redacted because ui.hide-url is set to true") + } +} + +func TestIntegrationEvaluateHealthForDNS(t *testing.T) { + conditionSuccess := Condition("[DNS_RCODE] == NOERROR") + conditionBody := Condition("[BODY] == pat(*.*.*.*)") + endpoint := Endpoint{ + Name: "example", + URL: "8.8.8.8", + DNSConfig: &dns.Config{ + QueryType: "A", + QueryName: "example.com.", + }, + Conditions: []Condition{conditionSuccess, conditionBody}, + } + err := endpoint.ValidateAndSetDefaults() + if err != nil { + t.Fatal("did not expect an error, got", err) + } + result := endpoint.EvaluateHealth() + if !result.ConditionResults[0].Success { + t.Errorf("Conditions '%s' and '%s' should have been a success", conditionSuccess, conditionBody) + } + if !result.Connected { + t.Error("Because the connection has been established, result.Connected should've been true") + } + if !result.Success { + t.Error("Because all conditions passed, this should have been a success") + } +} + +func TestIntegrationEvaluateHealthForSSH(t *testing.T) { + scenarios := []struct { + name string + endpoint Endpoint + conditions []Condition + success bool + }{ + { + name: "ssh-success", + endpoint: Endpoint{ + Name: "ssh-success", + URL: "ssh://localhost", + SSHConfig: &ssh.Config{ + Username: "scenario", + Password: "scenario", + }, + Body: "{ \"command\": \"uptime\" }", + }, + conditions: []Condition{Condition("[STATUS] == 0")}, + success: true, + }, + { + name: "ssh-failure", + endpoint: Endpoint{ + Name: "ssh-failure", + URL: "ssh://localhost", + SSHConfig: &ssh.Config{ + Username: "scenario", + Password: "scenario", + }, + Body: "{ \"command\": \"uptime\" }", + }, + conditions: []Condition{Condition("[STATUS] == 1")}, + success: false, + }, + } + + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + scenario.endpoint.ValidateAndSetDefaults() + scenario.endpoint.Conditions = scenario.conditions + result := scenario.endpoint.EvaluateHealth() + if result.Success != scenario.success { + t.Errorf("Expected success to be %v, but was %v", scenario.success, result.Success) + } + }) + } +} + +func TestIntegrationEvaluateHealthForICMP(t *testing.T) { + endpoint := Endpoint{ + Name: "icmp-test", + URL: "icmp://127.0.0.1", + Conditions: []Condition{"[CONNECTED] == true"}, + } + err := endpoint.ValidateAndSetDefaults() + if err != nil { + t.Fatal("did not expect an error, got", err) + } + result := endpoint.EvaluateHealth() + if !result.ConditionResults[0].Success { + t.Errorf("Conditions '%s' should have been a success", endpoint.Conditions[0]) + } + if !result.Connected { + t.Error("Because the connection has been established, result.Connected should've been true") + } + if !result.Success { + t.Error("Because all conditions passed, this should have been a success") + } +} + +func TestEndpoint_DisplayName(t *testing.T) { + if endpoint := (Endpoint{Name: "n"}); endpoint.DisplayName() != "n" { + t.Error("endpoint.DisplayName() should've been 'n', but was", endpoint.DisplayName()) + } + if endpoint := (Endpoint{Group: "g", Name: "n"}); endpoint.DisplayName() != "g/n" { + t.Error("endpoint.DisplayName() should've been 'g/n', but was", endpoint.DisplayName()) + } +} + +func TestEndpoint_getIP(t *testing.T) { + endpoint := Endpoint{ + Name: "invalid-url-test", + URL: "", + Conditions: []Condition{"[CONNECTED] == true"}, + } + result := &Result{} + endpoint.getIP(result) + if len(result.Errors) == 0 { + t.Error("endpoint.getIP(result) should've thrown an error because the URL is invalid, thus cannot be parsed") + } +} + +func TestEndpoint_needsToReadBody(t *testing.T) { + statusCondition := Condition("[STATUS] == 200") + bodyCondition := Condition("[BODY].status == UP") + bodyConditionWithLength := Condition("len([BODY].tags) > 0") + if (&Endpoint{Conditions: []Condition{statusCondition}}).needsToReadBody() { + t.Error("expected false, got true") + } + if !(&Endpoint{Conditions: []Condition{bodyCondition}}).needsToReadBody() { + t.Error("expected true, got false") + } + if !(&Endpoint{Conditions: []Condition{bodyConditionWithLength}}).needsToReadBody() { + t.Error("expected true, got false") + } + if !(&Endpoint{Conditions: []Condition{statusCondition, bodyCondition}}).needsToReadBody() { + t.Error("expected true, got false") + } + if !(&Endpoint{Conditions: []Condition{bodyCondition, statusCondition}}).needsToReadBody() { + t.Error("expected true, got false") + } + if !(&Endpoint{Conditions: []Condition{bodyConditionWithLength, statusCondition}}).needsToReadBody() { + t.Error("expected true, got false") + } +} + +func TestEndpoint_needsToRetrieveDomainExpiration(t *testing.T) { + if (&Endpoint{Conditions: []Condition{"[STATUS] == 200"}}).needsToRetrieveDomainExpiration() { + t.Error("expected false, got true") + } + if !(&Endpoint{Conditions: []Condition{"[STATUS] == 200", "[DOMAIN_EXPIRATION] < 720h"}}).needsToRetrieveDomainExpiration() { + t.Error("expected true, got false") + } +} + +func TestEndpoint_needsToRetrieveIP(t *testing.T) { + if (&Endpoint{Conditions: []Condition{"[STATUS] == 200"}}).needsToRetrieveIP() { + t.Error("expected false, got true") + } + if !(&Endpoint{Conditions: []Condition{"[STATUS] == 200", "[IP] == 127.0.0.1"}}).needsToRetrieveIP() { + t.Error("expected true, got false") + } +} diff --git a/config/endpoint/event.go b/config/endpoint/event.go new file mode 100644 index 00000000..da8d2275 --- /dev/null +++ b/config/endpoint/event.go @@ -0,0 +1,39 @@ +package endpoint + +import ( + "time" +) + +// Event is something that happens at a specific time +type Event struct { + // Type is the kind of event + Type EventType `json:"type"` + + // Timestamp is the moment at which the event happened + Timestamp time.Time `json:"timestamp"` +} + +// EventType is, uh, the types of events? +type EventType string + +var ( + // EventStart is a type of event that represents when an endpoint starts being monitored + EventStart EventType = "START" + + // EventHealthy is a type of event that represents an endpoint passing all of its conditions + EventHealthy EventType = "HEALTHY" + + // EventUnhealthy is a type of event that represents an endpoint failing one or more of its conditions + EventUnhealthy EventType = "UNHEALTHY" +) + +// NewEventFromResult creates an Event from a Result +func NewEventFromResult(result *Result) *Event { + event := &Event{Timestamp: result.Timestamp} + if result.Success { + event.Type = EventHealthy + } else { + event.Type = EventUnhealthy + } + return event +} diff --git a/config/endpoint/event_test.go b/config/endpoint/event_test.go new file mode 100644 index 00000000..bdfe07a2 --- /dev/null +++ b/config/endpoint/event_test.go @@ -0,0 +1,14 @@ +package endpoint + +import ( + "testing" +) + +func TestNewEventFromResult(t *testing.T) { + if event := NewEventFromResult(&Result{Success: true}); event.Type != EventHealthy { + t.Error("expected event.Type to be EventHealthy") + } + if event := NewEventFromResult(&Result{Success: false}); event.Type != EventUnhealthy { + t.Error("expected event.Type to be EventUnhealthy") + } +} diff --git a/config/endpoint/external_endpoint.go b/config/endpoint/external_endpoint.go new file mode 100644 index 00000000..58f37fed --- /dev/null +++ b/config/endpoint/external_endpoint.go @@ -0,0 +1,83 @@ +package endpoint + +import ( + "errors" + + "github.com/TwiN/gatus/v5/alerting/alert" +) + +var ( + // ErrExternalEndpointWithNoToken is the error with which Gatus will panic if an external endpoint is configured without a token. + ErrExternalEndpointWithNoToken = errors.New("you must specify a token for each external endpoint") +) + +// ExternalEndpoint is an endpoint whose result is pushed from outside Gatus, which means that +// said endpoints are not monitored by Gatus itself; Gatus only displays their results and takes +// care of alerting +type ExternalEndpoint struct { + // Enabled defines whether to enable the monitoring of the endpoint + Enabled *bool `yaml:"enabled,omitempty"` + + // Name of the endpoint. Can be anything. + Name string `yaml:"name"` + + // Group the endpoint is a part of. Used for grouping multiple endpoints together on the front end. + Group string `yaml:"group,omitempty"` + + // Token is the bearer token that must be provided through the Authorization header to push results to the endpoint + Token string `yaml:"token,omitempty"` + + // Alerts is the alerting configuration for the endpoint in case of failure + Alerts []*alert.Alert `yaml:"alerts,omitempty"` + + // NumberOfFailuresInARow is the number of unsuccessful evaluations in a row + NumberOfFailuresInARow int `yaml:"-"` + + // NumberOfSuccessesInARow is the number of successful evaluations in a row + NumberOfSuccessesInARow int `yaml:"-"` +} + +// ValidateAndSetDefaults validates the ExternalEndpoint and sets the default values +func (externalEndpoint *ExternalEndpoint) ValidateAndSetDefaults() error { + if err := validateEndpointNameGroupAndAlerts(externalEndpoint.Name, externalEndpoint.Group, externalEndpoint.Alerts); err != nil { + return err + } + if len(externalEndpoint.Token) == 0 { + return ErrExternalEndpointWithNoToken + } + return nil +} + +// IsEnabled returns whether the endpoint is enabled or not +func (externalEndpoint *ExternalEndpoint) IsEnabled() bool { + if externalEndpoint.Enabled == nil { + return true + } + return *externalEndpoint.Enabled +} + +// DisplayName returns an identifier made up of the Name and, if not empty, the Group. +func (externalEndpoint *ExternalEndpoint) DisplayName() string { + if len(externalEndpoint.Group) > 0 { + return externalEndpoint.Group + "/" + externalEndpoint.Name + } + return externalEndpoint.Name +} + +// Key returns the unique key for the Endpoint +func (externalEndpoint *ExternalEndpoint) Key() string { + return ConvertGroupAndEndpointNameToKey(externalEndpoint.Group, externalEndpoint.Name) +} + +// ToEndpoint converts the ExternalEndpoint to an Endpoint +func (externalEndpoint *ExternalEndpoint) ToEndpoint() *Endpoint { + endpoint := &Endpoint{ + Enabled: externalEndpoint.Enabled, + Name: externalEndpoint.Name, + Group: externalEndpoint.Group, + Alerts: externalEndpoint.Alerts, + NumberOfFailuresInARow: externalEndpoint.NumberOfFailuresInARow, + NumberOfSuccessesInARow: externalEndpoint.NumberOfSuccessesInARow, + } + return endpoint +} diff --git a/config/endpoint/external_endpoint_test.go b/config/endpoint/external_endpoint_test.go new file mode 100644 index 00000000..4ead308b --- /dev/null +++ b/config/endpoint/external_endpoint_test.go @@ -0,0 +1,25 @@ +package endpoint + +import ( + "testing" +) + +func TestExternalEndpoint_ToEndpoint(t *testing.T) { + externalEndpoint := &ExternalEndpoint{ + Name: "name", + Group: "group", + } + convertedEndpoint := externalEndpoint.ToEndpoint() + if externalEndpoint.Name != convertedEndpoint.Name { + t.Errorf("expected %s, got %s", externalEndpoint.Name, convertedEndpoint.Name) + } + if externalEndpoint.Group != convertedEndpoint.Group { + t.Errorf("expected %s, got %s", externalEndpoint.Group, convertedEndpoint.Group) + } + if externalEndpoint.Key() != convertedEndpoint.Key() { + t.Errorf("expected %s, got %s", externalEndpoint.Key(), convertedEndpoint.Key()) + } + if externalEndpoint.DisplayName() != convertedEndpoint.DisplayName() { + t.Errorf("expected %s, got %s", externalEndpoint.DisplayName(), convertedEndpoint.DisplayName()) + } +} diff --git a/config/endpoint/key.go b/config/endpoint/key.go new file mode 100644 index 00000000..964f558f --- /dev/null +++ b/config/endpoint/key.go @@ -0,0 +1,19 @@ +package endpoint + +import "strings" + +// ConvertGroupAndEndpointNameToKey converts a group and an endpoint to a key +func ConvertGroupAndEndpointNameToKey(groupName, endpointName string) string { + return sanitize(groupName) + "_" + sanitize(endpointName) +} + +func sanitize(s string) string { + s = strings.TrimSpace(strings.ToLower(s)) + s = strings.ReplaceAll(s, "/", "-") + s = strings.ReplaceAll(s, "_", "-") + s = strings.ReplaceAll(s, ".", "-") + s = strings.ReplaceAll(s, ",", "-") + s = strings.ReplaceAll(s, " ", "-") + s = strings.ReplaceAll(s, "#", "-") + return s +} diff --git a/config/endpoint/key_bench_test.go b/config/endpoint/key_bench_test.go new file mode 100644 index 00000000..cfec8c8b --- /dev/null +++ b/config/endpoint/key_bench_test.go @@ -0,0 +1,11 @@ +package endpoint + +import ( + "testing" +) + +func BenchmarkConvertGroupAndEndpointNameToKey(b *testing.B) { + for n := 0; n < b.N; n++ { + ConvertGroupAndEndpointNameToKey("group", "name") + } +} diff --git a/config/endpoint/key_test.go b/config/endpoint/key_test.go new file mode 100644 index 00000000..c693ba5d --- /dev/null +++ b/config/endpoint/key_test.go @@ -0,0 +1,36 @@ +package endpoint + +import "testing" + +func TestConvertGroupAndEndpointNameToKey(t *testing.T) { + type Scenario struct { + GroupName string + EndpointName string + ExpectedOutput string + } + scenarios := []Scenario{ + { + GroupName: "Core", + EndpointName: "Front End", + ExpectedOutput: "core_front-end", + }, + { + GroupName: "Load balancers", + EndpointName: "us-west-2", + ExpectedOutput: "load-balancers_us-west-2", + }, + { + GroupName: "a/b test", + EndpointName: "a", + ExpectedOutput: "a-b-test_a", + }, + } + for _, scenario := range scenarios { + t.Run(scenario.ExpectedOutput, func(t *testing.T) { + output := ConvertGroupAndEndpointNameToKey(scenario.GroupName, scenario.EndpointName) + if output != scenario.ExpectedOutput { + t.Errorf("expected '%s', got '%s'", scenario.ExpectedOutput, output) + } + }) + } +} diff --git a/config/endpoint/result.go b/config/endpoint/result.go new file mode 100644 index 00000000..2a7aba94 --- /dev/null +++ b/config/endpoint/result.go @@ -0,0 +1,69 @@ +package endpoint + +import ( + "time" +) + +// Result of the evaluation of a Endpoint +type Result struct { + // HTTPStatus is the HTTP response status code + HTTPStatus int `json:"status,omitempty"` + + // DNSRCode is the response code of a DNS query in a human-readable format + // + // Possible values: NOERROR, FORMERR, SERVFAIL, NXDOMAIN, NOTIMP, REFUSED + DNSRCode string `json:"-"` + + // Hostname extracted from Endpoint.URL + Hostname string `json:"hostname,omitempty"` + + // IP resolved from the Endpoint URL + IP string `json:"-"` + + // Connected whether a connection to the host was established successfully + Connected bool `json:"-"` + + // Duration time that the request took + Duration time.Duration `json:"duration"` + + // Errors encountered during the evaluation of the Endpoint's health + Errors []string `json:"errors,omitempty"` + + // ConditionResults are the results of each of the Endpoint's Condition + ConditionResults []*ConditionResult `json:"conditionResults,omitempty"` + + // Success whether the result signifies a success or not + Success bool `json:"success"` + + // Timestamp when the request was sent + Timestamp time.Time `json:"timestamp"` + + // CertificateExpiration is the duration before the certificate expires + CertificateExpiration time.Duration `json:"-"` + + // DomainExpiration is the duration before the domain expires + DomainExpiration time.Duration `json:"-"` + + // Body is the response body + // + // Note that this field is not persisted in the storage. + // It is used for health evaluation as well as debugging purposes. + Body []byte `json:"-"` + + /////////////////////////////////////////////////////////////////////// + // Below is used only for the UI and is not persisted in the storage // + /////////////////////////////////////////////////////////////////////// + port string `yaml:"-"` // used for endpoints[].ui.hide-port +} + +// AddError adds an error to the result's list of errors. +// It also ensures that there are no duplicates. +func (r *Result) AddError(error string) { + for _, resultError := range r.Errors { + if resultError == error { + // If the error already exists, don't add it + return + } + } + r.Errors = append(r.Errors, error) +} diff --git a/config/endpoint/result_test.go b/config/endpoint/result_test.go new file mode 100644 index 00000000..1e521c8f --- /dev/null +++ b/config/endpoint/result_test.go @@ -0,0 +1,21 @@ +package endpoint + +import ( + "testing" +) + +func TestResult_AddError(t *testing.T) { + result := &Result{} + result.AddError("potato") + if len(result.Errors) != 1 { + t.Error("should've had 1 error") + } + result.AddError("potato") + if len(result.Errors) != 1 { + t.Error("should've still had 1 error, because a duplicate error was added") + } + result.AddError("tomato") + if len(result.Errors) != 2 { + t.Error("should've had 2 error") + } +} diff --git a/config/endpoint/ssh/ssh.go b/config/endpoint/ssh/ssh.go new file mode 100644 index 00000000..4759e1d3 --- /dev/null +++ b/config/endpoint/ssh/ssh.go @@ -0,0 +1,33 @@ +package ssh + +import ( + "errors" +) + +var ( + // ErrEndpointWithoutSSHUsername is the error with which Gatus will panic if an endpoint with SSH monitoring is configured without a user. + ErrEndpointWithoutSSHUsername = errors.New("you must specify a username for each SSH endpoint") + + // ErrEndpointWithoutSSHPassword is the error with which Gatus will panic if an endpoint with SSH monitoring is configured without a password. + ErrEndpointWithoutSSHPassword = errors.New("you must specify a password for each SSH endpoint") +) + +type Config struct { + Username string `yaml:"username,omitempty"` + Password string `yaml:"password,omitempty"` +} + +// Validate the SSH configuration +func (cfg *Config) Validate() error { + // If there's no username and password, this endpoint can still check the SSH banner, so the endpoint is still valid + if len(cfg.Username) == 0 && len(cfg.Password) == 0 { + return nil + } + if len(cfg.Username) == 0 { + return ErrEndpointWithoutSSHUsername + } + if len(cfg.Password) == 0 { + return ErrEndpointWithoutSSHPassword + } + return nil +} diff --git a/config/endpoint/ssh/ssh_test.go b/config/endpoint/ssh/ssh_test.go new file mode 100644 index 00000000..d26fca90 --- /dev/null +++ b/config/endpoint/ssh/ssh_test.go @@ -0,0 +1,23 @@ +package ssh + +import ( + "errors" + "testing" +) + +func TestSSH_validate(t *testing.T) { + cfg := &Config{} + if err := cfg.Validate(); err != nil { + t.Error("didn't expect an error") + } + cfg.Username = "username" + if err := cfg.Validate(); err == nil { + t.Error("expected an error") + } else if !errors.Is(err, ErrEndpointWithoutSSHPassword) { + t.Errorf("expected error to be '%v', got '%v'", ErrEndpointWithoutSSHPassword, err) + } + cfg.Password = "password" + if err := cfg.Validate(); err != nil { + t.Errorf("expected no error, got '%v'", err) + } +} diff --git a/config/endpoint/status.go b/config/endpoint/status.go new file mode 100644 index 00000000..f1a725ba --- /dev/null +++ b/config/endpoint/status.go @@ -0,0 +1,38 @@ +package endpoint + +// Status contains the evaluation Results of an Endpoint +type Status struct { + // Name of the endpoint + Name string `json:"name,omitempty"` + + // Group the endpoint is a part of. Used for grouping multiple endpoints together on the front end. + Group string `json:"group,omitempty"` + + // Key of the Endpoint + Key string `json:"key"` + + // Results is the list of endpoint evaluation results + Results []*Result `json:"results"` + + // Events is a list of events + Events []*Event `json:"events,omitempty"` + + // Uptime information on the endpoint's uptime + // + // Used by the memory store. + // + // To retrieve the uptime between two time, use store.GetUptimeByKey. + Uptime *Uptime `json:"-"` +} + +// NewStatus creates a new Status +func NewStatus(group, name string) *Status { + return &Status{ + Name: name, + Group: group, + Key: ConvertGroupAndEndpointNameToKey(group, name), + Results: make([]*Result, 0), + Events: make([]*Event, 0), + Uptime: NewUptime(), + } +} diff --git a/config/endpoint/status_test.go b/config/endpoint/status_test.go new file mode 100644 index 00000000..510f9b51 --- /dev/null +++ b/config/endpoint/status_test.go @@ -0,0 +1,19 @@ +package endpoint + +import ( + "testing" +) + +func TestNewEndpointStatus(t *testing.T) { + ep := &Endpoint{Name: "name", Group: "group"} + status := NewStatus(ep.Group, ep.Name) + if status.Name != ep.Name { + t.Errorf("expected %s, got %s", ep.Name, status.Name) + } + if status.Group != ep.Group { + t.Errorf("expected %s, got %s", ep.Group, status.Group) + } + if status.Key != "group_name" { + t.Errorf("expected %s, got %s", "group_name", status.Key) + } +} diff --git a/config/endpoint/ui/ui.go b/config/endpoint/ui/ui.go new file mode 100644 index 00000000..0f784cb5 --- /dev/null +++ b/config/endpoint/ui/ui.go @@ -0,0 +1,69 @@ +package ui + +import "errors" + +// Config is the UI configuration for endpoint.Endpoint +type Config struct { + // HideConditions whether to hide the condition results on the UI + HideConditions bool `yaml:"hide-conditions"` + + // HideHostname whether to hide the hostname in the Result + HideHostname bool `yaml:"hide-hostname"` + + // HideURL whether to ensure the URL is not displayed in the results. Useful if the URL contains a token. + HideURL bool `yaml:"hide-url"` + + // HidePort whether to hide the port in the Result + HidePort bool `yaml:"hide-port"` + + // DontResolveFailedConditions whether to resolve failed conditions in the Result for display in the UI + DontResolveFailedConditions bool `yaml:"dont-resolve-failed-conditions"` + + // Badge is the configuration for the badges generated + Badge *Badge `yaml:"badge"` +} + +type Badge struct { + ResponseTime *ResponseTime `yaml:"response-time"` +} + +type ResponseTime struct { + Thresholds []int `yaml:"thresholds"` +} + +var ( + ErrInvalidBadgeResponseTimeConfig = errors.New("invalid response time badge configuration: expected parameter 'response-time' to have 5 ascending numerical values") +) + +// ValidateAndSetDefaults validates the UI configuration and sets the default values +func (config *Config) ValidateAndSetDefaults() error { + if config.Badge != nil { + if len(config.Badge.ResponseTime.Thresholds) != 5 { + return ErrInvalidBadgeResponseTimeConfig + } + for i := 4; i > 0; i-- { + if config.Badge.ResponseTime.Thresholds[i] < config.Badge.ResponseTime.Thresholds[i-1] { + return ErrInvalidBadgeResponseTimeConfig + } + } + } else { + config.Badge = GetDefaultConfig().Badge + } + return nil +} + +// GetDefaultConfig retrieves the default UI configuration +func GetDefaultConfig() *Config { + return &Config{ + HideHostname: false, + HideURL: false, + HidePort: false, + DontResolveFailedConditions: false, + HideConditions: false, + Badge: &Badge{ + ResponseTime: &ResponseTime{ + Thresholds: []int{50, 200, 300, 500, 750}, + }, + }, + } +} diff --git a/config/endpoint/ui/ui_test.go b/config/endpoint/ui/ui_test.go new file mode 100644 index 00000000..2bd0e770 --- /dev/null +++ b/config/endpoint/ui/ui_test.go @@ -0,0 +1,53 @@ +package ui + +import ( + "errors" + "testing" +) + +func TestValidateAndSetDefaults(t *testing.T) { + tests := []struct { + name string + config *Config + wantErr error + }{ + { + name: "with-valid-config", + config: &Config{ + Badge: &Badge{ + ResponseTime: &ResponseTime{Thresholds: []int{50, 200, 300, 500, 750}}, + }, + }, + wantErr: nil, + }, + { + name: "with-invalid-threshold-length", + config: &Config{ + Badge: &Badge{ + ResponseTime: &ResponseTime{Thresholds: []int{50, 200, 300, 500}}, + }, + }, + wantErr: ErrInvalidBadgeResponseTimeConfig, + }, + { + name: "with-invalid-thresholds-order", + config: &Config{ + Badge: &Badge{ResponseTime: &ResponseTime{Thresholds: []int{50, 200, 500, 300, 750}}}, + }, + wantErr: ErrInvalidBadgeResponseTimeConfig, + }, + { + name: "with-no-badge-configured", // should give default badge cfg + config: &Config{}, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.config.ValidateAndSetDefaults(); !errors.Is(err, tt.wantErr) { + t.Errorf("Expected error %v, got %v", tt.wantErr, err) + } + }) + } +} diff --git a/config/endpoint/uptime.go b/config/endpoint/uptime.go new file mode 100644 index 00000000..da4d2139 --- /dev/null +++ b/config/endpoint/uptime.go @@ -0,0 +1,24 @@ +package endpoint + +// Uptime is the struct that contains the relevant data for calculating the uptime as well as the uptime itself +// and some other statistics +type Uptime struct { + // HourlyStatistics is a map containing metrics collected (value) for every hourly unix timestamps (key) + // + // Used only if the storage type is memory + HourlyStatistics map[int64]*HourlyUptimeStatistics `json:"-"` +} + +// HourlyUptimeStatistics is a struct containing all metrics collected over the course of an hour +type HourlyUptimeStatistics struct { + TotalExecutions uint64 // Total number of checks + SuccessfulExecutions uint64 // Number of successful executions + TotalExecutionsResponseTime uint64 // Total response time for all executions in milliseconds +} + +// NewUptime creates a new Uptime +func NewUptime() *Uptime { + return &Uptime{ + HourlyStatistics: make(map[int64]*HourlyUptimeStatistics), + } +} diff --git a/config/maintenance/maintenance.go b/config/maintenance/maintenance.go new file mode 100644 index 00000000..72ab5f56 --- /dev/null +++ b/config/maintenance/maintenance.go @@ -0,0 +1,162 @@ +package maintenance + +import ( + "errors" + "fmt" + "strconv" + "strings" + "time" + _ "time/tzdata" // Required for IANA timezone support +) + +var ( + errInvalidMaintenanceStartFormat = errors.New("invalid maintenance start format: must be hh:mm, between 00:00 and 23:59 inclusively (e.g. 23:00)") + errInvalidMaintenanceDuration = errors.New("invalid maintenance duration: must be bigger than 0 (e.g. 30m)") + errInvalidDayName = fmt.Errorf("invalid value specified for 'on'. supported values are %s", longDayNames) + errInvalidTimezone = errors.New("invalid timezone specified or format not supported. Use IANA timezone format (e.g. America/Sao_Paulo)") + + longDayNames = []string{ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + } +) + +// Config allows for the configuration of a maintenance period. +// During this maintenance period, no alerts will be sent. +// +// Uses UTC by default. +type Config struct { + Enabled *bool `yaml:"enabled"` // Whether the maintenance period is enabled. Enabled by default if nil. + Start string `yaml:"start"` // Time at which the maintenance period starts (e.g. 23:00) + Duration time.Duration `yaml:"duration"` // Duration of the maintenance period (e.g. 4h) + Timezone string `yaml:"timezone"` // Timezone in string format which the maintenance period is configured (e.g. America/Sao_Paulo) + + // Every is a list of days of the week during which maintenance period applies. + // See longDayNames for list of valid values. + // Every day if empty. + Every []string `yaml:"every"` + + TimezoneLocation *time.Location // Timezone in location format which the maintenance period is configured + durationToStartFromMidnight time.Duration +} + +func GetDefaultConfig() *Config { + defaultValue := false + return &Config{ + Enabled: &defaultValue, + } +} + +// IsEnabled returns whether maintenance is enabled or not +func (c *Config) IsEnabled() bool { + if c.Enabled == nil { + return true + } + return *c.Enabled +} + +// ValidateAndSetDefaults validates the maintenance configuration and sets the default values if necessary. +// +// Must be called once in the application's lifecycle before IsUnderMaintenance is called, since it +// also sets durationToStartFromMidnight. +func (c *Config) ValidateAndSetDefaults() error { + if c == nil || !c.IsEnabled() { + // Don't waste time validating if maintenance is not enabled. + return nil + } + for _, day := range c.Every { + isDayValid := false + for _, longDayName := range longDayNames { + if day == longDayName { + isDayValid = true + break + } + } + if !isDayValid { + return errInvalidDayName + } + } + var err error + c.durationToStartFromMidnight, err = hhmmToDuration(c.Start) + if err != nil { + return err + } + if c.Duration <= 0 || c.Duration > 24*time.Hour { + return errInvalidMaintenanceDuration + } + if c.Timezone != "" { + c.TimezoneLocation, err = time.LoadLocation(c.Timezone) + if err != nil { + return fmt.Errorf("%w: %w", errInvalidTimezone, err) + } + } else { + c.Timezone = "UTC" + c.TimezoneLocation = time.UTC + } + return nil +} + +// IsUnderMaintenance checks whether the endpoints that Gatus monitors are within the configured maintenance window +func (c *Config) IsUnderMaintenance() bool { + if !c.IsEnabled() { + return false + } + now := time.Now() + if c.TimezoneLocation != nil { + now = now.In(c.TimezoneLocation) + } + adjustedDate := now.Day() + if now.Hour() < int(c.durationToStartFromMidnight.Hours()) { + // if time in maintenance window is later than now, treat it as yesterday + adjustedDate-- + } + // Set to midnight prior to adding duration + dayWhereMaintenancePeriodWouldStart := time.Date(now.Year(), now.Month(), adjustedDate, 0, 0, 0, 0, now.Location()) + hasMaintenanceEveryDay := len(c.Every) == 0 + hasMaintenancePeriodScheduledToStartOnThatWeekday := c.hasDay(dayWhereMaintenancePeriodWouldStart.Weekday().String()) + if !hasMaintenanceEveryDay && !hasMaintenancePeriodScheduledToStartOnThatWeekday { + // The day when the maintenance period would start is not scheduled + // to have any maintenance, so we can just return false. + return false + } + startOfMaintenancePeriod := dayWhereMaintenancePeriodWouldStart.Add(c.durationToStartFromMidnight) + endOfMaintenancePeriod := startOfMaintenancePeriod.Add(c.Duration) + return now.After(startOfMaintenancePeriod) && now.Before(endOfMaintenancePeriod) +} + +func (c *Config) hasDay(day string) bool { + for _, d := range c.Every { + if d == day { + return true + } + } + return false +} + +func hhmmToDuration(s string) (time.Duration, error) { + if len(s) != 5 { + return 0, errInvalidMaintenanceStartFormat + } + var hours, minutes int + var err error + if hours, err = extractNumericalValueFromPotentiallyZeroPaddedString(s[:2]); err != nil { + return 0, err + } + if minutes, err = extractNumericalValueFromPotentiallyZeroPaddedString(s[3:5]); err != nil { + return 0, err + } + duration := (time.Duration(hours) * time.Hour) + (time.Duration(minutes) * time.Minute) + if hours < 0 || hours > 23 || minutes < 0 || minutes > 59 || duration < 0 || duration >= 24*time.Hour { + return 0, errInvalidMaintenanceStartFormat + } + return duration, nil +} + +func extractNumericalValueFromPotentiallyZeroPaddedString(s string) (int, error) { + return strconv.Atoi(strings.TrimPrefix(s, "0")) +} diff --git a/config/maintenance/maintenance_test.go b/config/maintenance/maintenance_test.go new file mode 100644 index 00000000..f8ba5cab --- /dev/null +++ b/config/maintenance/maintenance_test.go @@ -0,0 +1,360 @@ +package maintenance + +import ( + "errors" + "fmt" + "strconv" + "testing" + "time" +) + +func TestGetDefaultConfig(t *testing.T) { + if *GetDefaultConfig().Enabled { + t.Fatal("expected default config to be disabled by default") + } +} + +func TestConfig_ValidateAndSetDefaults(t *testing.T) { + yes, no := true, false + scenarios := []struct { + name string + cfg *Config + expectedError error + }{ + { + name: "nil", + cfg: nil, + expectedError: nil, + }, + { + name: "disabled", + cfg: &Config{ + Enabled: &no, + }, + expectedError: nil, + }, + { + name: "invalid-day", + cfg: &Config{ + Every: []string{"invalid-day"}, + }, + expectedError: errInvalidDayName, + }, + { + name: "invalid-day", + cfg: &Config{ + Every: []string{"invalid-day"}, + }, + expectedError: errInvalidDayName, + }, + { + name: "invalid-start-format", + cfg: &Config{ + Start: "0000", + }, + expectedError: errInvalidMaintenanceStartFormat, + }, + { + name: "invalid-start-hours", + cfg: &Config{ + Start: "25:00", + }, + expectedError: errInvalidMaintenanceStartFormat, + }, + { + name: "invalid-start-minutes", + cfg: &Config{ + Start: "0:61", + }, + expectedError: errInvalidMaintenanceStartFormat, + }, + { + name: "invalid-start-minutes-non-numerical", + cfg: &Config{ + Start: "00:zz", + }, + expectedError: strconv.ErrSyntax, + }, + { + name: "invalid-start-hours-non-numerical", + cfg: &Config{ + Start: "zz:00", + }, + expectedError: strconv.ErrSyntax, + }, + { + name: "invalid-duration", + cfg: &Config{ + Start: "23:00", + Duration: 0, + }, + expectedError: errInvalidMaintenanceDuration, + }, + { + name: "invalid-timezone", + cfg: &Config{ + Start: "23:00", + Duration: time.Hour, + Timezone: "invalid-timezone", + }, + expectedError: errInvalidTimezone, + }, + { + name: "every-day-at-2300", + cfg: &Config{ + Start: "23:00", + Duration: time.Hour, + }, + expectedError: nil, + }, + { + name: "every-day-explicitly-at-2300", + cfg: &Config{ + Start: "23:00", + Duration: time.Hour, + Every: []string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}, + }, + expectedError: nil, + }, + { + name: "every-monday-at-0000", + cfg: &Config{ + Start: "00:00", + Duration: 30 * time.Minute, + Every: []string{"Monday"}, + }, + expectedError: nil, + }, + { + name: "every-friday-and-sunday-at-0000-explicitly-enabled", + cfg: &Config{ + Enabled: &yes, + Start: "08:00", + Duration: 8 * time.Hour, + Every: []string{"Friday", "Sunday"}, + }, + expectedError: nil, + }, + { + name: "timezone-amsterdam", + cfg: &Config{ + Start: "23:00", + Duration: time.Hour, + Timezone: "Europe/Amsterdam", + }, + expectedError: nil, + }, + { + name: "timezone-cet", + cfg: &Config{ + Start: "23:00", + Duration: time.Hour, + Timezone: "CET", + }, + expectedError: nil, + }, + { + name: "timezone-etc-plus-5", + cfg: &Config{ + Start: "23:00", + Duration: time.Hour, + Timezone: "Etc/GMT+5", + }, + expectedError: nil, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + err := scenario.cfg.ValidateAndSetDefaults() + if !errors.Is(err, scenario.expectedError) { + t.Errorf("expected %v, got %v", scenario.expectedError, err) + } + }) + } +} + +func TestConfig_IsUnderMaintenance(t *testing.T) { + yes, no := true, false + now := time.Now().UTC() + scenarios := []struct { + name string + cfg *Config + expected bool + }{ + { + name: "disabled", + cfg: &Config{ + Enabled: &no, + }, + expected: false, + }, + { + name: "under-maintenance-explicitly-enabled", + cfg: &Config{ + Enabled: &yes, + Start: fmt.Sprintf("%02d:00", now.Hour()), + Duration: 2 * time.Hour, + }, + expected: true, + }, + { + name: "under-maintenance-starting-now-for-2h", + cfg: &Config{ + Start: fmt.Sprintf("%02d:00", now.Hour()), + Duration: 2 * time.Hour, + }, + expected: true, + }, + { + name: "under-maintenance-starting-now-for-8h", + cfg: &Config{ + Start: fmt.Sprintf("%02d:00", now.Hour()), + Duration: 8 * time.Hour, + }, + expected: true, + }, + { + name: "under-maintenance-starting-now-for-8h-explicit-days", + cfg: &Config{ + Start: fmt.Sprintf("%02d:00", now.Hour()), + Duration: 8 * time.Hour, + Every: []string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}, + }, + expected: true, + }, + { + name: "under-maintenance-starting-now-for-23h-explicit-days", + cfg: &Config{ + Start: fmt.Sprintf("%02d:00", now.Hour()), + Duration: 23 * time.Hour, + Every: []string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}, + }, + expected: true, + }, + { + name: "under-maintenance-starting-4h-ago-for-8h", + cfg: &Config{ + Start: fmt.Sprintf("%02d:00", normalizeHour(now.Hour()-4)), + Duration: 8 * time.Hour, + }, + expected: true, + }, + { + name: "under-maintenance-starting-22h-ago-for-23h", + cfg: &Config{ + Start: fmt.Sprintf("%02d:00", normalizeHour(now.Hour()-22)), + Duration: 23 * time.Hour, + }, + expected: true, + }, + { + name: "under-maintenance-starting-22h-ago-for-24h", + cfg: &Config{ + Start: fmt.Sprintf("%02d:00", normalizeHour(now.Hour()-22)), + Duration: 24 * time.Hour, + }, + expected: true, + }, + { + name: "under-maintenance-amsterdam-timezone-starting-now-for-2h", + cfg: &Config{ + Start: fmt.Sprintf("%02d:00", now.Hour()), + Duration: 2 * time.Hour, + Timezone: "Europe/Amsterdam", + }, + expected: true, + }, + { + name: "under-maintenance-perth-timezone-starting-now-for-2h", + cfg: &Config{ + Start: fmt.Sprintf("%02d:00", inTimezone(now, "Australia/Perth", t).Hour()), + Duration: 2 * time.Hour, + Timezone: "Australia/Perth", + }, + expected: true, + }, + { + name: "under-maintenance-utc-timezone-starting-now-for-2h", + cfg: &Config{ + Start: fmt.Sprintf("%02d:00", now.Hour()), + Duration: 2 * time.Hour, + Timezone: "UTC", + }, + expected: true, + }, + { + name: "not-under-maintenance-starting-4h-ago-for-3h", + cfg: &Config{ + Start: fmt.Sprintf("%02d:00", normalizeHour(now.Hour()-4)), + Duration: 3 * time.Hour, + }, + expected: false, + }, + { + name: "not-under-maintenance-starting-5h-ago-for-1h", + cfg: &Config{ + Start: fmt.Sprintf("%02d:00", normalizeHour(now.Hour()-5)), + Duration: time.Hour, + }, + expected: false, + }, + { + name: "not-under-maintenance-today", + cfg: &Config{ + Start: fmt.Sprintf("%02d:00", now.Hour()), + Duration: time.Hour, + Every: []string{now.Add(48 * time.Hour).Weekday().String()}, + }, + expected: false, + }, + { + name: "not-under-maintenance-today-with-24h-duration", + cfg: &Config{ + Start: fmt.Sprintf("%02d:00", now.Hour()), + Duration: 24 * time.Hour, + Every: []string{now.Add(48 * time.Hour).Weekday().String()}, + }, + expected: false, + }, + { + name: "not-under-maintenance-los-angeles-timezone-starting-now-for-2h-today", + cfg: &Config{ + Start: fmt.Sprintf("%02d:00", now.Hour()), + Duration: 2 * time.Hour, + Timezone: "America/Los_Angeles", + Every: []string{now.Weekday().String()}, + }, + expected: false, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + t.Log(scenario.cfg.Start) + t.Log(now) + if err := scenario.cfg.ValidateAndSetDefaults(); err != nil { + t.Fatal("validation shouldn't have returned an error, got", err) + } + isUnderMaintenance := scenario.cfg.IsUnderMaintenance() + if isUnderMaintenance != scenario.expected { + t.Errorf("expected %v, got %v", scenario.expected, isUnderMaintenance) + t.Logf("start=%v; duration=%v; now=%v", scenario.cfg.Start, scenario.cfg.Duration, time.Now().UTC()) + } + }) + } +} + +func normalizeHour(hour int) int { + if hour < 0 { + return hour + 24 + } + return hour +} + +func inTimezone(passedTime time.Time, timezone string, t *testing.T) time.Time { + timezoneLocation, err := time.LoadLocation(timezone) + + if err != nil { + t.Fatalf("timezone %s did not load", timezone) + } + return passedTime.In(timezoneLocation) +} diff --git a/config/remote/remote.go b/config/remote/remote.go new file mode 100644 index 00000000..d5bdbc47 --- /dev/null +++ b/config/remote/remote.go @@ -0,0 +1,37 @@ +package remote + +import ( + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/logr" +) + +// NOTICE: This is an experimental alpha feature and may be updated/removed in future versions. +// For more information, see https://github.com/TwiN/gatus/issues/64 + +type Config struct { + // Instances is a list of remote instances to retrieve endpoint statuses from. + Instances []Instance `yaml:"instances,omitempty"` + + // ClientConfig is the configuration of the client used to communicate with the provider's target + ClientConfig *client.Config `yaml:"client,omitempty"` +} + +type Instance struct { + EndpointPrefix string `yaml:"endpoint-prefix"` + URL string `yaml:"url"` +} + +func (c *Config) ValidateAndSetDefaults() error { + if c.ClientConfig == nil { + c.ClientConfig = client.GetDefaultConfig() + } else { + if err := c.ClientConfig.ValidateAndSetDefaults(); err != nil { + return err + } + } + if len(c.Instances) > 0 { + logr.Warn("WARNING: Your configuration is using 'remote', which is in alpha and may be updated/removed in future versions.") + logr.Warn("WARNING: See https://github.com/TwiN/gatus/issues/64 for more information") + } + return nil +} diff --git a/config/ui/ui.go b/config/ui/ui.go new file mode 100644 index 00000000..33acf248 --- /dev/null +++ b/config/ui/ui.go @@ -0,0 +1,112 @@ +package ui + +import ( + "bytes" + "errors" + "html/template" + + static "github.com/TwiN/gatus/v5/web" +) + +const ( + defaultTitle = "Health Dashboard | Gatus" + defaultDescription = "Gatus is an advanced automated status page that lets you monitor your applications and configure alerts to notify you if there's an issue" + defaultHeader = "Health Status" + defaultLogo = "" + defaultLink = "" + defaultCustomCSS = "" +) + +var ( + defaultDarkMode = true + + ErrButtonValidationFailed = errors.New("invalid button configuration: missing required name or link") +) + +// Config is the configuration for the UI of Gatus +type Config struct { + Title string `yaml:"title,omitempty"` // Title of the page + Description string `yaml:"description,omitempty"` // Meta description of the page + Header string `yaml:"header,omitempty"` // Header is the text at the top of the page + Logo string `yaml:"logo,omitempty"` // Logo to display on the page + Link string `yaml:"link,omitempty"` // Link to open when clicking on the logo + Buttons []Button `yaml:"buttons,omitempty"` // Buttons to display below the header + CustomCSS string `yaml:"custom-css,omitempty"` // Custom CSS to include in the page + DarkMode *bool `yaml:"dark-mode,omitempty"` // DarkMode is a flag to enable dark mode by default +} + +func (cfg *Config) IsDarkMode() bool { + if cfg.DarkMode != nil { + return *cfg.DarkMode + } + return defaultDarkMode +} + +// Button is the configuration for a button on the UI +type Button struct { + Name string `yaml:"name,omitempty"` // Name is the text to display on the button + Link string `yaml:"link,omitempty"` // Link to open when the button is clicked. +} + +// Validate validates the button configuration +func (btn *Button) Validate() error { + if len(btn.Name) == 0 || len(btn.Link) == 0 { + return ErrButtonValidationFailed + } + return nil +} + +// GetDefaultConfig returns a Config struct with the default values +func GetDefaultConfig() *Config { + return &Config{ + Title: defaultTitle, + Description: defaultDescription, + Header: defaultHeader, + Logo: defaultLogo, + Link: defaultLink, + CustomCSS: defaultCustomCSS, + DarkMode: &defaultDarkMode, + } +} + +// ValidateAndSetDefaults validates the UI configuration and sets the default values if necessary. +func (cfg *Config) ValidateAndSetDefaults() error { + if len(cfg.Title) == 0 { + cfg.Title = defaultTitle + } + if len(cfg.Description) == 0 { + cfg.Description = defaultDescription + } + if len(cfg.Header) == 0 { + cfg.Header = defaultHeader + } + if len(cfg.Logo) == 0 { + cfg.Logo = defaultLogo + } + if len(cfg.Link) == 0 { + cfg.Link = defaultLink + } + if len(cfg.CustomCSS) == 0 { + cfg.CustomCSS = defaultCustomCSS + } + if cfg.DarkMode == nil { + cfg.DarkMode = &defaultDarkMode + } + for _, btn := range cfg.Buttons { + if err := btn.Validate(); err != nil { + return err + } + } + // Validate that the template works + t, err := template.ParseFS(static.FileSystem, static.IndexPath) + if err != nil { + return err + } + var buffer bytes.Buffer + return t.Execute(&buffer, ViewData{UI: cfg, Theme: "dark"}) +} + +type ViewData struct { + UI *Config + Theme string +} diff --git a/config/ui/ui_test.go b/config/ui/ui_test.go new file mode 100644 index 00000000..2811157c --- /dev/null +++ b/config/ui/ui_test.go @@ -0,0 +1,77 @@ +package ui + +import ( + "strconv" + "testing" +) + +func TestConfig_ValidateAndSetDefaults(t *testing.T) { + cfg := &Config{ + Title: "", + Description: "", + Header: "", + Logo: "", + Link: "", + } + if err := cfg.ValidateAndSetDefaults(); err != nil { + t.Error("expected no error, got", err.Error()) + } + if cfg.Title != defaultTitle { + t.Errorf("expected title to be %s, got %s", defaultTitle, cfg.Title) + } + if cfg.Description != defaultDescription { + t.Errorf("expected description to be %s, got %s", defaultDescription, cfg.Description) + } + if cfg.Header != defaultHeader { + t.Errorf("expected header to be %s, got %s", defaultHeader, cfg.Header) + } +} + +func TestButton_Validate(t *testing.T) { + scenarios := []struct { + Name, Link string + ExpectedError error + }{ + { + Name: "", + Link: "", + ExpectedError: ErrButtonValidationFailed, + }, + { + Name: "", + Link: "link", + ExpectedError: ErrButtonValidationFailed, + }, + { + Name: "name", + Link: "", + ExpectedError: ErrButtonValidationFailed, + }, + { + Name: "name", + Link: "link", + ExpectedError: nil, + }, + } + for i, scenario := range scenarios { + t.Run(strconv.Itoa(i)+"_"+scenario.Name+"_"+scenario.Link, func(t *testing.T) { + button := &Button{ + Name: scenario.Name, + Link: scenario.Link, + } + if err := button.Validate(); err != scenario.ExpectedError { + t.Errorf("expected error %v, got %v", scenario.ExpectedError, err) + } + }) + } +} + +func TestGetDefaultConfig(t *testing.T) { + defaultConfig := GetDefaultConfig() + if defaultConfig.Title != defaultTitle { + t.Error("expected GetDefaultConfig() to return defaultTitle, got", defaultConfig.Title) + } + if defaultConfig.Logo != defaultLogo { + t.Error("expected GetDefaultConfig() to return defaultLogo, got", defaultConfig.Logo) + } +} diff --git a/config/web/web.go b/config/web/web.go new file mode 100644 index 00000000..5110df02 --- /dev/null +++ b/config/web/web.go @@ -0,0 +1,107 @@ +package web + +import ( + "crypto/tls" + "errors" + "fmt" + "math" +) + +const ( + // DefaultAddress is the default address the application will bind to + DefaultAddress = "0.0.0.0" + + // DefaultPort is the default port the application will listen on + DefaultPort = 8080 + + // DefaultReadBufferSize is the default value for ReadBufferSize + DefaultReadBufferSize = 8192 + + // MinimumReadBufferSize is the minimum value for ReadBufferSize, and also the default value set + // for fiber.Config.ReadBufferSize + MinimumReadBufferSize = 4096 +) + +// Config is the structure which supports the configuration of the server listening to requests +type Config struct { + // Address to listen on (defaults to 0.0.0.0 specified by DefaultAddress) + Address string `yaml:"address"` + + // Port to listen on (default to 8080 specified by DefaultPort) + Port int `yaml:"port"` + + // ReadBufferSize sets fiber.Config.ReadBufferSize, which is the buffer size for reading requests coming from a + // single connection and also acts as a limit for the maximum header size. + // + // If you're getting occasional "Request Header Fields Too Large", you may want to try increasing this value. + // + // Defaults to DefaultReadBufferSize + ReadBufferSize int `yaml:"read-buffer-size,omitempty"` + + // TLS configuration (optional) + TLS *TLSConfig `yaml:"tls,omitempty"` +} + +type TLSConfig struct { + // CertificateFile is the public certificate for TLS in PEM format. + CertificateFile string `yaml:"certificate-file,omitempty"` + + // PrivateKeyFile is the private key file for TLS in PEM format. + PrivateKeyFile string `yaml:"private-key-file,omitempty"` +} + +// GetDefaultConfig returns a Config struct with the default values +func GetDefaultConfig() *Config { + return &Config{ + Address: DefaultAddress, + Port: DefaultPort, + ReadBufferSize: DefaultReadBufferSize, + } +} + +// ValidateAndSetDefaults validates the web configuration and sets the default values if necessary. +func (web *Config) ValidateAndSetDefaults() error { + // Validate the Address + if len(web.Address) == 0 { + web.Address = DefaultAddress + } + // Validate the Port + if web.Port == 0 { + web.Port = DefaultPort + } else if web.Port < 0 || web.Port > math.MaxUint16 { + return fmt.Errorf("invalid port: value should be between %d and %d", 0, math.MaxUint16) + } + // Validate ReadBufferSize + if web.ReadBufferSize == 0 { + web.ReadBufferSize = DefaultReadBufferSize // Not set? Use the default value. + } else if web.ReadBufferSize < MinimumReadBufferSize { + web.ReadBufferSize = MinimumReadBufferSize // Below the minimum? Use the minimum value. + } + // Try to load the TLS certificates + if web.TLS != nil { + if err := web.TLS.isValid(); err != nil { + return fmt.Errorf("invalid tls config: %w", err) + } + } + return nil +} + +func (web *Config) HasTLS() bool { + return web.TLS != nil && len(web.TLS.CertificateFile) > 0 && len(web.TLS.PrivateKeyFile) > 0 +} + +// SocketAddress returns the combination of the Address and the Port +func (web *Config) SocketAddress() string { + return fmt.Sprintf("%s:%d", web.Address, web.Port) +} + +func (t *TLSConfig) isValid() error { + if len(t.CertificateFile) > 0 && len(t.PrivateKeyFile) > 0 { + _, err := tls.LoadX509KeyPair(t.CertificateFile, t.PrivateKeyFile) + if err != nil { + return err + } + return nil + } + return errors.New("certificate-file and private-key-file must be specified") +} diff --git a/config/web/web_test.go b/config/web/web_test.go new file mode 100644 index 00000000..63a34f96 --- /dev/null +++ b/config/web/web_test.go @@ -0,0 +1,187 @@ +package web + +import ( + "testing" +) + +func TestGetDefaultConfig(t *testing.T) { + defaultConfig := GetDefaultConfig() + if defaultConfig.Port != DefaultPort { + t.Error("expected default config to have the default port") + } + if defaultConfig.Address != DefaultAddress { + t.Error("expected default config to have the default address") + } + if defaultConfig.ReadBufferSize != DefaultReadBufferSize { + t.Error("expected default config to have the default read buffer size") + } + if defaultConfig.TLS != nil { + t.Error("expected default config to have TLS disabled") + } +} + +func TestConfig_ValidateAndSetDefaults(t *testing.T) { + scenarios := []struct { + name string + cfg *Config + expectedAddress string + expectedPort int + expectedReadBufferSize int + expectedErr bool + }{ + { + name: "no-explicit-config", + cfg: &Config{}, + expectedAddress: "0.0.0.0", + expectedPort: 8080, + expectedReadBufferSize: 8192, + expectedErr: false, + }, + { + name: "invalid-port", + cfg: &Config{Port: 100000000}, + expectedErr: true, + }, + { + name: "read-buffer-size-below-minimum", + cfg: &Config{ReadBufferSize: 1024}, + expectedAddress: "0.0.0.0", + expectedPort: 8080, + expectedReadBufferSize: MinimumReadBufferSize, // minimum is 4096, default is 8192. + expectedErr: false, + }, + { + name: "read-buffer-size-at-minimum", + cfg: &Config{ReadBufferSize: MinimumReadBufferSize}, + expectedAddress: "0.0.0.0", + expectedPort: 8080, + expectedReadBufferSize: 4096, + expectedErr: false, + }, + { + name: "custom-read-buffer-size", + cfg: &Config{ReadBufferSize: 65536}, + expectedAddress: "0.0.0.0", + expectedPort: 8080, + expectedReadBufferSize: 65536, + expectedErr: false, + }, + { + name: "with-good-tls-config", + cfg: &Config{Port: 443, TLS: &TLSConfig{CertificateFile: "../../testdata/cert.pem", PrivateKeyFile: "../../testdata/cert.key"}}, + expectedAddress: "0.0.0.0", + expectedPort: 443, + expectedReadBufferSize: 8192, + expectedErr: false, + }, + { + name: "with-bad-tls-config", + cfg: &Config{Port: 443, TLS: &TLSConfig{CertificateFile: "../../testdata/badcert.pem", PrivateKeyFile: "../../testdata/cert.key"}}, + expectedAddress: "0.0.0.0", + expectedPort: 443, + expectedReadBufferSize: 8192, + expectedErr: true, + }, + { + name: "with-partial-tls-config", + cfg: &Config{Port: 443, TLS: &TLSConfig{CertificateFile: "", PrivateKeyFile: "../../testdata/cert.key"}}, + expectedAddress: "0.0.0.0", + expectedPort: 443, + expectedReadBufferSize: 8192, + expectedErr: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + err := scenario.cfg.ValidateAndSetDefaults() + if (err != nil) != scenario.expectedErr { + t.Errorf("expected the existence of an error to be %v, got %v", scenario.expectedErr, err) + return + } + if !scenario.expectedErr { + if scenario.cfg.Port != scenario.expectedPort { + t.Errorf("expected Port to be %d, got %d", scenario.expectedPort, scenario.cfg.Port) + } + if scenario.cfg.ReadBufferSize != scenario.expectedReadBufferSize { + t.Errorf("expected ReadBufferSize to be %d, got %d", scenario.expectedReadBufferSize, scenario.cfg.ReadBufferSize) + } + if scenario.cfg.Address != scenario.expectedAddress { + t.Errorf("expected Address to be %s, got %s", scenario.expectedAddress, scenario.cfg.Address) + } + } + }) + } +} + +func TestConfig_SocketAddress(t *testing.T) { + web := &Config{ + Address: "0.0.0.0", + Port: 8081, + } + if web.SocketAddress() != "0.0.0.0:8081" { + t.Errorf("expected %s, got %s", "0.0.0.0:8081", web.SocketAddress()) + } +} + +func TestConfig_isValid(t *testing.T) { + scenarios := []struct { + name string + cfg *Config + expectedErr bool + }{ + { + name: "good-tls-config", + cfg: &Config{TLS: &TLSConfig{CertificateFile: "../../testdata/cert.pem", PrivateKeyFile: "../../testdata/cert.key"}}, + expectedErr: false, + }, + { + name: "missing-certificate-file", + cfg: &Config{TLS: &TLSConfig{CertificateFile: "doesnotexist", PrivateKeyFile: "../../testdata/cert.key"}}, + expectedErr: true, + }, + { + name: "bad-certificate-file", + cfg: &Config{TLS: &TLSConfig{CertificateFile: "../../testdata/badcert.pem", PrivateKeyFile: "../../testdata/cert.key"}}, + expectedErr: true, + }, + { + name: "no-certificate-file", + cfg: &Config{TLS: &TLSConfig{CertificateFile: "", PrivateKeyFile: "../../testdata/cert.key"}}, + expectedErr: true, + }, + { + name: "missing-private-key-file", + cfg: &Config{TLS: &TLSConfig{CertificateFile: "../../testdata/cert.pem", PrivateKeyFile: "doesnotexist"}}, + expectedErr: true, + }, + { + name: "no-private-key-file", + cfg: &Config{TLS: &TLSConfig{CertificateFile: "../../testdata/cert.pem", PrivateKeyFile: ""}}, + expectedErr: true, + }, + { + name: "bad-private-key-file", + cfg: &Config{TLS: &TLSConfig{CertificateFile: "../../testdata/cert.pem", PrivateKeyFile: "../../testdata/badcert.key"}}, + expectedErr: true, + }, + { + name: "bad-certificate-and-private-key-file", + cfg: &Config{TLS: &TLSConfig{CertificateFile: "../../testdata/badcert.pem", PrivateKeyFile: "../../testdata/badcert.key"}}, + expectedErr: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + err := scenario.cfg.ValidateAndSetDefaults() + if (err != nil) != scenario.expectedErr { + t.Errorf("expected the existence of an error to be %v, got %v", scenario.expectedErr, err) + return + } + if !scenario.expectedErr { + if scenario.cfg.TLS.isValid() != nil { + t.Error("cfg.TLS.isValid() returned an error even though no error was expected") + } + } + }) + } +} diff --git a/controller/controller.go b/controller/controller.go new file mode 100644 index 00000000..47645a4c --- /dev/null +++ b/controller/controller.go @@ -0,0 +1,49 @@ +package controller + +import ( + "os" + "time" + + "github.com/TwiN/gatus/v5/api" + "github.com/TwiN/gatus/v5/config" + "github.com/TwiN/logr" + "github.com/gofiber/fiber/v2" +) + +var ( + app *fiber.App +) + +// Handle creates the router and starts the server +func Handle(cfg *config.Config) { + api := api.New(cfg) + app = api.Router() + server := app.Server() + server.ReadTimeout = 15 * time.Second + server.WriteTimeout = 15 * time.Second + server.IdleTimeout = 15 * time.Second + if os.Getenv("ROUTER_TEST") == "true" { + return + } + logr.Info("[controller.Handle] Listening on " + cfg.Web.SocketAddress()) + if cfg.Web.HasTLS() { + err := app.ListenTLS(cfg.Web.SocketAddress(), cfg.Web.TLS.CertificateFile, cfg.Web.TLS.PrivateKeyFile) + if err != nil { + logr.Fatalf("[controller.Handle] %s", err.Error()) + } + } else { + err := app.Listen(cfg.Web.SocketAddress()) + if err != nil { + logr.Fatalf("[controller.Handle] %s", err.Error()) + } + } + logr.Info("[controller.Handle] Server has shut down successfully") +} + +// Shutdown stops the server +func Shutdown() { + if app != nil { + _ = app.Shutdown() + app = nil + } +} diff --git a/controller/controller_test.go b/controller/controller_test.go new file mode 100644 index 00000000..aaefcbdc --- /dev/null +++ b/controller/controller_test.go @@ -0,0 +1,102 @@ +package controller + +import ( + "math/rand" + "net/http" + "net/http/httptest" + "os" + "testing" + + "github.com/TwiN/gatus/v5/config" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/config/web" + "github.com/gofiber/fiber/v2" +) + +func TestHandle(t *testing.T) { + cfg := &config.Config{ + Web: &web.Config{ + Address: "0.0.0.0", + Port: rand.Intn(65534), + }, + Endpoints: []*endpoint.Endpoint{ + { + Name: "frontend", + Group: "core", + }, + { + Name: "backend", + Group: "core", + }, + }, + } + _ = os.Setenv("ROUTER_TEST", "true") + _ = os.Setenv("ENVIRONMENT", "dev") + defer os.Clearenv() + Handle(cfg) + defer Shutdown() + request := httptest.NewRequest("GET", "/health", http.NoBody) + response, err := app.Test(request) + if err != nil { + t.Fatal(err) + } + if response.StatusCode != 200 { + t.Error("expected GET /health to return status code 200") + } + if app == nil { + t.Fatal("server should've been set (but because we set ROUTER_TEST, it shouldn't have been started)") + } +} + +func TestHandleTLS(t *testing.T) { + scenarios := []struct { + name string + tls *web.TLSConfig + expectedStatusCode int + }{ + { + name: "good-tls-config", + tls: &web.TLSConfig{CertificateFile: "../testdata/cert.pem", PrivateKeyFile: "../testdata/cert.key"}, + expectedStatusCode: 200, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + cfg := &config.Config{ + Web: &web.Config{Address: "0.0.0.0", Port: rand.Intn(65534), TLS: scenario.tls}, + Endpoints: []*endpoint.Endpoint{ + {Name: "frontend", Group: "core"}, + {Name: "backend", Group: "core"}, + }, + } + if err := cfg.Web.ValidateAndSetDefaults(); err != nil { + t.Error("expected no error from web (TLS) validation, got", err) + } + _ = os.Setenv("ROUTER_TEST", "true") + _ = os.Setenv("ENVIRONMENT", "dev") + defer os.Clearenv() + Handle(cfg) + defer Shutdown() + request := httptest.NewRequest("GET", "/health", http.NoBody) + response, err := app.Test(request) + if err != nil { + t.Fatal(err) + } + if response.StatusCode != scenario.expectedStatusCode { + t.Errorf("%s %s should have returned %d, but returned %d instead", request.Method, request.URL, scenario.expectedStatusCode, response.StatusCode) + } + if app == nil { + t.Fatal("server should've been set (but because we set ROUTER_TEST, it shouldn't have been started)") + } + }) + } +} + +func TestShutdown(t *testing.T) { + // Pretend that we called controller.Handle(), which initializes the server variable + app = fiber.New() + Shutdown() + if app != nil { + t.Error("server should've been shut down") + } +} diff --git a/docs/pagerduty-integration-guide.md b/docs/pagerduty-integration-guide.md new file mode 100644 index 00000000..f0fef82e --- /dev/null +++ b/docs/pagerduty-integration-guide.md @@ -0,0 +1,74 @@ +# PagerDuty + Gatus Integration Benefits +- Notify on-call responders based on alerts sent from Gatus. +- Incidents will automatically resolve in PagerDuty when the endpoint that caused the incident in Gatus returns to a healthy state. + + +# How it Works +- Endpoints that do not meet the user-specified conditions and that are configured with alerts of type `pagerduty` will trigger a new incident on the corresponding PagerDuty service when the alert's defined `failure-threshold` has been reached. +- Once the unhealthy endpoints have returned to a healthy state for the number of executions defined in `success-threshold`, the previously triggered incident will be automatically resolved. + + +# Requirements +- PagerDuty integrations require an Admin base role for account authorization. If you do not have this role, please reach out to an Admin or Account Owner within your organization to configure the integration. + + +# Support +If you need help with this integration, please create an issue at https://github.com/TwiN/gatus/issues + + +# Integration Walkthrough +## In PagerDuty +### Integrating With a PagerDuty Service +1. From the **Configuration** menu, select **Services**. +2. There are two ways to add an integration to a service: + * **If you are adding your integration to an existing service**: Click the **name** of the service you want to add the integration to. Then, select the **Integrations** tab and click the **New Integration** button. + * **If you are creating a new service for your integration**: Please read our documentation in section [Configuring Services and Integrations](https://support.pagerduty.com/docs/services-and-integrations#section-configuring-services-and-integrations) and follow the steps outlined in the [Create a New Service](https://support.pagerduty.com/docs/services-and-integrations#section-create-a-new-service) section, selecting **Gatus** as the **Integration Type** in step 4. Continue with the In Gatus section (below) once you have finished these steps. +3. Enter an **Integration Name** in the format `gatus-service-name` (e.g. `Gatus-Shopping-Cart`) and select **Gatus** from the Integration Type menu. +4. Click the **Add Integration** button to save your new integration. You will be redirected to the Integrations tab for your service. +5. An **Integration Key** will be generated on this screen. Keep this key saved in a safe place, as it will be used when you configure the integration with **Gatus** in the next section. +![PagerDuty Integration Key](https://raw.githubusercontent.com/TwiN/gatus/master/.github/assets/pagerduty-integration-key.png) + + +## In Gatus +In your configuration file, you must first specify the integration key at `alerting.pagerduty.integration-key`, like so: +```yaml +alerting: + pagerduty: + integration-key: "********************************" +``` +You can now add alerts of type `pagerduty` in the endpoint you've defined, like so: +```yaml +endpoints: + - name: website + interval: 30s + url: "https://twin.sh/health" + alerts: + - type: pagerduty + enabled: true + failure-threshold: 3 + success-threshold: 5 + description: "healthcheck failed 3 times in a row" + send-on-resolved: true + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" +``` + +The sample above will do the following: +- Send a request to the `https://twin.sh/health` (`endpoints[].url`) specified every **30s** (`endpoints[].interval`) +- Evaluate the conditions to determine whether the endpoint is "healthy" or not +- **If all conditions are not met 3 (`endpoints[].alerts[].failure-threshold`) times in a row**: Gatus will create a new incident +- **If, after an incident has been triggered, all conditions are met 5 (`endpoints[].alerts[].success-threshold`) times in a row _AND_ `endpoints[].alerts[].send-on-resolved` is set to `true`**: Gatus will resolve the triggered incident + +It is highly recommended to set `endpoints[].alerts[].send-on-resolved` to true for alerts of type `pagerduty`. + + +# How to Uninstall +1. Navigate to the PagerDuty service you'd like to uninstall the Gatus integration from +2. Click on the **Integration** tab +3. Click on the **Gatus** integration +4. Click on **Delete Integration** + +While the above will prevent incidents from being created, you are also highly encouraged to disable the alerts +in your Gatus configuration files or simply remove the integration key from the configuration file. diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..f8bcb549 --- /dev/null +++ b/go.mod @@ -0,0 +1,92 @@ +module github.com/TwiN/gatus/v5 + +go 1.23.3 + +require ( + code.gitea.io/sdk/gitea v0.19.0 + github.com/TwiN/deepmerge v0.2.2 + github.com/TwiN/g8/v2 v2.0.0 + github.com/TwiN/gocache/v2 v2.2.2 + github.com/TwiN/health v1.6.0 + github.com/TwiN/logr v0.3.1 + github.com/TwiN/whois v1.1.10 + github.com/aws/aws-sdk-go v1.55.6 + github.com/coreos/go-oidc/v3 v3.12.0 + github.com/gofiber/fiber/v2 v2.52.6 + github.com/google/go-github/v48 v48.2.0 + github.com/google/uuid v1.6.0 + github.com/ishidawataru/sctp v0.0.0-20230406120618-7ff4192f6ff2 + github.com/lib/pq v1.10.9 + github.com/miekg/dns v1.1.62 + github.com/prometheus-community/pro-bing v0.5.0 + github.com/prometheus/client_golang v1.20.5 + github.com/valyala/fasthttp v1.58.0 + github.com/wcharczuk/go-chart/v2 v2.1.2 + golang.org/x/crypto v0.36.0 + golang.org/x/net v0.37.0 + golang.org/x/oauth2 v0.25.0 + google.golang.org/api v0.214.0 + gopkg.in/mail.v2 v2.3.1 + gopkg.in/yaml.v3 v3.0.1 + modernc.org/sqlite v1.34.4 +) + +require ( + cloud.google.com/go/auth v0.13.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect + cloud.google.com/go/compute/metadata v0.6.0 // indirect + github.com/andybalholm/brotli v1.1.1 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davidmz/go-pageant v1.0.2 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-fed/httpsig v1.1.0 // indirect + github.com/go-jose/go-jose/v4 v4.0.2 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/google/s2a-go v0.1.8 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/googleapis/gax-go/v2 v2.14.0 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect + go.opentelemetry.io/otel v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect + golang.org/x/image v0.18.0 // indirect + golang.org/x/mod v0.18.0 // indirect + golang.org/x/sync v0.12.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect + golang.org/x/tools v0.22.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.2 // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect + modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect + modernc.org/libc v1.55.3 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.8.0 // indirect + modernc.org/strutil v1.2.0 // indirect + modernc.org/token v1.1.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..4cdcb8a5 --- /dev/null +++ b/go.sum @@ -0,0 +1,279 @@ +cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs= +cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q= +cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= +cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= +cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= +cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +code.gitea.io/sdk/gitea v0.19.0 h1:8I6s1s4RHgzxiPHhOQdgim1RWIRcr0LVMbHBjBFXq4Y= +code.gitea.io/sdk/gitea v0.19.0/go.mod h1:IG9xZJoltDNeDSW0qiF2Vqx5orMWa7OhVWrjvrd5NpI= +github.com/TwiN/deepmerge v0.2.2 h1:FUG9QMIYg/j2aQyPPhA3XTFJwXSNHI/swaR4Lbyxwg4= +github.com/TwiN/deepmerge v0.2.2/go.mod h1:4OHvjV3pPNJCJZBHswYAwk6rxiD8h8YZ+9cPo7nu4oI= +github.com/TwiN/g8/v2 v2.0.0 h1:+hwIbRLMhDd2iwHzkZUPp2FkX7yTx8ddYOnS91HkDqQ= +github.com/TwiN/g8/v2 v2.0.0/go.mod h1:4sVAF27q8T8ISggRa/Fb0drw7wpB22B6eWd+/+SGMqE= +github.com/TwiN/gocache/v2 v2.2.2 h1:4HToPfDV8FSbaYO5kkbhLpEllUYse5rAf+hVU/mSsuI= +github.com/TwiN/gocache/v2 v2.2.2/go.mod h1:WfIuwd7GR82/7EfQqEtmLFC3a2vqaKbs4Pe6neB7Gyc= +github.com/TwiN/health v1.6.0 h1:L2ks575JhRgQqWWOfKjw9B0ec172hx7GdToqkYUycQM= +github.com/TwiN/health v1.6.0/go.mod h1:Z6TszwQPMvtSiVx1QMidVRgvVr4KZGfiwqcD7/Z+3iw= +github.com/TwiN/logr v0.3.1 h1:CfTKA83jUmsAoxqrr3p4JxEkqXOBnEE9/f35L5MODy4= +github.com/TwiN/logr v0.3.1/go.mod h1:BZgZFYq6fQdU3KtR8qYato3zUEw53yQDaIuujHb55Jw= +github.com/TwiN/whois v1.1.10 h1:OdnxMRPlegKr+ypwMKq5VpJ8QoD6F2e5gY+MKTs9VyA= +github.com/TwiN/whois v1.1.10/go.mod h1:TjipCMpJRAJYKmtz/rXQBU6UGxMh6bk8SHazu7OMnQE= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk= +github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/coreos/go-oidc/v3 v3.12.0 h1:sJk+8G2qq94rDI6ehZ71Bol3oUHy63qNYmkiSjrc/Jo= +github.com/coreos/go-oidc/v3 v3.12.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= +github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= +github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= +github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk= +github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI= +github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-github/v48 v48.2.0 h1:68puzySE6WqUY9KWmpOsDEQfDZsso98rT6pZcz9HqcE= +github.com/google/go-github/v48 v48.2.0/go.mod h1:dDlehKBDo850ZPvCTK0sEqTCVWcrGl2LcDiajkYi89Y= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/gax-go/v2 v2.14.0 h1:f+jMrjBPl+DL9nI4IQzLUxMq7XrAqFYB7hBPqMNIe8o= +github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/ishidawataru/sctp v0.0.0-20230406120618-7ff4192f6ff2 h1:i2fYnDurfLlJH8AyyMOnkLHnHeP8Ff/DDpuZA/D3bPo= +github.com/ishidawataru/sctp v0.0.0-20230406120618-7ff4192f6ff2/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= +github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus-community/pro-bing v0.5.0 h1:Fq+4BUXKIvsPtXUY8K+04ud9dkAuFozqGmRAyNUpffY= +github.com/prometheus-community/pro-bing v0.5.0/go.mod h1:1joR9oXdMEAcAJJvhs+8vNDvTg5thfAZcRFhcUozG2g= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE= +github.com/valyala/fasthttp v1.58.0/go.mod h1:SYXvHHaFp7QZHGKSHmoMipInhrI5StHrhDTYVEjK/Kw= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/wcharczuk/go-chart/v2 v2.1.2 h1:Y17/oYNuXwZg6TFag06qe8sBajwwsuvPiJJXcUcLL6E= +github.com/wcharczuk/go-chart/v2 v2.1.2/go.mod h1:Zi4hbaqlWpYajnXB2K22IUYVXRXaLfSGNNR7P4ukyyQ= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= +golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= +golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.214.0 h1:h2Gkq07OYi6kusGOaT/9rnNljuXmqPnaig7WGPmKbwA= +google.golang.org/api v0.214.0/go.mod h1:bYPpLG8AyeMWwDU6NXoB00xC0DFkikVvd5MfwoxjLqE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk= +gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= +modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= +modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y= +modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s= +modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= +modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= +modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b h1:BnN1t+pb1cy61zbvSUV7SeI0PwosMhlAEi/vBY4qxp8= +modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= +modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= +modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= +modernc.org/sqlite v1.34.4 h1:sjdARozcL5KJBvYQvLlZEmctRgW9xqIZc2ncN7PU0P8= +modernc.org/sqlite v1.34.4/go.mod h1:3QQFCG2SEMtc2nv+Wq4cQCH7Hjcg+p/RMlS1XK+zwbk= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/jsonpath/jsonpath.go b/jsonpath/jsonpath.go new file mode 100644 index 00000000..b41e393f --- /dev/null +++ b/jsonpath/jsonpath.go @@ -0,0 +1,131 @@ +package jsonpath + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" +) + +// Eval is a half-baked json path implementation that needs some love +func Eval(path string, b []byte) (string, int, error) { + if len(path) == 0 && !(len(b) != 0 && b[0] == '[' && b[len(b)-1] == ']') { + // if there's no path AND the value is not a JSON array, then there's nothing to walk + return string(b), len(b), nil + } + var object interface{} + if err := json.Unmarshal(b, &object); err != nil { + return "", 0, err + } + return walk(path, object) +} + +// walk traverses the object and returns the value as a string as well as its length +func walk(path string, object interface{}) (string, int, error) { + var keys []string + startOfCurrentKey, bracketDepth := 0, 0 + for i := range path { + if path[i] == '[' { + bracketDepth++ + } else if path[i] == ']' { + bracketDepth-- + } + // If we encounter a dot, we've reached the end of a key unless we're inside a bracket + if path[i] == '.' && bracketDepth == 0 { + keys = append(keys, path[startOfCurrentKey:i]) + startOfCurrentKey = i + 1 + } + } + if startOfCurrentKey <= len(path) { + keys = append(keys, path[startOfCurrentKey:]) + } + currentKey := keys[0] + switch value := extractValue(currentKey, object).(type) { + case map[string]interface{}: + newPath := strings.Replace(path, fmt.Sprintf("%s.", currentKey), "", 1) + if path == newPath { + // If the path hasn't changed, it means we're at the end of the path + // So we'll treat it as a string by re-marshaling it to JSON since it's a map. + // Note that the output JSON will be minified. + b, err := json.Marshal(value) + return string(b), len(b), err + } + return walk(newPath, value) + case string: + if len(keys) > 1 { + return "", 0, fmt.Errorf("couldn't walk through '%s', because '%s' was a string instead of an object", keys[1], currentKey) + } + return value, len(value), nil + case []interface{}: + return fmt.Sprintf("%v", value), len(value), nil + case interface{}: + newValue := fmt.Sprintf("%v", value) + return newValue, len(newValue), nil + default: + return "", 0, fmt.Errorf("couldn't walk through '%s' because type was '%T', but expected 'map[string]interface{}'", currentKey, value) + } +} + +func extractValue(currentKey string, value interface{}) interface{} { + // Check if the current key ends with [#] + if strings.HasSuffix(currentKey, "]") && strings.Contains(currentKey, "[") { + var isNestedArray bool + var index string + startOfBracket, endOfBracket, bracketDepth := 0, 0, 0 + for i := range currentKey { + if currentKey[i] == '[' { + startOfBracket = i + bracketDepth++ + } else if currentKey[i] == ']' && bracketDepth == 1 { + bracketDepth-- + endOfBracket = i + index = currentKey[startOfBracket+1 : i] + if len(currentKey) > i+1 && currentKey[i+1] == '[' { + isNestedArray = true // there's more keys. + } + break + } + } + arrayIndex, err := strconv.Atoi(index) + if err != nil { + return nil + } + currentKeyWithoutIndex := currentKey[:startOfBracket] + // if currentKeyWithoutIndex contains only an index (i.e. [0] or 0) + if len(currentKeyWithoutIndex) == 0 { + array, _ := value.([]interface{}) + if len(array) > arrayIndex { + if isNestedArray { + return extractValue(currentKey[endOfBracket+1:], array[arrayIndex]) + } + return array[arrayIndex] + } + return nil + } + if value == nil || value.(map[string]interface{})[currentKeyWithoutIndex] == nil { + return nil + } + // if currentKeyWithoutIndex contains both a key and an index (i.e. data[0]) + array, _ := value.(map[string]interface{})[currentKeyWithoutIndex].([]interface{}) + if len(array) > arrayIndex { + if isNestedArray { + return extractValue(currentKey[endOfBracket+1:], array[arrayIndex]) + } + return array[arrayIndex] + } + return nil + } + if valueAsSlice, ok := value.([]interface{}); ok { + // If the type is a slice, return it + // This happens when the body (value) is a JSON array + return valueAsSlice + } + if valueAsMap, ok := value.(map[string]interface{}); ok { + // If the value is a map, then we get the currentKey from that map + // This happens when the body (value) is a JSON object + return valueAsMap[currentKey] + } + // If the value is neither a map, nor a slice, nor an index, then we cannot retrieve the currentKey + // from said value. This usually happens when the body (value) is null. + return value +} diff --git a/jsonpath/jsonpath_bench_test.go b/jsonpath/jsonpath_bench_test.go new file mode 100644 index 00000000..0dcae659 --- /dev/null +++ b/jsonpath/jsonpath_bench_test.go @@ -0,0 +1,11 @@ +package jsonpath + +import "testing" + +func BenchmarkEval(b *testing.B) { + for i := 0; i < b.N; i++ { + Eval("ids[0]", []byte(`{"ids": [1, 2]}`)) + Eval("long.simple.walk", []byte(`{"long": {"simple": {"walk": "value"}}}`)) + Eval("data[0].apps[1].name", []byte(`{"data": [{"apps": [{"name":"app1"}, {"name":"app2"}, {"name":"app3"}]}]}`)) + } +} diff --git a/jsonpath/jsonpath_test.go b/jsonpath/jsonpath_test.go new file mode 100644 index 00000000..83244c5c --- /dev/null +++ b/jsonpath/jsonpath_test.go @@ -0,0 +1,196 @@ +package jsonpath + +import ( + "testing" +) + +func TestEval(t *testing.T) { + type Scenario struct { + Name string + Path string + Data string + ExpectedOutput string + ExpectedOutputLength int + ExpectedError bool + } + scenarios := []Scenario{ + { + Name: "simple", + Path: "key", + Data: `{"key": "value"}`, + ExpectedOutput: "value", + ExpectedOutputLength: 5, + ExpectedError: false, + }, + { + Name: "simple-with-invalid-data", + Path: "key", + Data: "invalid data", + ExpectedOutput: "", + ExpectedOutputLength: 0, + ExpectedError: true, + }, + { + Name: "invalid-path", + Path: "key", + Data: `{}`, + ExpectedOutput: "", + ExpectedOutputLength: 0, + ExpectedError: true, + }, + { + Name: "long-simple-walk", + Path: "long.simple.walk", + Data: `{"long": {"simple": {"walk": "value"}}}`, + ExpectedOutput: "value", + ExpectedOutputLength: 5, + ExpectedError: false, + }, + { + Name: "array-of-objects", + Path: "ids[1].id", + Data: `{"ids": [{"id": 1}, {"id": 2}]}`, + ExpectedOutput: "2", + ExpectedOutputLength: 1, + ExpectedError: false, + }, + { + Name: "array-of-values", + Path: "ids[0]", + Data: `{"ids": [1, 2]}`, + ExpectedOutput: "1", + ExpectedOutputLength: 1, + ExpectedError: false, + }, + { + Name: "array-of-values-with-no-path", + Path: "", + Data: `[1, 2]`, + ExpectedOutput: "[1 2]", // the output is an array + ExpectedOutputLength: 2, + ExpectedError: false, + }, + { + Name: "array-of-values-and-invalid-index", + Path: "ids[wat]", + Data: `{"ids": [1, 2]}`, + ExpectedOutput: "", + ExpectedOutputLength: 0, + ExpectedError: true, + }, + { + Name: "array-of-values-at-root", + Path: "[1]", + Data: `[1, 2]`, + ExpectedOutput: "2", + ExpectedOutputLength: 1, + ExpectedError: false, + }, + { + Name: "array-of-objects-at-root", + Path: "[0]", + Data: `[{"id": 1}, {"id": 2}]`, + ExpectedOutput: `{"id":1}`, + ExpectedOutputLength: 8, + ExpectedError: false, + }, + { + Name: "array-of-objects-with-int-at-root", + Path: "[0].id", + Data: `[{"id": 1}, {"id": 2}]`, + ExpectedOutput: "1", + ExpectedOutputLength: 1, + ExpectedError: false, + }, + { + Name: "array-of-objects-at-root-and-invalid-index", + Path: "[5].id", + Data: `[{"id": 1}, {"id": 2}]`, + ExpectedOutput: "", + ExpectedOutputLength: 0, + ExpectedError: true, + }, + { + Name: "long-walk-and-array", + Path: "data.ids[0].id", + Data: `{"data": {"ids": [{"id": 1}, {"id": 2}, {"id": 3}]}}`, + ExpectedOutput: "1", + ExpectedOutputLength: 1, + ExpectedError: false, + }, + { + Name: "nested-array", + Path: "[3][2]", + Data: `[[1, 2], [3, 4], [], [5, 6, 7]]`, + ExpectedOutput: "7", + ExpectedOutputLength: 1, + ExpectedError: false, + }, + { + Name: "object-with-nested-arrays", + Path: "data[1][1]", + Data: `{"data": [["a", "b", "c"], ["d", "eeeee", "f"]]}`, + ExpectedOutput: "eeeee", + ExpectedOutputLength: 5, + ExpectedError: false, + }, + { + Name: "object-with-arrays-of-objects", + Path: "data[0].apps[1].name", + Data: `{"data": [{"apps": [{"name":"app1"}, {"name":"app2"}, {"name":"app3"}]}]}`, + ExpectedOutput: "app2", + ExpectedOutputLength: 4, + ExpectedError: false, + }, + { + Name: "object-with-arrays-of-objects-with-missing-element", + Path: "data[0].apps[1].name", + Data: `{"data": [{"apps": []}]}`, + ExpectedOutput: "", + ExpectedOutputLength: 0, + ExpectedError: true, + }, + { + Name: "partially-invalid-path-issue122", + Path: "data.name.invalid", + Data: `{"data": {"name": "john"}}`, + ExpectedOutput: "", + ExpectedOutputLength: 0, + ExpectedError: true, + }, + { + Name: "float-as-string", + Path: "balance", + Data: `{"balance": "123.40000000000005"}`, + ExpectedOutput: "123.40000000000005", + ExpectedOutputLength: 18, + ExpectedError: false, + }, + { + Name: "float-as-number", + Path: "balance", + Data: `{"balance": 123.40000000000005}`, + ExpectedOutput: "123.40000000000005", + ExpectedOutputLength: 18, + ExpectedError: false, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + output, outputLength, err := Eval(scenario.Path, []byte(scenario.Data)) + if (err != nil) != scenario.ExpectedError { + if scenario.ExpectedError { + t.Errorf("Expected error, got '%v'", err) + } else { + t.Errorf("Expected no error, got '%v'", err) + } + } + if outputLength != scenario.ExpectedOutputLength { + t.Errorf("Expected output length to be %v, but was %v", scenario.ExpectedOutputLength, outputLength) + } + if output != scenario.ExpectedOutput { + t.Errorf("Expected output to be %v, but was %v", scenario.ExpectedOutput, output) + } + }) + } +} diff --git a/main.go b/main.go new file mode 100644 index 00000000..bbb6e0c1 --- /dev/null +++ b/main.go @@ -0,0 +1,197 @@ +package main + +import ( + "os" + "os/signal" + "strconv" + "syscall" + "time" + + "github.com/TwiN/gatus/v5/config" + "github.com/TwiN/gatus/v5/controller" + "github.com/TwiN/gatus/v5/storage/store" + "github.com/TwiN/gatus/v5/watchdog" + "github.com/TwiN/logr" +) + +const ( + GatusConfigPathEnvVar = "GATUS_CONFIG_PATH" + GatusConfigFileEnvVar = "GATUS_CONFIG_FILE" // Deprecated in favor of GatusConfigPathEnvVar + GatusLogLevelEnvVar = "GATUS_LOG_LEVEL" +) + +func main() { + if delayInSeconds, _ := strconv.Atoi(os.Getenv("GATUS_DELAY_START_SECONDS")); delayInSeconds > 0 { + logr.Infof("Delaying start by %d seconds", delayInSeconds) + time.Sleep(time.Duration(delayInSeconds) * time.Second) + } + configureLogging() + cfg, err := loadConfiguration() + if err != nil { + panic(err) + } + initializeStorage(cfg) + start(cfg) + // Wait for termination signal + signalChannel := make(chan os.Signal, 1) + done := make(chan bool, 1) + signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM) + go func() { + <-signalChannel + logr.Info("Received termination signal, attempting to gracefully shut down") + stop(cfg) + save() + done <- true + }() + <-done + logr.Info("Shutting down") +} + +func start(cfg *config.Config) { + go controller.Handle(cfg) + watchdog.Monitor(cfg) + go listenToConfigurationFileChanges(cfg) +} + +func stop(cfg *config.Config) { + watchdog.Shutdown(cfg) + controller.Shutdown() +} + +func save() { + if err := store.Get().Save(); err != nil { + logr.Errorf("Failed to save storage provider: %s", err.Error()) + } +} + +func configureLogging() { + logLevelAsString := os.Getenv(GatusLogLevelEnvVar) + if logLevel, err := logr.LevelFromString(logLevelAsString); err != nil { + logr.SetThreshold(logr.LevelInfo) + if len(logLevelAsString) == 0 { + logr.Infof("[main.configureLogging] Defaulting log level to %s", logr.LevelInfo) + } else { + logr.Warnf("[main.configureLogging] Invalid log level '%s', defaulting to %s", logLevelAsString, logr.LevelInfo) + } + } else { + logr.SetThreshold(logLevel) + logr.Infof("[main.configureLogging] Log Level is set to %s", logr.GetThreshold()) + } +} + +func loadConfiguration() (*config.Config, error) { + configPath := os.Getenv(GatusConfigPathEnvVar) + // Backwards compatibility + if len(configPath) == 0 { + if configPath = os.Getenv(GatusConfigFileEnvVar); len(configPath) > 0 { + logr.Warnf("WARNING: %s is deprecated. Please use %s instead.", GatusConfigFileEnvVar, GatusConfigPathEnvVar) + } + } + return config.LoadConfiguration(configPath) +} + +// initializeStorage initializes the storage provider +// +// Q: "TwiN, why are you putting this here? Wouldn't it make more sense to have this in the config?!" +// A: Yes. Yes it would make more sense to have it in the config package. But I don't want to import +// the massive SQL dependencies just because I want to import the config, so here we are. +func initializeStorage(cfg *config.Config) { + err := store.Initialize(cfg.Storage) + if err != nil { + panic(err) + } + // Remove all EndpointStatus that represent endpoints which no longer exist in the configuration + var keys []string + for _, ep := range cfg.Endpoints { + keys = append(keys, ep.Key()) + } + for _, ee := range cfg.ExternalEndpoints { + keys = append(keys, ee.Key()) + } + numberOfEndpointStatusesDeleted := store.Get().DeleteAllEndpointStatusesNotInKeys(keys) + if numberOfEndpointStatusesDeleted > 0 { + logr.Infof("[main.initializeStorage] Deleted %d endpoint statuses because their matching endpoints no longer existed", numberOfEndpointStatusesDeleted) + } + // Clean up the triggered alerts from the storage provider and load valid triggered endpoint alerts + numberOfPersistedTriggeredAlertsLoaded := 0 + for _, ep := range cfg.Endpoints { + var checksums []string + for _, alert := range ep.Alerts { + if alert.IsEnabled() { + checksums = append(checksums, alert.Checksum()) + } + } + numberOfTriggeredAlertsDeleted := store.Get().DeleteAllTriggeredAlertsNotInChecksumsByEndpoint(ep, checksums) + if numberOfTriggeredAlertsDeleted > 0 { + logr.Debugf("[main.initializeStorage] Deleted %d triggered alerts for endpoint with key=%s because their configurations have been changed or deleted", numberOfTriggeredAlertsDeleted, ep.Key()) + } + for _, alert := range ep.Alerts { + exists, resolveKey, numberOfSuccessesInARow, err := store.Get().GetTriggeredEndpointAlert(ep, alert) + if err != nil { + logr.Errorf("[main.initializeStorage] Failed to get triggered alert for endpoint with key=%s: %s", ep.Key(), err.Error()) + continue + } + if exists { + alert.Triggered, alert.ResolveKey = true, resolveKey + ep.NumberOfSuccessesInARow, ep.NumberOfFailuresInARow = numberOfSuccessesInARow, alert.FailureThreshold + numberOfPersistedTriggeredAlertsLoaded++ + } + } + } + for _, ee := range cfg.ExternalEndpoints { + var checksums []string + for _, alert := range ee.Alerts { + if alert.IsEnabled() { + checksums = append(checksums, alert.Checksum()) + } + } + convertedEndpoint := ee.ToEndpoint() + numberOfTriggeredAlertsDeleted := store.Get().DeleteAllTriggeredAlertsNotInChecksumsByEndpoint(convertedEndpoint, checksums) + if numberOfTriggeredAlertsDeleted > 0 { + logr.Debugf("[main.initializeStorage] Deleted %d triggered alerts for endpoint with key=%s because their configurations have been changed or deleted", numberOfTriggeredAlertsDeleted, ee.Key()) + } + for _, alert := range ee.Alerts { + exists, resolveKey, numberOfSuccessesInARow, err := store.Get().GetTriggeredEndpointAlert(convertedEndpoint, alert) + if err != nil { + logr.Errorf("[main.initializeStorage] Failed to get triggered alert for endpoint with key=%s: %s", ee.Key(), err.Error()) + continue + } + if exists { + alert.Triggered, alert.ResolveKey = true, resolveKey + ee.NumberOfSuccessesInARow, ee.NumberOfFailuresInARow = numberOfSuccessesInARow, alert.FailureThreshold + numberOfPersistedTriggeredAlertsLoaded++ + } + } + } + if numberOfPersistedTriggeredAlertsLoaded > 0 { + logr.Infof("[main.initializeStorage] Loaded %d persisted triggered alerts", numberOfPersistedTriggeredAlertsLoaded) + } +} + +func listenToConfigurationFileChanges(cfg *config.Config) { + for { + time.Sleep(30 * time.Second) + if cfg.HasLoadedConfigurationBeenModified() { + logr.Info("[main.listenToConfigurationFileChanges] Configuration file has been modified") + stop(cfg) + time.Sleep(time.Second) // Wait a bit to make sure everything is done. + save() + updatedConfig, err := loadConfiguration() + if err != nil { + if cfg.SkipInvalidConfigUpdate { + logr.Errorf("[main.listenToConfigurationFileChanges] Failed to load new configuration: %s", err.Error()) + logr.Error("[main.listenToConfigurationFileChanges] The configuration file was updated, but it is not valid. The old configuration will continue being used.") + // Update the last file modification time to avoid trying to process the same invalid configuration again + cfg.UpdateLastFileModTime() + continue + } else { + panic(err) + } + } + store.Get().Close() + initializeStorage(updatedConfig) + start(updatedConfig) + return + } + } +} diff --git a/metrics/metrics.go b/metrics/metrics.go new file mode 100644 index 00000000..990e4a5e --- /dev/null +++ b/metrics/metrics.go @@ -0,0 +1,84 @@ +package metrics + +import ( + "strconv" + + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +const namespace = "gatus" // The prefix of the metrics + +var ( + initializedMetrics bool // Whether the metrics have been initialized + + resultTotal *prometheus.CounterVec + resultDurationSeconds *prometheus.GaugeVec + resultConnectedTotal *prometheus.CounterVec + resultCodeTotal *prometheus.CounterVec + resultCertificateExpirationSeconds *prometheus.GaugeVec + resultEndpointSuccess *prometheus.GaugeVec +) + +func initializePrometheusMetrics() { + resultTotal = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Name: "results_total", + Help: "Number of results per endpoint", + }, []string{"key", "group", "name", "type", "success"}) + resultDurationSeconds = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "results_duration_seconds", + Help: "Duration of the request in seconds", + }, []string{"key", "group", "name", "type"}) + resultConnectedTotal = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Name: "results_connected_total", + Help: "Total number of results in which a connection was successfully established", + }, []string{"key", "group", "name", "type"}) + resultCodeTotal = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Name: "results_code_total", + Help: "Total number of results by code", + }, []string{"key", "group", "name", "type", "code"}) + resultCertificateExpirationSeconds = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "results_certificate_expiration_seconds", + Help: "Number of seconds until the certificate expires", + }, []string{"key", "group", "name", "type"}) + resultEndpointSuccess = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "results_endpoint_success", + Help: "Displays whether or not the endpoint was a success", + }, []string{"key", "group", "name", "type"}) +} + +// PublishMetricsForEndpoint publishes metrics for the given endpoint and its result. +// These metrics will be exposed at /metrics if the metrics are enabled +func PublishMetricsForEndpoint(ep *endpoint.Endpoint, result *endpoint.Result) { + if !initializedMetrics { + initializePrometheusMetrics() + initializedMetrics = true + } + endpointType := ep.Type() + resultTotal.WithLabelValues(ep.Key(), ep.Group, ep.Name, string(endpointType), strconv.FormatBool(result.Success)).Inc() + resultDurationSeconds.WithLabelValues(ep.Key(), ep.Group, ep.Name, string(endpointType)).Set(result.Duration.Seconds()) + if result.Connected { + resultConnectedTotal.WithLabelValues(ep.Key(), ep.Group, ep.Name, string(endpointType)).Inc() + } + if result.DNSRCode != "" { + resultCodeTotal.WithLabelValues(ep.Key(), ep.Group, ep.Name, string(endpointType), result.DNSRCode).Inc() + } + if result.HTTPStatus != 0 { + resultCodeTotal.WithLabelValues(ep.Key(), ep.Group, ep.Name, string(endpointType), strconv.Itoa(result.HTTPStatus)).Inc() + } + if result.CertificateExpiration != 0 { + resultCertificateExpirationSeconds.WithLabelValues(ep.Key(), ep.Group, ep.Name, string(endpointType)).Set(result.CertificateExpiration.Seconds()) + } + if result.Success { + resultEndpointSuccess.WithLabelValues(ep.Key(), ep.Group, ep.Name, string(endpointType)).Set(1) + } else { + resultEndpointSuccess.WithLabelValues(ep.Key(), ep.Group, ep.Name, string(endpointType)).Set(0) + } +} diff --git a/metrics/metrics_test.go b/metrics/metrics_test.go new file mode 100644 index 00000000..73a680d9 --- /dev/null +++ b/metrics/metrics_test.go @@ -0,0 +1,127 @@ +package metrics + +import ( + "bytes" + "testing" + "time" + + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/config/endpoint/dns" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" +) + +func TestPublishMetricsForEndpoint(t *testing.T) { + httpEndpoint := &endpoint.Endpoint{Name: "http-ep-name", Group: "http-ep-group", URL: "https://example.org"} + PublishMetricsForEndpoint(httpEndpoint, &endpoint.Result{ + HTTPStatus: 200, + Connected: true, + Duration: 123 * time.Millisecond, + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[STATUS] == 200", Success: true}, + {Condition: "[CERTIFICATE_EXPIRATION] > 48h", Success: true}, + }, + Success: true, + CertificateExpiration: 49 * time.Hour, + }) + err := testutil.GatherAndCompare(prometheus.Gatherers{prometheus.DefaultGatherer}, bytes.NewBufferString(` +# HELP gatus_results_code_total Total number of results by code +# TYPE gatus_results_code_total counter +gatus_results_code_total{code="200",group="http-ep-group",key="http-ep-group_http-ep-name",name="http-ep-name",type="HTTP"} 1 +# HELP gatus_results_connected_total Total number of results in which a connection was successfully established +# TYPE gatus_results_connected_total counter +gatus_results_connected_total{group="http-ep-group",key="http-ep-group_http-ep-name",name="http-ep-name",type="HTTP"} 1 +# HELP gatus_results_duration_seconds Duration of the request in seconds +# TYPE gatus_results_duration_seconds gauge +gatus_results_duration_seconds{group="http-ep-group",key="http-ep-group_http-ep-name",name="http-ep-name",type="HTTP"} 0.123 +# HELP gatus_results_total Number of results per endpoint +# TYPE gatus_results_total counter +gatus_results_total{group="http-ep-group",key="http-ep-group_http-ep-name",name="http-ep-name",success="true",type="HTTP"} 1 +# HELP gatus_results_certificate_expiration_seconds Number of seconds until the certificate expires +# TYPE gatus_results_certificate_expiration_seconds gauge +gatus_results_certificate_expiration_seconds{group="http-ep-group",key="http-ep-group_http-ep-name",name="http-ep-name",type="HTTP"} 176400 +# HELP gatus_results_endpoint_success Displays whether or not the endpoint was a success +# TYPE gatus_results_endpoint_success gauge +gatus_results_endpoint_success{group="http-ep-group",key="http-ep-group_http-ep-name",name="http-ep-name",type="HTTP"} 1 +`), "gatus_results_code_total", "gatus_results_connected_total", "gatus_results_duration_seconds", "gatus_results_total", "gatus_results_certificate_expiration_seconds", "gatus_results_endpoint_success") + if err != nil { + t.Errorf("Expected no errors but got: %v", err) + } + PublishMetricsForEndpoint(httpEndpoint, &endpoint.Result{ + HTTPStatus: 200, + Connected: true, + Duration: 125 * time.Millisecond, + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[STATUS] == 200", Success: true}, + {Condition: "[CERTIFICATE_EXPIRATION] > 47h", Success: false}, + }, + Success: false, + CertificateExpiration: 47 * time.Hour, + }) + err = testutil.GatherAndCompare(prometheus.Gatherers{prometheus.DefaultGatherer}, bytes.NewBufferString(` +# HELP gatus_results_code_total Total number of results by code +# TYPE gatus_results_code_total counter +gatus_results_code_total{code="200",group="http-ep-group",key="http-ep-group_http-ep-name",name="http-ep-name",type="HTTP"} 2 +# HELP gatus_results_connected_total Total number of results in which a connection was successfully established +# TYPE gatus_results_connected_total counter +gatus_results_connected_total{group="http-ep-group",key="http-ep-group_http-ep-name",name="http-ep-name",type="HTTP"} 2 +# HELP gatus_results_duration_seconds Duration of the request in seconds +# TYPE gatus_results_duration_seconds gauge +gatus_results_duration_seconds{group="http-ep-group",key="http-ep-group_http-ep-name",name="http-ep-name",type="HTTP"} 0.125 +# HELP gatus_results_total Number of results per endpoint +# TYPE gatus_results_total counter +gatus_results_total{group="http-ep-group",key="http-ep-group_http-ep-name",name="http-ep-name",success="false",type="HTTP"} 1 +gatus_results_total{group="http-ep-group",key="http-ep-group_http-ep-name",name="http-ep-name",success="true",type="HTTP"} 1 +# HELP gatus_results_certificate_expiration_seconds Number of seconds until the certificate expires +# TYPE gatus_results_certificate_expiration_seconds gauge +gatus_results_certificate_expiration_seconds{group="http-ep-group",key="http-ep-group_http-ep-name",name="http-ep-name",type="HTTP"} 169200 +# HELP gatus_results_endpoint_success Displays whether or not the endpoint was a success +# TYPE gatus_results_endpoint_success gauge +gatus_results_endpoint_success{group="http-ep-group",key="http-ep-group_http-ep-name",name="http-ep-name",type="HTTP"} 0 +`), "gatus_results_code_total", "gatus_results_connected_total", "gatus_results_duration_seconds", "gatus_results_total", "gatus_results_certificate_expiration_seconds", "gatus_results_endpoint_success") + if err != nil { + t.Errorf("Expected no errors but got: %v", err) + } + dnsEndpoint := &endpoint.Endpoint{Name: "dns-ep-name", Group: "dns-ep-group", URL: "8.8.8.8", DNSConfig: &dns.Config{ + QueryType: "A", + QueryName: "example.com.", + }} + PublishMetricsForEndpoint(dnsEndpoint, &endpoint.Result{ + DNSRCode: "NOERROR", + Connected: true, + Duration: 50 * time.Millisecond, + ConditionResults: []*endpoint.ConditionResult{ + {Condition: "[DNS_RCODE] == NOERROR", Success: true}, + }, + Success: true, + }) + err = testutil.GatherAndCompare(prometheus.Gatherers{prometheus.DefaultGatherer}, bytes.NewBufferString(` +# HELP gatus_results_code_total Total number of results by code +# TYPE gatus_results_code_total counter +gatus_results_code_total{code="200",group="http-ep-group",key="http-ep-group_http-ep-name",name="http-ep-name",type="HTTP"} 2 +gatus_results_code_total{code="NOERROR",group="dns-ep-group",key="dns-ep-group_dns-ep-name",name="dns-ep-name",type="DNS"} 1 +# HELP gatus_results_connected_total Total number of results in which a connection was successfully established +# TYPE gatus_results_connected_total counter +gatus_results_connected_total{group="dns-ep-group",key="dns-ep-group_dns-ep-name",name="dns-ep-name",type="DNS"} 1 +gatus_results_connected_total{group="http-ep-group",key="http-ep-group_http-ep-name",name="http-ep-name",type="HTTP"} 2 +# HELP gatus_results_duration_seconds Duration of the request in seconds +# TYPE gatus_results_duration_seconds gauge +gatus_results_duration_seconds{group="dns-ep-group",key="dns-ep-group_dns-ep-name",name="dns-ep-name",type="DNS"} 0.05 +gatus_results_duration_seconds{group="http-ep-group",key="http-ep-group_http-ep-name",name="http-ep-name",type="HTTP"} 0.125 +# HELP gatus_results_total Number of results per endpoint +# TYPE gatus_results_total counter +gatus_results_total{group="dns-ep-group",key="dns-ep-group_dns-ep-name",name="dns-ep-name",success="true",type="DNS"} 1 +gatus_results_total{group="http-ep-group",key="http-ep-group_http-ep-name",name="http-ep-name",success="false",type="HTTP"} 1 +gatus_results_total{group="http-ep-group",key="http-ep-group_http-ep-name",name="http-ep-name",success="true",type="HTTP"} 1 +# HELP gatus_results_certificate_expiration_seconds Number of seconds until the certificate expires +# TYPE gatus_results_certificate_expiration_seconds gauge +gatus_results_certificate_expiration_seconds{group="http-ep-group",key="http-ep-group_http-ep-name",name="http-ep-name",type="HTTP"} 169200 +# HELP gatus_results_endpoint_success Displays whether or not the endpoint was a success +# TYPE gatus_results_endpoint_success gauge +gatus_results_endpoint_success{group="dns-ep-group",key="dns-ep-group_dns-ep-name",name="dns-ep-name",type="DNS"} 1 +gatus_results_endpoint_success{group="http-ep-group",key="http-ep-group_http-ep-name",name="http-ep-name",type="HTTP"} 0 +`), "gatus_results_code_total", "gatus_results_connected_total", "gatus_results_duration_seconds", "gatus_results_total", "gatus_results_certificate_expiration_seconds", "gatus_results_endpoint_success") + if err != nil { + t.Errorf("Expected no errors but got: %v", err) + } +} diff --git a/pattern/pattern.go b/pattern/pattern.go new file mode 100644 index 00000000..91d26ed6 --- /dev/null +++ b/pattern/pattern.go @@ -0,0 +1,20 @@ +package pattern + +import ( + "path/filepath" + "strings" +) + +// Match checks whether a string matches a pattern +func Match(pattern, s string) bool { + if pattern == "*" { + return true + } + // Separators found in the string break filepath.Match, so we'll remove all of them. + // This has a pretty significant impact on performance when there are separators in + // the strings, but at least it doesn't break filepath.Match. + s = strings.ReplaceAll(s, string(filepath.Separator), "") + pattern = strings.ReplaceAll(pattern, string(filepath.Separator), "") + matched, _ := filepath.Match(pattern, s) + return matched +} diff --git a/pattern/pattern_bench_test.go b/pattern/pattern_bench_test.go new file mode 100644 index 00000000..e27df78f --- /dev/null +++ b/pattern/pattern_bench_test.go @@ -0,0 +1,21 @@ +package pattern + +import "testing" + +func BenchmarkMatch(b *testing.B) { + for n := 0; n < b.N; n++ { + if !Match("*ing*", "livingroom") { + b.Error("should've matched") + } + } + b.ReportAllocs() +} + +func BenchmarkMatchWithBackslash(b *testing.B) { + for n := 0; n < b.N; n++ { + if !Match("*ing*", "living\\room") { + b.Error("should've matched") + } + } + b.ReportAllocs() +} diff --git a/pattern/pattern_test.go b/pattern/pattern_test.go new file mode 100644 index 00000000..c4b84f9b --- /dev/null +++ b/pattern/pattern_test.go @@ -0,0 +1,43 @@ +package pattern + +import ( + "fmt" + "testing" +) + +func TestMatch(t *testing.T) { + testMatch(t, "*", "livingroom_123", true) + testMatch(t, "**", "livingroom_123", true) + testMatch(t, "living*", "livingroom_123", true) + testMatch(t, "*living*", "livingroom_123", true) + testMatch(t, "*123", "livingroom_123", true) + testMatch(t, "*_*", "livingroom_123", true) + testMatch(t, "living*_*3", "livingroom_123", true) + testMatch(t, "living*room_*3", "livingroom_123", true) + testMatch(t, "living*room_*3", "livingroom_123", true) + testMatch(t, "*vin*om*2*", "livingroom_123", true) + testMatch(t, "livingroom_123", "livingroom_123", true) + testMatch(t, "*livingroom_123*", "livingroom_123", true) + testMatch(t, "*test*", "\\test", true) + testMatch(t, "livingroom", "livingroom_123", false) + testMatch(t, "livingroom123", "livingroom_123", false) + testMatch(t, "what", "livingroom_123", false) + testMatch(t, "*what*", "livingroom_123", false) + testMatch(t, "*.*", "livingroom_123", false) + testMatch(t, "room*123", "livingroom_123", false) +} + +func testMatch(t *testing.T, pattern, key string, expectedToMatch bool) { + t.Run(fmt.Sprintf("pattern '%s' from '%s'", pattern, key), func(t *testing.T) { + matched := Match(pattern, key) + if expectedToMatch { + if !matched { + t.Errorf("%s should've matched pattern '%s'", key, pattern) + } + } else { + if matched { + t.Errorf("%s shouldn't have matched pattern '%s'", key, pattern) + } + } + }) +} diff --git a/security/basic.go b/security/basic.go new file mode 100644 index 00000000..1ae349cb --- /dev/null +++ b/security/basic.go @@ -0,0 +1,16 @@ +package security + +// BasicConfig is the configuration for Basic authentication +type BasicConfig struct { + // Username is the name which will need to be used for a successful authentication + Username string `yaml:"username"` + + // PasswordBcryptHashBase64Encoded is the base64 encoded string of the Bcrypt hash of the password to use to + // authenticate using basic auth. + PasswordBcryptHashBase64Encoded string `yaml:"password-bcrypt-base64"` +} + +// isValid returns whether the basic security configuration is valid or not +func (c *BasicConfig) isValid() bool { + return len(c.Username) > 0 && len(c.PasswordBcryptHashBase64Encoded) > 0 +} diff --git a/security/basic_test.go b/security/basic_test.go new file mode 100644 index 00000000..4895f48e --- /dev/null +++ b/security/basic_test.go @@ -0,0 +1,23 @@ +package security + +import "testing" + +func TestBasicConfig_IsValidUsingBcrypt(t *testing.T) { + basicConfig := &BasicConfig{ + Username: "admin", + PasswordBcryptHashBase64Encoded: "JDJhJDA4JDFoRnpPY1hnaFl1OC9ISlFsa21VS09wOGlPU1ZOTDlHZG1qeTFvb3dIckRBUnlHUmNIRWlT", + } + if !basicConfig.isValid() { + t.Error("basicConfig should've been valid") + } +} + +func TestBasicConfig_IsValidWhenPasswordIsInvalidUsingBcrypt(t *testing.T) { + basicConfig := &BasicConfig{ + Username: "admin", + PasswordBcryptHashBase64Encoded: "", + } + if basicConfig.isValid() { + t.Error("basicConfig shouldn't have been valid") + } +} diff --git a/security/config.go b/security/config.go new file mode 100644 index 00000000..994fc0c2 --- /dev/null +++ b/security/config.go @@ -0,0 +1,110 @@ +package security + +import ( + "encoding/base64" + "net/http" + + g8 "github.com/TwiN/g8/v2" + "github.com/TwiN/logr" + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/adaptor" + "github.com/gofiber/fiber/v2/middleware/basicauth" + "golang.org/x/crypto/bcrypt" +) + +const ( + cookieNameState = "gatus_state" + cookieNameNonce = "gatus_nonce" + cookieNameSession = "gatus_session" +) + +// Config is the security configuration for Gatus +type Config struct { + Basic *BasicConfig `yaml:"basic,omitempty"` + OIDC *OIDCConfig `yaml:"oidc,omitempty"` + + gate *g8.Gate +} + +// IsValid returns whether the security configuration is valid or not +func (c *Config) IsValid() bool { + return (c.Basic != nil && c.Basic.isValid()) || (c.OIDC != nil && c.OIDC.isValid()) +} + +// RegisterHandlers registers all handlers required based on the security configuration +func (c *Config) RegisterHandlers(router fiber.Router) error { + if c.OIDC != nil { + if err := c.OIDC.initialize(); err != nil { + return err + } + router.All("/oidc/login", c.OIDC.loginHandler) + router.All("/authorization-code/callback", adaptor.HTTPHandlerFunc(c.OIDC.callbackHandler)) + } + return nil +} + +// ApplySecurityMiddleware applies an authentication middleware to the router passed. +// The router passed should be a sub-router in charge of handlers that require authentication. +func (c *Config) ApplySecurityMiddleware(router fiber.Router) error { + if c.OIDC != nil { + // We're going to use g8 for session handling + clientProvider := g8.NewClientProvider(func(token string) *g8.Client { + if _, exists := sessions.Get(token); exists { + return g8.NewClient(token) + } + return nil + }) + customTokenExtractorFunc := func(request *http.Request) string { + sessionCookie, err := request.Cookie(cookieNameSession) + if err != nil { + return "" + } + return sessionCookie.Value + } + // TODO: g8: Add a way to update cookie after? would need the writer + authorizationService := g8.NewAuthorizationService().WithClientProvider(clientProvider) + c.gate = g8.New().WithAuthorizationService(authorizationService).WithCustomTokenExtractor(customTokenExtractorFunc) + router.Use(adaptor.HTTPMiddleware(c.gate.Protect)) + } else if c.Basic != nil { + var decodedBcryptHash []byte + if len(c.Basic.PasswordBcryptHashBase64Encoded) > 0 { + var err error + decodedBcryptHash, err = base64.URLEncoding.DecodeString(c.Basic.PasswordBcryptHashBase64Encoded) + if err != nil { + return err + } + } + router.Use(basicauth.New(basicauth.Config{ + Authorizer: func(username, password string) bool { + if len(c.Basic.PasswordBcryptHashBase64Encoded) > 0 { + if username != c.Basic.Username || bcrypt.CompareHashAndPassword(decodedBcryptHash, []byte(password)) != nil { + return false + } + } + return true + }, + Unauthorized: func(ctx *fiber.Ctx) error { + ctx.Set("WWW-Authenticate", "Basic") + return ctx.Status(401).SendString("Unauthorized") + }, + })) + } + return nil +} + +// IsAuthenticated checks whether the user is authenticated +// If the Config does not warrant authentication, it will always return true. +func (c *Config) IsAuthenticated(ctx *fiber.Ctx) bool { + if c.gate != nil { + // TODO: Update g8 to support fasthttp natively? (see g8's fasthttp branch) + request, err := adaptor.ConvertRequest(ctx, false) + if err != nil { + logr.Errorf("[security.IsAuthenticated] Unexpected error converting request: %v", err) + return false + } + token := c.gate.ExtractTokenFromRequest(request) + _, hasSession := sessions.Get(token) + return hasSession + } + return false +} diff --git a/security/config_test.go b/security/config_test.go new file mode 100644 index 00000000..45fb245b --- /dev/null +++ b/security/config_test.go @@ -0,0 +1,136 @@ +package security + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/gofiber/fiber/v2" + "golang.org/x/oauth2" +) + +func TestConfig_IsValid(t *testing.T) { + c := &Config{ + Basic: nil, + OIDC: nil, + } + if c.IsValid() { + t.Error("expected empty config to be valid") + } +} + +func TestConfig_ApplySecurityMiddleware(t *testing.T) { + /////////// + // BASIC // + /////////// + t.Run("basic", func(t *testing.T) { + // Bcrypt + c := &Config{Basic: &BasicConfig{ + Username: "john.doe", + PasswordBcryptHashBase64Encoded: "JDJhJDA4JDFoRnpPY1hnaFl1OC9ISlFsa21VS09wOGlPU1ZOTDlHZG1qeTFvb3dIckRBUnlHUmNIRWlT", + }} + app := fiber.New() + if err := c.ApplySecurityMiddleware(app); err != nil { + t.Error("expected no error, got", err) + } + app.Get("/test", func(c *fiber.Ctx) error { + return c.SendStatus(200) + }) + // Try to access the route without basic auth + request := httptest.NewRequest("GET", "/test", http.NoBody) + response, err := app.Test(request) + if err != nil { + t.Fatal("expected no error, got", err) + } + if response.StatusCode != 401 { + t.Error("expected code to be 401, but was", response.StatusCode) + } + // Try again, but with basic auth + request = httptest.NewRequest("GET", "/test", http.NoBody) + request.SetBasicAuth("john.doe", "hunter2") + response, err = app.Test(request) + if err != nil { + t.Fatal("expected no error, got", err) + } + if response.StatusCode != 200 { + t.Error("expected code to be 200, but was", response.StatusCode) + } + }) + ////////// + // OIDC // + ////////// + t.Run("oidc", func(t *testing.T) { + c := &Config{OIDC: &OIDCConfig{ + IssuerURL: "https://sso.gatus.io/", + RedirectURL: "http://localhost:80/authorization-code/callback", + Scopes: []string{"openid"}, + AllowedSubjects: []string{"user1@example.com"}, + oauth2Config: oauth2.Config{}, + verifier: nil, + }} + app := fiber.New() + if err := c.ApplySecurityMiddleware(app); err != nil { + t.Error("expected no error, got", err) + } + app.Get("/test", func(c *fiber.Ctx) error { + return c.SendStatus(200) + }) + // Try without any session cookie + request := httptest.NewRequest("GET", "/test", http.NoBody) + response, err := app.Test(request) + if err != nil { + t.Fatal("expected no error, got", err) + } + if response.StatusCode != 401 { + t.Error("expected code to be 401, but was", response.StatusCode) + } + // Try with a session cookie + request = httptest.NewRequest("GET", "/test", http.NoBody) + request.AddCookie(&http.Cookie{Name: "session", Value: "123"}) + response, err = app.Test(request) + if err != nil { + t.Fatal("expected no error, got", err) + } + if response.StatusCode != 401 { + t.Error("expected code to be 401, but was", response.StatusCode) + } + }) +} + +func TestConfig_RegisterHandlers(t *testing.T) { + c := &Config{} + app := fiber.New() + c.RegisterHandlers(app) + // Try to access the OIDC handler. This should fail, because the security config doesn't have OIDC + request := httptest.NewRequest("GET", "/oidc/login", http.NoBody) + response, err := app.Test(request) + if err != nil { + t.Fatal("expected no error, got", err) + } + if response.StatusCode != 404 { + t.Error("expected code to be 404, but was", response.StatusCode) + } + // Set an empty OIDC config. This should fail, because the IssuerURL is required. + c.OIDC = &OIDCConfig{} + if err := c.RegisterHandlers(app); err == nil { + t.Fatal("expected an error, but got none") + } + // Set the OIDC config and try again + c.OIDC = &OIDCConfig{ + IssuerURL: "https://sso.gatus.io/", + RedirectURL: "http://localhost:80/authorization-code/callback", + Scopes: []string{"openid"}, + AllowedSubjects: []string{"user1@example.com"}, + } + if err := c.RegisterHandlers(app); err != nil { + t.Fatal("expected no error, but got", err) + } + request = httptest.NewRequest("GET", "/oidc/login", http.NoBody) + response, err = app.Test(request) + if err != nil { + t.Fatal("expected no error, got", err) + } + if response.StatusCode != 302 { + t.Error("expected code to be 302, but was", response.StatusCode) + } +} diff --git a/security/oidc.go b/security/oidc.go new file mode 100644 index 00000000..6505b8ef --- /dev/null +++ b/security/oidc.go @@ -0,0 +1,142 @@ +package security + +import ( + "context" + "net/http" + "strings" + "time" + + "github.com/TwiN/logr" + "github.com/coreos/go-oidc/v3/oidc" + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" + "golang.org/x/oauth2" +) + +// OIDCConfig is the configuration for OIDC authentication +type OIDCConfig struct { + IssuerURL string `yaml:"issuer-url"` // e.g. https://dev-12345678.okta.com + RedirectURL string `yaml:"redirect-url"` // e.g. http://localhost:8080/authorization-code/callback + ClientID string `yaml:"client-id"` + ClientSecret string `yaml:"client-secret"` + Scopes []string `yaml:"scopes"` // e.g. ["openid"] + AllowedSubjects []string `yaml:"allowed-subjects"` // e.g. ["user1@example.com"]. If empty, all subjects are allowed + + oauth2Config oauth2.Config + verifier *oidc.IDTokenVerifier +} + +// isValid returns whether the basic security configuration is valid or not +func (c *OIDCConfig) isValid() bool { + return len(c.IssuerURL) > 0 && len(c.RedirectURL) > 0 && strings.HasSuffix(c.RedirectURL, "/authorization-code/callback") && len(c.ClientID) > 0 && len(c.ClientSecret) > 0 && len(c.Scopes) > 0 +} + +func (c *OIDCConfig) initialize() error { + provider, err := oidc.NewProvider(context.Background(), c.IssuerURL) + if err != nil { + return err + } + c.verifier = provider.Verifier(&oidc.Config{ClientID: c.ClientID}) + // Configure an OpenID Connect aware OAuth2 client. + c.oauth2Config = oauth2.Config{ + ClientID: c.ClientID, + ClientSecret: c.ClientSecret, + Scopes: c.Scopes, + RedirectURL: c.RedirectURL, + Endpoint: provider.Endpoint(), + } + return nil +} + +func (c *OIDCConfig) loginHandler(ctx *fiber.Ctx) error { + state, nonce := uuid.NewString(), uuid.NewString() + ctx.Cookie(&fiber.Cookie{ + Name: cookieNameState, + Value: state, + Path: "/", + MaxAge: int(time.Hour.Seconds()), + SameSite: "lax", + HTTPOnly: true, + }) + ctx.Cookie(&fiber.Cookie{ + Name: cookieNameNonce, + Value: nonce, + Path: "/", + MaxAge: int(time.Hour.Seconds()), + SameSite: "lax", + HTTPOnly: true, + }) + return ctx.Redirect(c.oauth2Config.AuthCodeURL(state, oidc.Nonce(nonce)), http.StatusFound) +} + +func (c *OIDCConfig) callbackHandler(w http.ResponseWriter, r *http.Request) { // TODO: Migrate to a native fiber handler + // Check if there's an error + if len(r.URL.Query().Get("error")) > 0 { + http.Error(w, r.URL.Query().Get("error")+": "+r.URL.Query().Get("error_description"), http.StatusBadRequest) + return + } + // Ensure that the state has the expected value + state, err := r.Cookie(cookieNameState) + if err != nil { + http.Error(w, "state not found", http.StatusBadRequest) + return + } + if r.URL.Query().Get("state") != state.Value { + http.Error(w, "state did not match", http.StatusBadRequest) + return + } + // Validate token + oauth2Token, err := c.oauth2Config.Exchange(r.Context(), r.URL.Query().Get("code")) + if err != nil { + http.Error(w, "Error exchanging token: "+err.Error(), http.StatusInternalServerError) + return + } + rawIDToken, ok := oauth2Token.Extra("id_token").(string) + if !ok { + http.Error(w, "Missing 'id_token' in oauth2 token", http.StatusInternalServerError) + return + } + idToken, err := c.verifier.Verify(r.Context(), rawIDToken) + if err != nil { + http.Error(w, "Failed to verify id_token: "+err.Error(), http.StatusInternalServerError) + return + } + // Validate nonce + nonce, err := r.Cookie(cookieNameNonce) + if err != nil { + http.Error(w, "nonce not found", http.StatusBadRequest) + return + } + if idToken.Nonce != nonce.Value { + http.Error(w, "nonce did not match", http.StatusBadRequest) + return + } + if len(c.AllowedSubjects) == 0 { + // If there's no allowed subjects, all subjects are allowed. + c.setSessionCookie(w, idToken) + http.Redirect(w, r, "/", http.StatusFound) + return + } + for _, subject := range c.AllowedSubjects { + if strings.ToLower(subject) == strings.ToLower(idToken.Subject) { + c.setSessionCookie(w, idToken) + http.Redirect(w, r, "/", http.StatusFound) + return + } + } + logr.Debugf("[security.callbackHandler] Subject %s is not in the list of allowed subjects", idToken.Subject) + http.Redirect(w, r, "/?error=access_denied", http.StatusFound) +} + +func (c *OIDCConfig) setSessionCookie(w http.ResponseWriter, idToken *oidc.IDToken) { + // At this point, the user has been confirmed. All that's left to do is create a session. + sessionID := uuid.NewString() + sessions.SetWithTTL(sessionID, idToken.Subject, time.Hour) + http.SetCookie(w, &http.Cookie{ + Name: cookieNameSession, + Value: sessionID, + Path: "/", + MaxAge: int(time.Hour.Seconds()), + SameSite: http.SameSiteStrictMode, + }) +} diff --git a/security/oidc_test.go b/security/oidc_test.go new file mode 100644 index 00000000..bd7b7f95 --- /dev/null +++ b/security/oidc_test.go @@ -0,0 +1,70 @@ +package security + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/coreos/go-oidc/v3/oidc" +) + +func TestOIDCConfig_isValid(t *testing.T) { + c := &OIDCConfig{ + IssuerURL: "https://sso.gatus.io/", + RedirectURL: "http://localhost:80/authorization-code/callback", + ClientID: "client-id", + ClientSecret: "client-secret", + Scopes: []string{"openid"}, + AllowedSubjects: []string{"user1@example.com"}, + } + if !c.isValid() { + t.Error("OIDCConfig should be valid") + } +} + +func TestOIDCConfig_callbackHandler(t *testing.T) { + c := &OIDCConfig{ + IssuerURL: "https://sso.gatus.io/", + RedirectURL: "http://localhost:80/authorization-code/callback", + ClientID: "client-id", + ClientSecret: "client-secret", + Scopes: []string{"openid"}, + AllowedSubjects: []string{"user1@example.com"}, + } + if err := c.initialize(); err != nil { + t.Fatal("expected no error, but got", err) + } + // Try with no state cookie + request, _ := http.NewRequest("GET", "/authorization-code/callback", nil) + responseRecorder := httptest.NewRecorder() + c.callbackHandler(responseRecorder, request) + if responseRecorder.Code != http.StatusBadRequest { + t.Error("expected code to be 400, but was", responseRecorder.Code) + } + // Try with state cookie + request, _ = http.NewRequest("GET", "/authorization-code/callback", nil) + request.AddCookie(&http.Cookie{Name: cookieNameState, Value: "fake-state"}) + responseRecorder = httptest.NewRecorder() + c.callbackHandler(responseRecorder, request) + if responseRecorder.Code != http.StatusBadRequest { + t.Error("expected code to be 400, but was", responseRecorder.Code) + } + // Try with state cookie and state query parameter + request, _ = http.NewRequest("GET", "/authorization-code/callback?state=fake-state", nil) + request.AddCookie(&http.Cookie{Name: cookieNameState, Value: "fake-state"}) + responseRecorder = httptest.NewRecorder() + c.callbackHandler(responseRecorder, request) + // Exchange should fail, so 500. + if responseRecorder.Code != http.StatusInternalServerError { + t.Error("expected code to be 500, but was", responseRecorder.Code) + } +} + +func TestOIDCConfig_setSessionCookie(t *testing.T) { + c := &OIDCConfig{} + responseRecorder := httptest.NewRecorder() + c.setSessionCookie(responseRecorder, &oidc.IDToken{Subject: "test@example.com"}) + if len(responseRecorder.Result().Cookies()) == 0 { + t.Error("expected cookie to be set") + } +} diff --git a/security/sessions.go b/security/sessions.go new file mode 100644 index 00000000..853f3c3d --- /dev/null +++ b/security/sessions.go @@ -0,0 +1,5 @@ +package security + +import "github.com/TwiN/gocache/v2" + +var sessions = gocache.NewCache().WithEvictionPolicy(gocache.LeastRecentlyUsed) // TODO: Move this to storage diff --git a/storage/config.go b/storage/config.go new file mode 100644 index 00000000..5ee3539d --- /dev/null +++ b/storage/config.go @@ -0,0 +1,42 @@ +package storage + +import ( + "errors" +) + +var ( + ErrSQLStorageRequiresPath = errors.New("sql storage requires a non-empty path to be defined") + ErrMemoryStorageDoesNotSupportPath = errors.New("memory storage does not support persistence, use sqlite if you want persistence on file") +) + +// Config is the configuration for storage +type Config struct { + // Path is the path used by the store to achieve persistence + // If blank, persistence is disabled. + // Note that not all Type support persistence + Path string `yaml:"path"` + + // Type of store + // If blank, uses the default in-memory store + Type Type `yaml:"type"` + + // Caching is whether to enable caching. + // This is used to drastically decrease read latency by pre-emptively caching writes + // as they happen, also known as the write-through caching strategy. + // Does not apply if Config.Type is not TypePostgres or TypeSQLite. + Caching bool `yaml:"caching,omitempty"` +} + +// ValidateAndSetDefaults validates the configuration and sets the default values (if applicable) +func (c *Config) ValidateAndSetDefaults() error { + if c.Type == "" { + c.Type = TypeMemory + } + if (c.Type == TypePostgres || c.Type == TypeSQLite) && len(c.Path) == 0 { + return ErrSQLStorageRequiresPath + } + if c.Type == TypeMemory && len(c.Path) > 0 { + return ErrMemoryStorageDoesNotSupportPath + } + return nil +} diff --git a/storage/store/common/errors.go b/storage/store/common/errors.go new file mode 100644 index 00000000..77560f93 --- /dev/null +++ b/storage/store/common/errors.go @@ -0,0 +1,8 @@ +package common + +import "errors" + +var ( + ErrEndpointNotFound = errors.New("endpoint not found") // When an endpoint does not exist in the store + ErrInvalidTimeRange = errors.New("'from' cannot be older than 'to'") // When an invalid time range is provided +) diff --git a/storage/store/common/limits.go b/storage/store/common/limits.go new file mode 100644 index 00000000..92007220 --- /dev/null +++ b/storage/store/common/limits.go @@ -0,0 +1,9 @@ +package common + +const ( + // MaximumNumberOfResults is the maximum number of results that an endpoint can have + MaximumNumberOfResults = 100 + + // MaximumNumberOfEvents is the maximum number of events that an endpoint can have + MaximumNumberOfEvents = 50 +) diff --git a/storage/store/common/paging/paging.go b/storage/store/common/paging/paging.go new file mode 100644 index 00000000..0471d98e --- /dev/null +++ b/storage/store/common/paging/paging.go @@ -0,0 +1,28 @@ +package paging + +// EndpointStatusParams represents all parameters that can be used for paging purposes +type EndpointStatusParams struct { + EventsPage int // Number of the event page + EventsPageSize int // Size of the event page + ResultsPage int // Number of the result page + ResultsPageSize int // Size of the result page +} + +// NewEndpointStatusParams creates a new EndpointStatusParams +func NewEndpointStatusParams() *EndpointStatusParams { + return &EndpointStatusParams{} +} + +// WithEvents sets the values for EventsPage and EventsPageSize +func (params *EndpointStatusParams) WithEvents(page, pageSize int) *EndpointStatusParams { + params.EventsPage = page + params.EventsPageSize = pageSize + return params +} + +// WithResults sets the values for ResultsPage and ResultsPageSize +func (params *EndpointStatusParams) WithResults(page, pageSize int) *EndpointStatusParams { + params.ResultsPage = page + params.ResultsPageSize = pageSize + return params +} diff --git a/storage/store/common/paging/paging_test.go b/storage/store/common/paging/paging_test.go new file mode 100644 index 00000000..10675eef --- /dev/null +++ b/storage/store/common/paging/paging_test.go @@ -0,0 +1,72 @@ +package paging + +import "testing" + +func TestNewEndpointStatusParams(t *testing.T) { + type Scenario struct { + Name string + Params *EndpointStatusParams + ExpectedEventsPage int + ExpectedEventsPageSize int + ExpectedResultsPage int + ExpectedResultsPageSize int + } + scenarios := []Scenario{ + { + Name: "empty-params", + Params: NewEndpointStatusParams(), + ExpectedEventsPage: 0, + ExpectedEventsPageSize: 0, + ExpectedResultsPage: 0, + ExpectedResultsPageSize: 0, + }, + { + Name: "with-events-page-2-size-7", + Params: NewEndpointStatusParams().WithEvents(2, 7), + ExpectedEventsPage: 2, + ExpectedEventsPageSize: 7, + ExpectedResultsPage: 0, + ExpectedResultsPageSize: 0, + }, + { + Name: "with-events-page-4-size-3-uptime", + Params: NewEndpointStatusParams().WithEvents(4, 3), + ExpectedEventsPage: 4, + ExpectedEventsPageSize: 3, + ExpectedResultsPage: 0, + ExpectedResultsPageSize: 0, + }, + { + Name: "with-results-page-1-size-20-uptime", + Params: NewEndpointStatusParams().WithResults(1, 20), + ExpectedEventsPage: 0, + ExpectedEventsPageSize: 0, + ExpectedResultsPage: 1, + ExpectedResultsPageSize: 20, + }, + { + Name: "with-results-page-2-size-10-events-page-3-size-50", + Params: NewEndpointStatusParams().WithResults(2, 10).WithEvents(3, 50), + ExpectedEventsPage: 3, + ExpectedEventsPageSize: 50, + ExpectedResultsPage: 2, + ExpectedResultsPageSize: 10, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + if scenario.Params.EventsPage != scenario.ExpectedEventsPage { + t.Errorf("expected ExpectedEventsPage to be %d, was %d", scenario.ExpectedEventsPageSize, scenario.Params.EventsPage) + } + if scenario.Params.EventsPageSize != scenario.ExpectedEventsPageSize { + t.Errorf("expected EventsPageSize to be %d, was %d", scenario.ExpectedEventsPageSize, scenario.Params.EventsPageSize) + } + if scenario.Params.ResultsPage != scenario.ExpectedResultsPage { + t.Errorf("expected ResultsPage to be %d, was %d", scenario.ExpectedResultsPage, scenario.Params.ResultsPage) + } + if scenario.Params.ResultsPageSize != scenario.ExpectedResultsPageSize { + t.Errorf("expected ResultsPageSize to be %d, was %d", scenario.ExpectedResultsPageSize, scenario.Params.ResultsPageSize) + } + }) + } +} diff --git a/storage/store/memory/memory.go b/storage/store/memory/memory.go new file mode 100644 index 00000000..6451381d --- /dev/null +++ b/storage/store/memory/memory.go @@ -0,0 +1,222 @@ +package memory + +import ( + "sort" + "sync" + "time" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/storage/store/common" + "github.com/TwiN/gatus/v5/storage/store/common/paging" + "github.com/TwiN/gocache/v2" +) + +// Store that leverages gocache +type Store struct { + sync.RWMutex + + cache *gocache.Cache +} + +// NewStore creates a new store using gocache.Cache +// +// This store holds everything in memory, and if the file parameter is not blank, +// supports eventual persistence. +func NewStore() (*Store, error) { + store := &Store{ + cache: gocache.NewCache().WithMaxSize(gocache.NoMaxSize), + } + return store, nil +} + +// GetAllEndpointStatuses returns all monitored endpoint.Status +// with a subset of endpoint.Result defined by the page and pageSize parameters +func (s *Store) GetAllEndpointStatuses(params *paging.EndpointStatusParams) ([]*endpoint.Status, error) { + endpointStatuses := s.cache.GetAll() + pagedEndpointStatuses := make([]*endpoint.Status, 0, len(endpointStatuses)) + for _, v := range endpointStatuses { + pagedEndpointStatuses = append(pagedEndpointStatuses, ShallowCopyEndpointStatus(v.(*endpoint.Status), params)) + } + sort.Slice(pagedEndpointStatuses, func(i, j int) bool { + return pagedEndpointStatuses[i].Key < pagedEndpointStatuses[j].Key + }) + return pagedEndpointStatuses, nil +} + +// GetEndpointStatus returns the endpoint status for a given endpoint name in the given group +func (s *Store) GetEndpointStatus(groupName, endpointName string, params *paging.EndpointStatusParams) (*endpoint.Status, error) { + return s.GetEndpointStatusByKey(endpoint.ConvertGroupAndEndpointNameToKey(groupName, endpointName), params) +} + +// GetEndpointStatusByKey returns the endpoint status for a given key +func (s *Store) GetEndpointStatusByKey(key string, params *paging.EndpointStatusParams) (*endpoint.Status, error) { + endpointStatus := s.cache.GetValue(key) + if endpointStatus == nil { + return nil, common.ErrEndpointNotFound + } + return ShallowCopyEndpointStatus(endpointStatus.(*endpoint.Status), params), nil +} + +// GetUptimeByKey returns the uptime percentage during a time range +func (s *Store) GetUptimeByKey(key string, from, to time.Time) (float64, error) { + if from.After(to) { + return 0, common.ErrInvalidTimeRange + } + endpointStatus := s.cache.GetValue(key) + if endpointStatus == nil || endpointStatus.(*endpoint.Status).Uptime == nil { + return 0, common.ErrEndpointNotFound + } + successfulExecutions := uint64(0) + totalExecutions := uint64(0) + current := from + for to.Sub(current) >= 0 { + hourlyUnixTimestamp := current.Truncate(time.Hour).Unix() + hourlyStats := endpointStatus.(*endpoint.Status).Uptime.HourlyStatistics[hourlyUnixTimestamp] + if hourlyStats == nil || hourlyStats.TotalExecutions == 0 { + current = current.Add(time.Hour) + continue + } + successfulExecutions += hourlyStats.SuccessfulExecutions + totalExecutions += hourlyStats.TotalExecutions + current = current.Add(time.Hour) + } + if totalExecutions == 0 { + return 0, nil + } + return float64(successfulExecutions) / float64(totalExecutions), nil +} + +// GetAverageResponseTimeByKey returns the average response time in milliseconds (value) during a time range +func (s *Store) GetAverageResponseTimeByKey(key string, from, to time.Time) (int, error) { + if from.After(to) { + return 0, common.ErrInvalidTimeRange + } + endpointStatus := s.cache.GetValue(key) + if endpointStatus == nil || endpointStatus.(*endpoint.Status).Uptime == nil { + return 0, common.ErrEndpointNotFound + } + current := from + var totalExecutions, totalResponseTime uint64 + for to.Sub(current) >= 0 { + hourlyUnixTimestamp := current.Truncate(time.Hour).Unix() + hourlyStats := endpointStatus.(*endpoint.Status).Uptime.HourlyStatistics[hourlyUnixTimestamp] + if hourlyStats == nil || hourlyStats.TotalExecutions == 0 { + current = current.Add(time.Hour) + continue + } + totalExecutions += hourlyStats.TotalExecutions + totalResponseTime += hourlyStats.TotalExecutionsResponseTime + current = current.Add(time.Hour) + } + if totalExecutions == 0 { + return 0, nil + } + return int(float64(totalResponseTime) / float64(totalExecutions)), nil +} + +// GetHourlyAverageResponseTimeByKey returns a map of hourly (key) average response time in milliseconds (value) during a time range +func (s *Store) GetHourlyAverageResponseTimeByKey(key string, from, to time.Time) (map[int64]int, error) { + if from.After(to) { + return nil, common.ErrInvalidTimeRange + } + endpointStatus := s.cache.GetValue(key) + if endpointStatus == nil || endpointStatus.(*endpoint.Status).Uptime == nil { + return nil, common.ErrEndpointNotFound + } + hourlyAverageResponseTimes := make(map[int64]int) + current := from + for to.Sub(current) >= 0 { + hourlyUnixTimestamp := current.Truncate(time.Hour).Unix() + hourlyStats := endpointStatus.(*endpoint.Status).Uptime.HourlyStatistics[hourlyUnixTimestamp] + if hourlyStats == nil || hourlyStats.TotalExecutions == 0 { + current = current.Add(time.Hour) + continue + } + hourlyAverageResponseTimes[hourlyUnixTimestamp] = int(float64(hourlyStats.TotalExecutionsResponseTime) / float64(hourlyStats.TotalExecutions)) + current = current.Add(time.Hour) + } + return hourlyAverageResponseTimes, nil +} + +// Insert adds the observed result for the specified endpoint into the store +func (s *Store) Insert(ep *endpoint.Endpoint, result *endpoint.Result) error { + key := ep.Key() + s.Lock() + status, exists := s.cache.Get(key) + if !exists { + status = endpoint.NewStatus(ep.Group, ep.Name) + status.(*endpoint.Status).Events = append(status.(*endpoint.Status).Events, &endpoint.Event{ + Type: endpoint.EventStart, + Timestamp: time.Now(), + }) + } + AddResult(status.(*endpoint.Status), result) + s.cache.Set(key, status) + s.Unlock() + return nil +} + +// DeleteAllEndpointStatusesNotInKeys removes all Status that are not within the keys provided +func (s *Store) DeleteAllEndpointStatusesNotInKeys(keys []string) int { + var keysToDelete []string + for _, existingKey := range s.cache.GetKeysByPattern("*", 0) { + shouldDelete := true + for _, key := range keys { + if existingKey == key { + shouldDelete = false + break + } + } + if shouldDelete { + keysToDelete = append(keysToDelete, existingKey) + } + } + return s.cache.DeleteAll(keysToDelete) +} + +// GetTriggeredEndpointAlert returns whether the triggered alert for the specified endpoint as well as the necessary information to resolve it +// +// Always returns that the alert does not exist for the in-memory store since it does not support persistence across restarts +func (s *Store) GetTriggeredEndpointAlert(ep *endpoint.Endpoint, alert *alert.Alert) (exists bool, resolveKey string, numberOfSuccessesInARow int, err error) { + return false, "", 0, nil +} + +// UpsertTriggeredEndpointAlert inserts/updates a triggered alert for an endpoint +// Used for persistence of triggered alerts across application restarts +// +// Does nothing for the in-memory store since it does not support persistence across restarts +func (s *Store) UpsertTriggeredEndpointAlert(ep *endpoint.Endpoint, triggeredAlert *alert.Alert) error { + return nil +} + +// DeleteTriggeredEndpointAlert deletes a triggered alert for an endpoint +// +// Does nothing for the in-memory store since it does not support persistence across restarts +func (s *Store) DeleteTriggeredEndpointAlert(ep *endpoint.Endpoint, triggeredAlert *alert.Alert) error { + return nil +} + +// DeleteAllTriggeredAlertsNotInChecksumsByEndpoint removes all triggered alerts owned by an endpoint whose alert +// configurations are not provided in the checksums list. +// This prevents triggered alerts that have been removed or modified from lingering in the database. +// +// Does nothing for the in-memory store since it does not support persistence across restarts +func (s *Store) DeleteAllTriggeredAlertsNotInChecksumsByEndpoint(ep *endpoint.Endpoint, checksums []string) int { + return 0 +} + +// Clear deletes everything from the store +func (s *Store) Clear() { + s.cache.Clear() +} + +// Save persists the cache to the store file +func (s *Store) Save() error { + return nil +} + +// Close does nothing, because there's nothing to close +func (s *Store) Close() { + return +} diff --git a/storage/store/memory/memory_test.go b/storage/store/memory/memory_test.go new file mode 100644 index 00000000..5d489677 --- /dev/null +++ b/storage/store/memory/memory_test.go @@ -0,0 +1,135 @@ +package memory + +import ( + "testing" + "time" + + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/storage/store/common/paging" +) + +var ( + firstCondition = endpoint.Condition("[STATUS] == 200") + secondCondition = endpoint.Condition("[RESPONSE_TIME] < 500") + thirdCondition = endpoint.Condition("[CERTIFICATE_EXPIRATION] < 72h") + + now = time.Now() + + testEndpoint = endpoint.Endpoint{ + Name: "name", + Group: "group", + URL: "https://example.org/what/ever", + Method: "GET", + Body: "body", + Interval: 30 * time.Second, + Conditions: []endpoint.Condition{firstCondition, secondCondition, thirdCondition}, + Alerts: nil, + NumberOfFailuresInARow: 0, + NumberOfSuccessesInARow: 0, + } + testSuccessfulResult = endpoint.Result{ + Hostname: "example.org", + IP: "127.0.0.1", + HTTPStatus: 200, + Errors: nil, + Connected: true, + Success: true, + Timestamp: now, + Duration: 150 * time.Millisecond, + CertificateExpiration: 10 * time.Hour, + ConditionResults: []*endpoint.ConditionResult{ + { + Condition: "[STATUS] == 200", + Success: true, + }, + { + Condition: "[RESPONSE_TIME] < 500", + Success: true, + }, + { + Condition: "[CERTIFICATE_EXPIRATION] < 72h", + Success: true, + }, + }, + } + testUnsuccessfulResult = endpoint.Result{ + Hostname: "example.org", + IP: "127.0.0.1", + HTTPStatus: 200, + Errors: []string{"error-1", "error-2"}, + Connected: true, + Success: false, + Timestamp: now, + Duration: 750 * time.Millisecond, + CertificateExpiration: 10 * time.Hour, + ConditionResults: []*endpoint.ConditionResult{ + { + Condition: "[STATUS] == 200", + Success: true, + }, + { + Condition: "[RESPONSE_TIME] < 500", + Success: false, + }, + { + Condition: "[CERTIFICATE_EXPIRATION] < 72h", + Success: false, + }, + }, + } +) + +// Note that are much more extensive tests in /storage/store/store_test.go. +// This test is simply an extra sanity check +func TestStore_SanityCheck(t *testing.T) { + store, _ := NewStore() + defer store.Close() + store.Insert(&testEndpoint, &testSuccessfulResult) + endpointStatuses, _ := store.GetAllEndpointStatuses(paging.NewEndpointStatusParams()) + if numberOfEndpointStatuses := len(endpointStatuses); numberOfEndpointStatuses != 1 { + t.Fatalf("expected 1 EndpointStatus, got %d", numberOfEndpointStatuses) + } + store.Insert(&testEndpoint, &testUnsuccessfulResult) + // Both results inserted are for the same endpoint, therefore, the count shouldn't have increased + endpointStatuses, _ = store.GetAllEndpointStatuses(paging.NewEndpointStatusParams()) + if numberOfEndpointStatuses := len(endpointStatuses); numberOfEndpointStatuses != 1 { + t.Fatalf("expected 1 EndpointStatus, got %d", numberOfEndpointStatuses) + } + if hourlyAverageResponseTime, err := store.GetHourlyAverageResponseTimeByKey(testEndpoint.Key(), time.Now().Add(-24*time.Hour), time.Now()); err != nil { + t.Errorf("expected no error, got %v", err) + } else if len(hourlyAverageResponseTime) != 1 { + t.Errorf("expected 1 hour to have had a result in the past 24 hours, got %d", len(hourlyAverageResponseTime)) + } + if uptime, _ := store.GetUptimeByKey(testEndpoint.Key(), time.Now().Add(-24*time.Hour), time.Now()); uptime != 0.5 { + t.Errorf("expected uptime of last 24h to be 0.5, got %f", uptime) + } + if averageResponseTime, _ := store.GetAverageResponseTimeByKey(testEndpoint.Key(), time.Now().Add(-24*time.Hour), time.Now()); averageResponseTime != 450 { + t.Errorf("expected average response time of last 24h to be 450, got %d", averageResponseTime) + } + ss, _ := store.GetEndpointStatus(testEndpoint.Group, testEndpoint.Name, paging.NewEndpointStatusParams().WithResults(1, 20).WithEvents(1, 20)) + if ss == nil { + t.Fatalf("Store should've had key '%s', but didn't", testEndpoint.Key()) + } + if len(ss.Events) != 3 { + t.Errorf("Endpoint '%s' should've had 3 events, got %d", ss.Name, len(ss.Events)) + } + if len(ss.Results) != 2 { + t.Errorf("Endpoint '%s' should've had 2 results, got %d", ss.Name, len(ss.Results)) + } + if deleted := store.DeleteAllEndpointStatusesNotInKeys([]string{}); deleted != 1 { + t.Errorf("%d entries should've been deleted, got %d", 1, deleted) + } +} + +func TestStore_Save(t *testing.T) { + store, err := NewStore() + if err != nil { + t.Fatal("expected no error, got", err.Error()) + } + err = store.Save() + if err != nil { + t.Fatal("expected no error, got", err.Error()) + } + store.Clear() + store.Close() +} diff --git a/storage/store/memory/uptime.go b/storage/store/memory/uptime.go new file mode 100644 index 00000000..93322359 --- /dev/null +++ b/storage/store/memory/uptime.go @@ -0,0 +1,43 @@ +package memory + +import ( + "time" + + "github.com/TwiN/gatus/v5/config/endpoint" +) + +const ( + uptimeCleanUpThreshold = 32 * 24 + uptimeRetention = 30 * 24 * time.Hour +) + +// processUptimeAfterResult processes the result by extracting the relevant from the result and recalculating the uptime +// if necessary +func processUptimeAfterResult(uptime *endpoint.Uptime, result *endpoint.Result) { + if uptime.HourlyStatistics == nil { + uptime.HourlyStatistics = make(map[int64]*endpoint.HourlyUptimeStatistics) + } + unixTimestampFlooredAtHour := result.Timestamp.Truncate(time.Hour).Unix() + hourlyStats, _ := uptime.HourlyStatistics[unixTimestampFlooredAtHour] + if hourlyStats == nil { + hourlyStats = &endpoint.HourlyUptimeStatistics{} + uptime.HourlyStatistics[unixTimestampFlooredAtHour] = hourlyStats + } + if result.Success { + hourlyStats.SuccessfulExecutions++ + } + hourlyStats.TotalExecutions++ + hourlyStats.TotalExecutionsResponseTime += uint64(result.Duration.Milliseconds()) + // Clean up only when we're starting to have too many useless keys + // Note that this is only triggered when there are more entries than there should be after + // 32 days, despite the fact that we are deleting everything that's older than 30 days. + // This is to prevent re-iterating on every `processUptimeAfterResult` as soon as the uptime has been logged for 30 days. + if len(uptime.HourlyStatistics) > uptimeCleanUpThreshold { + sevenDaysAgo := time.Now().Add(-(uptimeRetention + time.Hour)).Unix() + for hourlyUnixTimestamp := range uptime.HourlyStatistics { + if sevenDaysAgo > hourlyUnixTimestamp { + delete(uptime.HourlyStatistics, hourlyUnixTimestamp) + } + } + } +} diff --git a/storage/store/memory/uptime_bench_test.go b/storage/store/memory/uptime_bench_test.go new file mode 100644 index 00000000..f055339a --- /dev/null +++ b/storage/store/memory/uptime_bench_test.go @@ -0,0 +1,26 @@ +package memory + +import ( + "testing" + "time" + + "github.com/TwiN/gatus/v5/config/endpoint" +) + +func BenchmarkProcessUptimeAfterResult(b *testing.B) { + uptime := endpoint.NewUptime() + now := time.Now() + now = time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, now.Location()) + // Start 12000 days ago + timestamp := now.Add(-12000 * 24 * time.Hour) + for n := 0; n < b.N; n++ { + processUptimeAfterResult(uptime, &endpoint.Result{ + Duration: 18 * time.Millisecond, + Success: n%15 == 0, + Timestamp: timestamp, + }) + // Simulate an endpoint with an interval of 3 minutes + timestamp = timestamp.Add(3 * time.Minute) + } + b.ReportAllocs() +} diff --git a/storage/store/memory/uptime_test.go b/storage/store/memory/uptime_test.go new file mode 100644 index 00000000..3d9f36c1 --- /dev/null +++ b/storage/store/memory/uptime_test.go @@ -0,0 +1,72 @@ +package memory + +import ( + "testing" + "time" + + "github.com/TwiN/gatus/v5/config/endpoint" +) + +func TestProcessUptimeAfterResult(t *testing.T) { + ep := &endpoint.Endpoint{Name: "name", Group: "group"} + status := endpoint.NewStatus(ep.Group, ep.Name) + uptime := status.Uptime + + now := time.Now() + now = time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, now.Location()) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-7 * 24 * time.Hour), Success: true}) + + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-6 * 24 * time.Hour), Success: false}) + + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-8 * 24 * time.Hour), Success: true}) + + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-24 * time.Hour), Success: true}) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-12 * time.Hour), Success: true}) + + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-1 * time.Hour), Success: true, Duration: 10 * time.Millisecond}) + checkHourlyStatistics(t, uptime.HourlyStatistics[now.Unix()-now.Unix()%3600-3600], 10, 1, 1) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-30 * time.Minute), Success: false, Duration: 500 * time.Millisecond}) + checkHourlyStatistics(t, uptime.HourlyStatistics[now.Unix()-now.Unix()%3600-3600], 510, 2, 1) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-15 * time.Minute), Success: false, Duration: 25 * time.Millisecond}) + checkHourlyStatistics(t, uptime.HourlyStatistics[now.Unix()-now.Unix()%3600-3600], 535, 3, 1) + + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-10 * time.Minute), Success: false}) + + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-120 * time.Hour), Success: true}) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-119 * time.Hour), Success: true}) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-118 * time.Hour), Success: true}) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-117 * time.Hour), Success: true}) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-10 * time.Hour), Success: true}) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-8 * time.Hour), Success: true}) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-30 * time.Minute), Success: true}) + processUptimeAfterResult(uptime, &endpoint.Result{Timestamp: now.Add(-25 * time.Minute), Success: true}) +} + +func TestAddResultUptimeIsCleaningUpAfterItself(t *testing.T) { + ep := &endpoint.Endpoint{Name: "name", Group: "group"} + status := endpoint.NewStatus(ep.Group, ep.Name) + now := time.Now() + now = time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, now.Location()) + // Start 12 days ago + timestamp := now.Add(-12 * 24 * time.Hour) + for timestamp.Unix() <= now.Unix() { + AddResult(status, &endpoint.Result{Timestamp: timestamp, Success: true}) + if len(status.Uptime.HourlyStatistics) > uptimeCleanUpThreshold { + t.Errorf("At no point in time should there be more than %d entries in status.SuccessfulExecutionsPerHour, but there are %d", uptimeCleanUpThreshold, len(status.Uptime.HourlyStatistics)) + } + // Simulate endpoint with an interval of 3 minutes + timestamp = timestamp.Add(3 * time.Minute) + } +} + +func checkHourlyStatistics(t *testing.T, hourlyUptimeStatistics *endpoint.HourlyUptimeStatistics, expectedTotalExecutionsResponseTime uint64, expectedTotalExecutions uint64, expectedSuccessfulExecutions uint64) { + if hourlyUptimeStatistics.TotalExecutionsResponseTime != expectedTotalExecutionsResponseTime { + t.Error("TotalExecutionsResponseTime should've been", expectedTotalExecutionsResponseTime, "got", hourlyUptimeStatistics.TotalExecutionsResponseTime) + } + if hourlyUptimeStatistics.TotalExecutions != expectedTotalExecutions { + t.Error("TotalExecutions should've been", expectedTotalExecutions, "got", hourlyUptimeStatistics.TotalExecutions) + } + if hourlyUptimeStatistics.SuccessfulExecutions != expectedSuccessfulExecutions { + t.Error("SuccessfulExecutions should've been", expectedSuccessfulExecutions, "got", hourlyUptimeStatistics.SuccessfulExecutions) + } +} diff --git a/storage/store/memory/util.go b/storage/store/memory/util.go new file mode 100644 index 00000000..961f740a --- /dev/null +++ b/storage/store/memory/util.go @@ -0,0 +1,81 @@ +package memory + +import ( + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/storage/store/common" + "github.com/TwiN/gatus/v5/storage/store/common/paging" +) + +// ShallowCopyEndpointStatus returns a shallow copy of a Status with only the results +// within the range defined by the page and pageSize parameters +func ShallowCopyEndpointStatus(ss *endpoint.Status, params *paging.EndpointStatusParams) *endpoint.Status { + shallowCopy := &endpoint.Status{ + Name: ss.Name, + Group: ss.Group, + Key: ss.Key, + Uptime: endpoint.NewUptime(), + } + numberOfResults := len(ss.Results) + resultsStart, resultsEnd := getStartAndEndIndex(numberOfResults, params.ResultsPage, params.ResultsPageSize) + if resultsStart < 0 || resultsEnd < 0 { + shallowCopy.Results = []*endpoint.Result{} + } else { + shallowCopy.Results = ss.Results[resultsStart:resultsEnd] + } + numberOfEvents := len(ss.Events) + eventsStart, eventsEnd := getStartAndEndIndex(numberOfEvents, params.EventsPage, params.EventsPageSize) + if eventsStart < 0 || eventsEnd < 0 { + shallowCopy.Events = []*endpoint.Event{} + } else { + shallowCopy.Events = ss.Events[eventsStart:eventsEnd] + } + return shallowCopy +} + +func getStartAndEndIndex(numberOfResults int, page, pageSize int) (int, int) { + if page < 1 || pageSize < 0 { + return -1, -1 + } + start := numberOfResults - (page * pageSize) + end := numberOfResults - ((page - 1) * pageSize) + if start > numberOfResults { + start = -1 + } else if start < 0 { + start = 0 + } + if end > numberOfResults { + end = numberOfResults + } + return start, end +} + +// AddResult adds a Result to Status.Results and makes sure that there are +// no more than MaximumNumberOfResults results in the Results slice +func AddResult(ss *endpoint.Status, result *endpoint.Result) { + if ss == nil { + return + } + if len(ss.Results) > 0 { + // Check if there's any change since the last result + if ss.Results[len(ss.Results)-1].Success != result.Success { + ss.Events = append(ss.Events, endpoint.NewEventFromResult(result)) + if len(ss.Events) > common.MaximumNumberOfEvents { + // Doing ss.Events[1:] would usually be sufficient, but in the case where for some reason, the slice has + // more than one extra element, we can get rid of all of them at once and thus returning the slice to a + // length of MaximumNumberOfEvents by using ss.Events[len(ss.Events)-MaximumNumberOfEvents:] instead + ss.Events = ss.Events[len(ss.Events)-common.MaximumNumberOfEvents:] + } + } + } else { + // This is the first result, so we need to add the first healthy/unhealthy event + ss.Events = append(ss.Events, endpoint.NewEventFromResult(result)) + } + ss.Results = append(ss.Results, result) + if len(ss.Results) > common.MaximumNumberOfResults { + // Doing ss.Results[1:] would usually be sufficient, but in the case where for some reason, the slice has more + // than one extra element, we can get rid of all of them at once and thus returning the slice to a length of + // MaximumNumberOfResults by using ss.Results[len(ss.Results)-MaximumNumberOfResults:] instead + ss.Results = ss.Results[len(ss.Results)-common.MaximumNumberOfResults:] + } + processUptimeAfterResult(ss.Uptime, result) +} diff --git a/storage/store/memory/util_bench_test.go b/storage/store/memory/util_bench_test.go new file mode 100644 index 00000000..36252829 --- /dev/null +++ b/storage/store/memory/util_bench_test.go @@ -0,0 +1,21 @@ +package memory + +import ( + "testing" + + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/storage/store/common" + "github.com/TwiN/gatus/v5/storage/store/common/paging" +) + +func BenchmarkShallowCopyEndpointStatus(b *testing.B) { + ep := &testEndpoint + status := endpoint.NewStatus(ep.Group, ep.Name) + for i := 0; i < common.MaximumNumberOfResults; i++ { + AddResult(status, &testSuccessfulResult) + } + for n := 0; n < b.N; n++ { + ShallowCopyEndpointStatus(status, paging.NewEndpointStatusParams().WithResults(1, 20)) + } + b.ReportAllocs() +} diff --git a/storage/store/memory/util_test.go b/storage/store/memory/util_test.go new file mode 100644 index 00000000..1de445e7 --- /dev/null +++ b/storage/store/memory/util_test.go @@ -0,0 +1,66 @@ +package memory + +import ( + "testing" + "time" + + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/storage/store/common" + "github.com/TwiN/gatus/v5/storage/store/common/paging" +) + +func TestAddResult(t *testing.T) { + ep := &endpoint.Endpoint{Name: "name", Group: "group"} + endpointStatus := endpoint.NewStatus(ep.Group, ep.Name) + for i := 0; i < (common.MaximumNumberOfResults+common.MaximumNumberOfEvents)*2; i++ { + AddResult(endpointStatus, &endpoint.Result{Success: i%2 == 0, Timestamp: time.Now()}) + } + if len(endpointStatus.Results) != common.MaximumNumberOfResults { + t.Errorf("expected endpointStatus.Results to not exceed a length of %d", common.MaximumNumberOfResults) + } + if len(endpointStatus.Events) != common.MaximumNumberOfEvents { + t.Errorf("expected endpointStatus.Events to not exceed a length of %d", common.MaximumNumberOfEvents) + } + // Try to add nil endpointStatus + AddResult(nil, &endpoint.Result{Timestamp: time.Now()}) +} + +func TestShallowCopyEndpointStatus(t *testing.T) { + ep := &endpoint.Endpoint{Name: "name", Group: "group"} + endpointStatus := endpoint.NewStatus(ep.Group, ep.Name) + ts := time.Now().Add(-25 * time.Hour) + for i := 0; i < 25; i++ { + AddResult(endpointStatus, &endpoint.Result{Success: i%2 == 0, Timestamp: ts}) + ts = ts.Add(time.Hour) + } + if len(ShallowCopyEndpointStatus(endpointStatus, paging.NewEndpointStatusParams().WithResults(-1, -1)).Results) != 0 { + t.Error("expected to have 0 result") + } + if len(ShallowCopyEndpointStatus(endpointStatus, paging.NewEndpointStatusParams().WithResults(1, 1)).Results) != 1 { + t.Error("expected to have 1 result") + } + if len(ShallowCopyEndpointStatus(endpointStatus, paging.NewEndpointStatusParams().WithResults(5, 0)).Results) != 0 { + t.Error("expected to have 0 results") + } + if len(ShallowCopyEndpointStatus(endpointStatus, paging.NewEndpointStatusParams().WithResults(-1, 20)).Results) != 0 { + t.Error("expected to have 0 result, because the page was invalid") + } + if len(ShallowCopyEndpointStatus(endpointStatus, paging.NewEndpointStatusParams().WithResults(1, -1)).Results) != 0 { + t.Error("expected to have 0 result, because the page size was invalid") + } + if len(ShallowCopyEndpointStatus(endpointStatus, paging.NewEndpointStatusParams().WithResults(1, 10)).Results) != 10 { + t.Error("expected to have 10 results, because given a page size of 10, page 1 should have 10 elements") + } + if len(ShallowCopyEndpointStatus(endpointStatus, paging.NewEndpointStatusParams().WithResults(2, 10)).Results) != 10 { + t.Error("expected to have 10 results, because given a page size of 10, page 2 should have 10 elements") + } + if len(ShallowCopyEndpointStatus(endpointStatus, paging.NewEndpointStatusParams().WithResults(3, 10)).Results) != 5 { + t.Error("expected to have 5 results, because given a page size of 10, page 3 should have 5 elements") + } + if len(ShallowCopyEndpointStatus(endpointStatus, paging.NewEndpointStatusParams().WithResults(4, 10)).Results) != 0 { + t.Error("expected to have 0 results, because given a page size of 10, page 4 should have 0 elements") + } + if len(ShallowCopyEndpointStatus(endpointStatus, paging.NewEndpointStatusParams().WithResults(1, 50)).Results) != 25 { + t.Error("expected to have 25 results, because there's only 25 results") + } +} diff --git a/storage/store/sql/specific_postgres.go b/storage/store/sql/specific_postgres.go new file mode 100644 index 00000000..aa8b5d5f --- /dev/null +++ b/storage/store/sql/specific_postgres.go @@ -0,0 +1,85 @@ +package sql + +func (s *Store) createPostgresSchema() error { + _, err := s.db.Exec(` + CREATE TABLE IF NOT EXISTS endpoints ( + endpoint_id BIGSERIAL PRIMARY KEY, + endpoint_key TEXT UNIQUE, + endpoint_name TEXT NOT NULL, + endpoint_group TEXT NOT NULL, + UNIQUE(endpoint_name, endpoint_group) + ) + `) + if err != nil { + return err + } + _, err = s.db.Exec(` + CREATE TABLE IF NOT EXISTS endpoint_events ( + endpoint_event_id BIGSERIAL PRIMARY KEY, + endpoint_id BIGINT NOT NULL REFERENCES endpoints(endpoint_id) ON DELETE CASCADE, + event_type TEXT NOT NULL, + event_timestamp TIMESTAMP NOT NULL + ) + `) + if err != nil { + return err + } + _, err = s.db.Exec(` + CREATE TABLE IF NOT EXISTS endpoint_results ( + endpoint_result_id BIGSERIAL PRIMARY KEY, + endpoint_id BIGINT NOT NULL REFERENCES endpoints(endpoint_id) ON DELETE CASCADE, + success BOOLEAN NOT NULL, + errors TEXT NOT NULL, + connected BOOLEAN NOT NULL, + status BIGINT NOT NULL, + dns_rcode TEXT NOT NULL, + certificate_expiration BIGINT NOT NULL, + domain_expiration BIGINT NOT NULL, + hostname TEXT NOT NULL, + ip TEXT NOT NULL, + duration BIGINT NOT NULL, + timestamp TIMESTAMP NOT NULL + ) + `) + if err != nil { + return err + } + _, err = s.db.Exec(` + CREATE TABLE IF NOT EXISTS endpoint_result_conditions ( + endpoint_result_condition_id BIGSERIAL PRIMARY KEY, + endpoint_result_id BIGINT NOT NULL REFERENCES endpoint_results(endpoint_result_id) ON DELETE CASCADE, + condition TEXT NOT NULL, + success BOOLEAN NOT NULL + ) + `) + if err != nil { + return err + } + _, err = s.db.Exec(` + CREATE TABLE IF NOT EXISTS endpoint_uptimes ( + endpoint_uptime_id BIGSERIAL PRIMARY KEY, + endpoint_id BIGINT NOT NULL REFERENCES endpoints(endpoint_id) ON DELETE CASCADE, + hour_unix_timestamp BIGINT NOT NULL, + total_executions BIGINT NOT NULL, + successful_executions BIGINT NOT NULL, + total_response_time BIGINT NOT NULL, + UNIQUE(endpoint_id, hour_unix_timestamp) + ) + `) + if err != nil { + return err + } + _, err = s.db.Exec(` + CREATE TABLE IF NOT EXISTS endpoint_alerts_triggered ( + endpoint_alert_trigger_id BIGSERIAL PRIMARY KEY, + endpoint_id BIGINT NOT NULL REFERENCES endpoints(endpoint_id) ON DELETE CASCADE, + configuration_checksum TEXT NOT NULL, + resolve_key TEXT NOT NULL, + number_of_successes_in_a_row INTEGER NOT NULL, + UNIQUE(endpoint_id, configuration_checksum) + ) + `) + // Silent table modifications TODO: Remove this in v6.0.0 + _, _ = s.db.Exec(`ALTER TABLE endpoint_results ADD IF NOT EXISTS domain_expiration BIGINT NOT NULL DEFAULT 0`) + return err +} diff --git a/storage/store/sql/specific_sqlite.go b/storage/store/sql/specific_sqlite.go new file mode 100644 index 00000000..0fe7fa54 --- /dev/null +++ b/storage/store/sql/specific_sqlite.go @@ -0,0 +1,85 @@ +package sql + +func (s *Store) createSQLiteSchema() error { + _, err := s.db.Exec(` + CREATE TABLE IF NOT EXISTS endpoints ( + endpoint_id INTEGER PRIMARY KEY, + endpoint_key TEXT UNIQUE, + endpoint_name TEXT NOT NULL, + endpoint_group TEXT NOT NULL, + UNIQUE(endpoint_name, endpoint_group) + ) + `) + if err != nil { + return err + } + _, err = s.db.Exec(` + CREATE TABLE IF NOT EXISTS endpoint_events ( + endpoint_event_id INTEGER PRIMARY KEY, + endpoint_id INTEGER NOT NULL REFERENCES endpoints(endpoint_id) ON DELETE CASCADE, + event_type TEXT NOT NULL, + event_timestamp TIMESTAMP NOT NULL + ) + `) + if err != nil { + return err + } + _, err = s.db.Exec(` + CREATE TABLE IF NOT EXISTS endpoint_results ( + endpoint_result_id INTEGER PRIMARY KEY, + endpoint_id INTEGER NOT NULL REFERENCES endpoints(endpoint_id) ON DELETE CASCADE, + success INTEGER NOT NULL, + errors TEXT NOT NULL, + connected INTEGER NOT NULL, + status INTEGER NOT NULL, + dns_rcode TEXT NOT NULL, + certificate_expiration INTEGER NOT NULL, + domain_expiration INTEGER NOT NULL, + hostname TEXT NOT NULL, + ip TEXT NOT NULL, + duration INTEGER NOT NULL, + timestamp TIMESTAMP NOT NULL + ) + `) + if err != nil { + return err + } + _, err = s.db.Exec(` + CREATE TABLE IF NOT EXISTS endpoint_result_conditions ( + endpoint_result_condition_id INTEGER PRIMARY KEY, + endpoint_result_id INTEGER NOT NULL REFERENCES endpoint_results(endpoint_result_id) ON DELETE CASCADE, + condition TEXT NOT NULL, + success INTEGER NOT NULL + ) + `) + if err != nil { + return err + } + _, err = s.db.Exec(` + CREATE TABLE IF NOT EXISTS endpoint_uptimes ( + endpoint_uptime_id INTEGER PRIMARY KEY, + endpoint_id INTEGER NOT NULL REFERENCES endpoints(endpoint_id) ON DELETE CASCADE, + hour_unix_timestamp INTEGER NOT NULL, + total_executions INTEGER NOT NULL, + successful_executions INTEGER NOT NULL, + total_response_time INTEGER NOT NULL, + UNIQUE(endpoint_id, hour_unix_timestamp) + ) + `) + if err != nil { + return err + } + _, err = s.db.Exec(` + CREATE TABLE IF NOT EXISTS endpoint_alerts_triggered ( + endpoint_alert_trigger_id INTEGER PRIMARY KEY, + endpoint_id INTEGER NOT NULL REFERENCES endpoints(endpoint_id) ON DELETE CASCADE, + configuration_checksum TEXT NOT NULL, + resolve_key TEXT NOT NULL, + number_of_successes_in_a_row INTEGER NOT NULL, + UNIQUE(endpoint_id, configuration_checksum) + ) + `) + // Silent table modifications TODO: Remove this in v6.0.0 + _, _ = s.db.Exec(`ALTER TABLE endpoint_results ADD domain_expiration INTEGER NOT NULL DEFAULT 0`) + return err +} diff --git a/storage/store/sql/sql.go b/storage/store/sql/sql.go new file mode 100644 index 00000000..30b7906f --- /dev/null +++ b/storage/store/sql/sql.go @@ -0,0 +1,1084 @@ +package sql + +import ( + "database/sql" + "errors" + "fmt" + "strconv" + "strings" + "time" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/storage/store/common" + "github.com/TwiN/gatus/v5/storage/store/common/paging" + "github.com/TwiN/gocache/v2" + "github.com/TwiN/logr" + _ "github.com/lib/pq" + _ "modernc.org/sqlite" +) + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Note that only exported functions in this file may create, commit, or rollback a transaction // +////////////////////////////////////////////////////////////////////////////////////////////////// + +const ( + // arraySeparator is the separator used to separate multiple strings in a single column. + // It's a dirty hack, but it's only used for persisting errors, and since this data will likely only ever be used + // for aesthetic purposes, I deemed it wasn't worth the performance impact of yet another one-to-many table. + arraySeparator = "|~|" + + eventsCleanUpThreshold = common.MaximumNumberOfEvents + 10 // Maximum number of events before triggering a cleanup + resultsCleanUpThreshold = common.MaximumNumberOfResults + 10 // Maximum number of results before triggering a cleanup + + uptimeTotalEntriesMergeThreshold = 100 // Maximum number of uptime entries before triggering a merge + uptimeAgeCleanUpThreshold = 32 * 24 * time.Hour // Maximum uptime age before triggering a cleanup + uptimeRetention = 30 * 24 * time.Hour // Minimum duration that must be kept to operate as intended + uptimeHourlyBuffer = 48 * time.Hour // Number of hours to buffer from now when determining which hourly uptime entries can be merged into daily uptime entries + + cacheTTL = 10 * time.Minute +) + +var ( + // ErrPathNotSpecified is the error returned when the path parameter passed in NewStore is blank + ErrPathNotSpecified = errors.New("path cannot be empty") + + // ErrDatabaseDriverNotSpecified is the error returned when the driver parameter passed in NewStore is blank + ErrDatabaseDriverNotSpecified = errors.New("database driver cannot be empty") + + errNoRowsReturned = errors.New("expected a row to be returned, but none was") +) + +// Store that leverages a database +type Store struct { + driver, path string + + db *sql.DB + + // writeThroughCache is a cache used to drastically decrease read latency by pre-emptively + // caching writes as they happen. If nil, writes are not cached. + writeThroughCache *gocache.Cache +} + +// NewStore initializes the database and creates the schema if it doesn't already exist in the path specified +func NewStore(driver, path string, caching bool) (*Store, error) { + if len(driver) == 0 { + return nil, ErrDatabaseDriverNotSpecified + } + if len(path) == 0 { + return nil, ErrPathNotSpecified + } + store := &Store{driver: driver, path: path} + var err error + if store.db, err = sql.Open(driver, path); err != nil { + return nil, err + } + if err := store.db.Ping(); err != nil { + return nil, err + } + if driver == "sqlite" { + _, _ = store.db.Exec("PRAGMA foreign_keys=ON") + _, _ = store.db.Exec("PRAGMA journal_mode=WAL") + _, _ = store.db.Exec("PRAGMA synchronous=NORMAL") + // Prevents driver from running into "database is locked" errors + // This is because we're using WAL to improve performance + store.db.SetMaxOpenConns(1) + } + if err = store.createSchema(); err != nil { + _ = store.db.Close() + return nil, err + } + if caching { + store.writeThroughCache = gocache.NewCache().WithMaxSize(10000) + } + return store, nil +} + +// createSchema creates the schema required to perform all database operations. +func (s *Store) createSchema() error { + if s.driver == "sqlite" { + return s.createSQLiteSchema() + } + return s.createPostgresSchema() +} + +// GetAllEndpointStatuses returns all monitored endpoint.Status +// with a subset of endpoint.Result defined by the page and pageSize parameters +func (s *Store) GetAllEndpointStatuses(params *paging.EndpointStatusParams) ([]*endpoint.Status, error) { + tx, err := s.db.Begin() + if err != nil { + return nil, err + } + keys, err := s.getAllEndpointKeys(tx) + if err != nil { + _ = tx.Rollback() + return nil, err + } + endpointStatuses := make([]*endpoint.Status, 0, len(keys)) + for _, key := range keys { + endpointStatus, err := s.getEndpointStatusByKey(tx, key, params) + if err != nil { + continue + } + endpointStatuses = append(endpointStatuses, endpointStatus) + } + if err = tx.Commit(); err != nil { + _ = tx.Rollback() + } + return endpointStatuses, err +} + +// GetEndpointStatus returns the endpoint status for a given endpoint name in the given group +func (s *Store) GetEndpointStatus(groupName, endpointName string, params *paging.EndpointStatusParams) (*endpoint.Status, error) { + return s.GetEndpointStatusByKey(endpoint.ConvertGroupAndEndpointNameToKey(groupName, endpointName), params) +} + +// GetEndpointStatusByKey returns the endpoint status for a given key +func (s *Store) GetEndpointStatusByKey(key string, params *paging.EndpointStatusParams) (*endpoint.Status, error) { + tx, err := s.db.Begin() + if err != nil { + return nil, err + } + endpointStatus, err := s.getEndpointStatusByKey(tx, key, params) + if err != nil { + _ = tx.Rollback() + return nil, err + } + if err = tx.Commit(); err != nil { + _ = tx.Rollback() + } + return endpointStatus, err +} + +// GetUptimeByKey returns the uptime percentage during a time range +func (s *Store) GetUptimeByKey(key string, from, to time.Time) (float64, error) { + if from.After(to) { + return 0, common.ErrInvalidTimeRange + } + tx, err := s.db.Begin() + if err != nil { + return 0, err + } + endpointID, _, _, err := s.getEndpointIDGroupAndNameByKey(tx, key) + if err != nil { + _ = tx.Rollback() + return 0, err + } + uptime, _, err := s.getEndpointUptime(tx, endpointID, from, to) + if err != nil { + _ = tx.Rollback() + return 0, err + } + if err = tx.Commit(); err != nil { + _ = tx.Rollback() + } + return uptime, nil +} + +// GetAverageResponseTimeByKey returns the average response time in milliseconds (value) during a time range +func (s *Store) GetAverageResponseTimeByKey(key string, from, to time.Time) (int, error) { + if from.After(to) { + return 0, common.ErrInvalidTimeRange + } + tx, err := s.db.Begin() + if err != nil { + return 0, err + } + endpointID, _, _, err := s.getEndpointIDGroupAndNameByKey(tx, key) + if err != nil { + _ = tx.Rollback() + return 0, err + } + averageResponseTime, err := s.getEndpointAverageResponseTime(tx, endpointID, from, to) + if err != nil { + _ = tx.Rollback() + return 0, err + } + if err = tx.Commit(); err != nil { + _ = tx.Rollback() + } + return averageResponseTime, nil +} + +// GetHourlyAverageResponseTimeByKey returns a map of hourly (key) average response time in milliseconds (value) during a time range +func (s *Store) GetHourlyAverageResponseTimeByKey(key string, from, to time.Time) (map[int64]int, error) { + if from.After(to) { + return nil, common.ErrInvalidTimeRange + } + tx, err := s.db.Begin() + if err != nil { + return nil, err + } + endpointID, _, _, err := s.getEndpointIDGroupAndNameByKey(tx, key) + if err != nil { + _ = tx.Rollback() + return nil, err + } + hourlyAverageResponseTimes, err := s.getEndpointHourlyAverageResponseTimes(tx, endpointID, from, to) + if err != nil { + _ = tx.Rollback() + return nil, err + } + if err = tx.Commit(); err != nil { + _ = tx.Rollback() + } + return hourlyAverageResponseTimes, nil +} + +// Insert adds the observed result for the specified endpoint into the store +func (s *Store) Insert(ep *endpoint.Endpoint, result *endpoint.Result) error { + tx, err := s.db.Begin() + if err != nil { + return err + } + endpointID, err := s.getEndpointID(tx, ep) + if err != nil { + if errors.Is(err, common.ErrEndpointNotFound) { + // Endpoint doesn't exist in the database, insert it + if endpointID, err = s.insertEndpoint(tx, ep); err != nil { + _ = tx.Rollback() + logr.Errorf("[sql.Insert] Failed to create endpoint with key=%s: %s", ep.Key(), err.Error()) + return err + } + } else { + _ = tx.Rollback() + logr.Errorf("[sql.Insert] Failed to retrieve id of endpoint with key=%s: %s", ep.Key(), err.Error()) + return err + } + } + // First, we need to check if we need to insert a new event. + // + // A new event must be added if either of the following cases happen: + // 1. There is only 1 event. The total number of events for an endpoint can only be 1 if the only existing event is + // of type EventStart, in which case we will have to create a new event of type EventHealthy or EventUnhealthy + // based on result.Success. + // 2. The lastResult.Success != result.Success. This implies that the endpoint went from healthy to unhealthy or + // vice versa, in which case we will have to create a new event of type EventHealthy or EventUnhealthy + // based on result.Success. + numberOfEvents, err := s.getNumberOfEventsByEndpointID(tx, endpointID) + if err != nil { + // Silently fail + logr.Errorf("[sql.Insert] Failed to retrieve total number of events for endpoint with key=%s: %s", ep.Key(), err.Error()) + } + if numberOfEvents == 0 { + // There's no events yet, which means we need to add the EventStart and the first healthy/unhealthy event + err = s.insertEndpointEvent(tx, endpointID, &endpoint.Event{ + Type: endpoint.EventStart, + Timestamp: result.Timestamp.Add(-50 * time.Millisecond), + }) + if err != nil { + // Silently fail + logr.Errorf("[sql.Insert] Failed to insert event=%s for endpoint with key=%s: %s", endpoint.EventStart, ep.Key(), err.Error()) + } + event := endpoint.NewEventFromResult(result) + if err = s.insertEndpointEvent(tx, endpointID, event); err != nil { + // Silently fail + logr.Errorf("[sql.Insert] Failed to insert event=%s for endpoint with key=%s: %s", event.Type, ep.Key(), err.Error()) + } + } else { + // Get the success value of the previous result + var lastResultSuccess bool + if lastResultSuccess, err = s.getLastEndpointResultSuccessValue(tx, endpointID); err != nil { + logr.Errorf("[sql.Insert] Failed to retrieve outcome of previous result for endpoint with key=%s: %s", ep.Key(), err.Error()) + } else { + // If we managed to retrieve the outcome of the previous result, we'll compare it with the new result. + // If the final outcome (success or failure) of the previous and the new result aren't the same, it means + // that the endpoint either went from Healthy to Unhealthy or Unhealthy -> Healthy, therefore, we'll add + // an event to mark the change in state + if lastResultSuccess != result.Success { + event := endpoint.NewEventFromResult(result) + if err = s.insertEndpointEvent(tx, endpointID, event); err != nil { + // Silently fail + logr.Errorf("[sql.Insert] Failed to insert event=%s for endpoint with key=%s: %s", event.Type, ep.Key(), err.Error()) + } + } + } + // Clean up old events if there's more than twice the maximum number of events + // This lets us both keep the table clean without impacting performance too much + // (since we're only deleting MaximumNumberOfEvents at a time instead of 1) + if numberOfEvents > eventsCleanUpThreshold { + if err = s.deleteOldEndpointEvents(tx, endpointID); err != nil { + logr.Errorf("[sql.Insert] Failed to delete old events for endpoint with key=%s: %s", ep.Key(), err.Error()) + } + } + } + // Second, we need to insert the result. + if err = s.insertEndpointResult(tx, endpointID, result); err != nil { + logr.Errorf("[sql.Insert] Failed to insert result for endpoint with key=%s: %s", ep.Key(), err.Error()) + _ = tx.Rollback() // If we can't insert the result, we'll rollback now since there's no point continuing + return err + } + // Clean up old results + numberOfResults, err := s.getNumberOfResultsByEndpointID(tx, endpointID) + if err != nil { + logr.Errorf("[sql.Insert] Failed to retrieve total number of results for endpoint with key=%s: %s", ep.Key(), err.Error()) + } else { + if numberOfResults > resultsCleanUpThreshold { + if err = s.deleteOldEndpointResults(tx, endpointID); err != nil { + logr.Errorf("[sql.Insert] Failed to delete old results for endpoint with key=%s: %s", ep.Key(), err.Error()) + } + } + } + // Finally, we need to insert the uptime data. + // Because the uptime data significantly outlives the results, we can't rely on the results for determining the uptime + if err = s.updateEndpointUptime(tx, endpointID, result); err != nil { + logr.Errorf("[sql.Insert] Failed to update uptime for endpoint with key=%s: %s", ep.Key(), err.Error()) + } + // Merge hourly uptime entries that can be merged into daily entries and clean up old uptime entries + numberOfUptimeEntries, err := s.getNumberOfUptimeEntriesByEndpointID(tx, endpointID) + if err != nil { + logr.Errorf("[sql.Insert] Failed to retrieve total number of uptime entries for endpoint with key=%s: %s", ep.Key(), err.Error()) + } else { + // Merge older hourly uptime entries into daily uptime entries if we have more than uptimeTotalEntriesMergeThreshold + if numberOfUptimeEntries >= uptimeTotalEntriesMergeThreshold { + logr.Infof("[sql.Insert] Merging hourly uptime entries for endpoint with key=%s; This is a lot of work, it shouldn't happen too often", ep.Key()) + if err = s.mergeHourlyUptimeEntriesOlderThanMergeThresholdIntoDailyUptimeEntries(tx, endpointID); err != nil { + logr.Errorf("[sql.Insert] Failed to merge hourly uptime entries for endpoint with key=%s: %s", ep.Key(), err.Error()) + } + } + } + // Clean up outdated uptime entries + // In most cases, this would be handled by mergeHourlyUptimeEntriesOlderThanMergeThresholdIntoDailyUptimeEntries, + // but if Gatus was temporarily shut down, we might have some old entries that need to be cleaned up + ageOfOldestUptimeEntry, err := s.getAgeOfOldestEndpointUptimeEntry(tx, endpointID) + if err != nil { + logr.Errorf("[sql.Insert] Failed to retrieve oldest endpoint uptime entry for endpoint with key=%s: %s", ep.Key(), err.Error()) + } else { + if ageOfOldestUptimeEntry > uptimeAgeCleanUpThreshold { + if err = s.deleteOldUptimeEntries(tx, endpointID, time.Now().Add(-(uptimeRetention + time.Hour))); err != nil { + logr.Errorf("[sql.Insert] Failed to delete old uptime entries for endpoint with key=%s: %s", ep.Key(), err.Error()) + } + } + } + if s.writeThroughCache != nil { + cacheKeysToRefresh := s.writeThroughCache.GetKeysByPattern(ep.Key()+"*", 0) + for _, cacheKey := range cacheKeysToRefresh { + s.writeThroughCache.Delete(cacheKey) + endpointKey, params, err := extractKeyAndParamsFromCacheKey(cacheKey) + if err != nil { + logr.Errorf("[sql.Insert] Silently deleting cache key %s instead of refreshing due to error: %s", cacheKey, err.Error()) + continue + } + // Retrieve the endpoint status by key, which will in turn refresh the cache + _, _ = s.getEndpointStatusByKey(tx, endpointKey, params) + } + } + if err = tx.Commit(); err != nil { + _ = tx.Rollback() + } + return err +} + +// DeleteAllEndpointStatusesNotInKeys removes all rows owned by an endpoint whose key is not within the keys provided +func (s *Store) DeleteAllEndpointStatusesNotInKeys(keys []string) int { + var err error + var result sql.Result + if len(keys) == 0 { + // Delete everything + result, err = s.db.Exec("DELETE FROM endpoints") + } else { + args := make([]interface{}, 0, len(keys)) + query := "DELETE FROM endpoints WHERE endpoint_key NOT IN (" + for i := range keys { + query += fmt.Sprintf("$%d,", i+1) + args = append(args, keys[i]) + } + query = query[:len(query)-1] + ")" // Remove the last comma and add the closing parenthesis + result, err = s.db.Exec(query, args...) + } + if err != nil { + logr.Errorf("[sql.DeleteAllEndpointStatusesNotInKeys] Failed to delete rows that do not belong to any of keys=%v: %s", keys, err.Error()) + return 0 + } + if s.writeThroughCache != nil { + // It's easier to just wipe out the entire cache than to try to find all keys that are not in the keys list + // This only happens on start and during tests, so it's fine for us to just clear the cache without worrying + // about performance + _ = s.writeThroughCache.DeleteKeysByPattern("*") + } + // Return number of rows deleted + rowsAffects, _ := result.RowsAffected() + return int(rowsAffects) +} + +// GetTriggeredEndpointAlert returns whether the triggered alert for the specified endpoint as well as the necessary information to resolve it +func (s *Store) GetTriggeredEndpointAlert(ep *endpoint.Endpoint, alert *alert.Alert) (exists bool, resolveKey string, numberOfSuccessesInARow int, err error) { + //logr.Debugf("[sql.GetTriggeredEndpointAlert] Getting triggered alert with checksum=%s for endpoint with key=%s", alert.Checksum(), ep.Key()) + err = s.db.QueryRow( + "SELECT resolve_key, number_of_successes_in_a_row FROM endpoint_alerts_triggered WHERE endpoint_id = (SELECT endpoint_id FROM endpoints WHERE endpoint_key = $1 LIMIT 1) AND configuration_checksum = $2", + ep.Key(), + alert.Checksum(), + ).Scan(&resolveKey, &numberOfSuccessesInARow) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return false, "", 0, nil + } + return false, "", 0, err + } + return true, resolveKey, numberOfSuccessesInARow, nil +} + +// UpsertTriggeredEndpointAlert inserts/updates a triggered alert for an endpoint +// Used for persistence of triggered alerts across application restarts +func (s *Store) UpsertTriggeredEndpointAlert(ep *endpoint.Endpoint, triggeredAlert *alert.Alert) error { + //logr.Debugf("[sql.UpsertTriggeredEndpointAlert] Upserting triggered alert with checksum=%s for endpoint with key=%s", triggeredAlert.Checksum(), ep.Key()) + tx, err := s.db.Begin() + if err != nil { + return err + } + endpointID, err := s.getEndpointID(tx, ep) + if err != nil { + if errors.Is(err, common.ErrEndpointNotFound) { + // Endpoint doesn't exist in the database, insert it + // This shouldn't happen, but we'll handle it anyway + if endpointID, err = s.insertEndpoint(tx, ep); err != nil { + _ = tx.Rollback() + logr.Errorf("[sql.UpsertTriggeredEndpointAlert] Failed to create endpoint with key=%s: %s", ep.Key(), err.Error()) + return err + } + } else { + _ = tx.Rollback() + logr.Errorf("[sql.UpsertTriggeredEndpointAlert] Failed to retrieve id of endpoint with key=%s: %s", ep.Key(), err.Error()) + return err + } + } + _, err = tx.Exec( + ` + INSERT INTO endpoint_alerts_triggered (endpoint_id, configuration_checksum, resolve_key, number_of_successes_in_a_row) + VALUES ($1, $2, $3, $4) + ON CONFLICT(endpoint_id, configuration_checksum) DO UPDATE SET + resolve_key = $3, + number_of_successes_in_a_row = $4 + `, + endpointID, + triggeredAlert.Checksum(), + triggeredAlert.ResolveKey, + ep.NumberOfSuccessesInARow, // We only persist NumberOfSuccessesInARow, because all alerts in this table are already triggered + ) + if err != nil { + _ = tx.Rollback() + logr.Errorf("[sql.UpsertTriggeredEndpointAlert] Failed to persist triggered alert for endpoint with key=%s: %s", ep.Key(), err.Error()) + return err + } + if err = tx.Commit(); err != nil { + _ = tx.Rollback() + } + return nil +} + +// DeleteTriggeredEndpointAlert deletes a triggered alert for an endpoint +func (s *Store) DeleteTriggeredEndpointAlert(ep *endpoint.Endpoint, triggeredAlert *alert.Alert) error { + //logr.Debugf("[sql.DeleteTriggeredEndpointAlert] Deleting triggered alert with checksum=%s for endpoint with key=%s", triggeredAlert.Checksum(), ep.Key()) + _, err := s.db.Exec("DELETE FROM endpoint_alerts_triggered WHERE configuration_checksum = $1 AND endpoint_id = (SELECT endpoint_id FROM endpoints WHERE endpoint_key = $2 LIMIT 1)", triggeredAlert.Checksum(), ep.Key()) + return err +} + +// DeleteAllTriggeredAlertsNotInChecksumsByEndpoint removes all triggered alerts owned by an endpoint whose alert +// configurations are not provided in the checksums list. +// This prevents triggered alerts that have been removed or modified from lingering in the database. +func (s *Store) DeleteAllTriggeredAlertsNotInChecksumsByEndpoint(ep *endpoint.Endpoint, checksums []string) int { + //logr.Debugf("[sql.DeleteAllTriggeredAlertsNotInChecksumsByEndpoint] Deleting triggered alerts for endpoint with key=%s that do not belong to any of checksums=%v", ep.Key(), checksums) + var err error + var result sql.Result + if len(checksums) == 0 { + // No checksums? Then it means there are no (enabled) alerts configured for that endpoint, so we can get rid of all + // persisted triggered alerts for that endpoint + result, err = s.db.Exec("DELETE FROM endpoint_alerts_triggered WHERE endpoint_id = (SELECT endpoint_id FROM endpoints WHERE endpoint_key = $1 LIMIT 1)", ep.Key()) + } else { + args := make([]interface{}, 0, len(checksums)+1) + args = append(args, ep.Key()) + query := `DELETE FROM endpoint_alerts_triggered + WHERE endpoint_id = (SELECT endpoint_id FROM endpoints WHERE endpoint_key = $1 LIMIT 1) + AND configuration_checksum NOT IN (` + for i := range checksums { + query += fmt.Sprintf("$%d,", i+2) + args = append(args, checksums[i]) + } + query = query[:len(query)-1] + ")" // Remove the last comma and add the closing parenthesis + result, err = s.db.Exec(query, args...) + } + if err != nil { + logr.Errorf("[sql.DeleteAllTriggeredAlertsNotInChecksumsByEndpoint] Failed to delete rows for endpoint with key=%s that do not belong to any of checksums=%v: %s", ep.Key(), checksums, err.Error()) + return 0 + } + // Return number of rows deleted + rowsAffects, _ := result.RowsAffected() + return int(rowsAffects) +} + +// Clear deletes everything from the store +func (s *Store) Clear() { + _, _ = s.db.Exec("DELETE FROM endpoints") + if s.writeThroughCache != nil { + _ = s.writeThroughCache.DeleteKeysByPattern("*") + } +} + +// Save does nothing, because this store is immediately persistent. +func (s *Store) Save() error { + return nil +} + +// Close the database handle +func (s *Store) Close() { + _ = s.db.Close() + if s.writeThroughCache != nil { + // Clear the cache too. If the store's been closed, we don't want to keep the cache around. + _ = s.writeThroughCache.DeleteKeysByPattern("*") + } +} + +// insertEndpoint inserts an endpoint in the store and returns the generated id of said endpoint +func (s *Store) insertEndpoint(tx *sql.Tx, ep *endpoint.Endpoint) (int64, error) { + //logr.Debugf("[sql.insertEndpoint] Inserting endpoint with group=%s and name=%s", ep.Group, ep.Name) + var id int64 + err := tx.QueryRow( + "INSERT INTO endpoints (endpoint_key, endpoint_name, endpoint_group) VALUES ($1, $2, $3) RETURNING endpoint_id", + ep.Key(), + ep.Name, + ep.Group, + ).Scan(&id) + if err != nil { + return 0, err + } + return id, nil +} + +// insertEndpointEvent inserts en event in the store +func (s *Store) insertEndpointEvent(tx *sql.Tx, endpointID int64, event *endpoint.Event) error { + _, err := tx.Exec( + "INSERT INTO endpoint_events (endpoint_id, event_type, event_timestamp) VALUES ($1, $2, $3)", + endpointID, + event.Type, + event.Timestamp.UTC(), + ) + if err != nil { + return err + } + return nil +} + +// insertEndpointResult inserts a result in the store +func (s *Store) insertEndpointResult(tx *sql.Tx, endpointID int64, result *endpoint.Result) error { + var endpointResultID int64 + err := tx.QueryRow( + ` + INSERT INTO endpoint_results (endpoint_id, success, errors, connected, status, dns_rcode, certificate_expiration, domain_expiration, hostname, ip, duration, timestamp) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) + RETURNING endpoint_result_id + `, + endpointID, + result.Success, + strings.Join(result.Errors, arraySeparator), + result.Connected, + result.HTTPStatus, + result.DNSRCode, + result.CertificateExpiration, + result.DomainExpiration, + result.Hostname, + result.IP, + result.Duration, + result.Timestamp.UTC(), + ).Scan(&endpointResultID) + if err != nil { + return err + } + return s.insertConditionResults(tx, endpointResultID, result.ConditionResults) +} + +func (s *Store) insertConditionResults(tx *sql.Tx, endpointResultID int64, conditionResults []*endpoint.ConditionResult) error { + var err error + for _, cr := range conditionResults { + _, err = tx.Exec("INSERT INTO endpoint_result_conditions (endpoint_result_id, condition, success) VALUES ($1, $2, $3)", + endpointResultID, + cr.Condition, + cr.Success, + ) + if err != nil { + return err + } + } + return nil +} + +func (s *Store) updateEndpointUptime(tx *sql.Tx, endpointID int64, result *endpoint.Result) error { + unixTimestampFlooredAtHour := result.Timestamp.Truncate(time.Hour).Unix() + var successfulExecutions int + if result.Success { + successfulExecutions = 1 + } + _, err := tx.Exec( + ` + INSERT INTO endpoint_uptimes (endpoint_id, hour_unix_timestamp, total_executions, successful_executions, total_response_time) + VALUES ($1, $2, $3, $4, $5) + ON CONFLICT(endpoint_id, hour_unix_timestamp) DO UPDATE SET + total_executions = excluded.total_executions + endpoint_uptimes.total_executions, + successful_executions = excluded.successful_executions + endpoint_uptimes.successful_executions, + total_response_time = excluded.total_response_time + endpoint_uptimes.total_response_time + `, + endpointID, + unixTimestampFlooredAtHour, + 1, + successfulExecutions, + result.Duration.Milliseconds(), + ) + return err +} + +func (s *Store) getAllEndpointKeys(tx *sql.Tx) (keys []string, err error) { + rows, err := tx.Query("SELECT endpoint_key FROM endpoints ORDER BY endpoint_key") + if err != nil { + return nil, err + } + for rows.Next() { + var key string + _ = rows.Scan(&key) + keys = append(keys, key) + } + return +} + +func (s *Store) getEndpointStatusByKey(tx *sql.Tx, key string, parameters *paging.EndpointStatusParams) (*endpoint.Status, error) { + var cacheKey string + if s.writeThroughCache != nil { + cacheKey = generateCacheKey(key, parameters) + if cachedEndpointStatus, exists := s.writeThroughCache.Get(cacheKey); exists { + if castedCachedEndpointStatus, ok := cachedEndpointStatus.(*endpoint.Status); ok { + return castedCachedEndpointStatus, nil + } + } + } + endpointID, group, endpointName, err := s.getEndpointIDGroupAndNameByKey(tx, key) + if err != nil { + return nil, err + } + endpointStatus := endpoint.NewStatus(group, endpointName) + if parameters.EventsPageSize > 0 { + if endpointStatus.Events, err = s.getEndpointEventsByEndpointID(tx, endpointID, parameters.EventsPage, parameters.EventsPageSize); err != nil { + logr.Errorf("[sql.getEndpointStatusByKey] Failed to retrieve events for key=%s: %s", key, err.Error()) + } + } + if parameters.ResultsPageSize > 0 { + if endpointStatus.Results, err = s.getEndpointResultsByEndpointID(tx, endpointID, parameters.ResultsPage, parameters.ResultsPageSize); err != nil { + logr.Errorf("[sql.getEndpointStatusByKey] Failed to retrieve results for key=%s: %s", key, err.Error()) + } + } + if s.writeThroughCache != nil { + s.writeThroughCache.SetWithTTL(cacheKey, endpointStatus, cacheTTL) + } + return endpointStatus, nil +} + +func (s *Store) getEndpointIDGroupAndNameByKey(tx *sql.Tx, key string) (id int64, group, name string, err error) { + err = tx.QueryRow( + ` + SELECT endpoint_id, endpoint_group, endpoint_name + FROM endpoints + WHERE endpoint_key = $1 + LIMIT 1 + `, + key, + ).Scan(&id, &group, &name) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return 0, "", "", common.ErrEndpointNotFound + } + return 0, "", "", err + } + return +} + +func (s *Store) getEndpointEventsByEndpointID(tx *sql.Tx, endpointID int64, page, pageSize int) (events []*endpoint.Event, err error) { + rows, err := tx.Query( + ` + SELECT event_type, event_timestamp + FROM endpoint_events + WHERE endpoint_id = $1 + ORDER BY endpoint_event_id ASC + LIMIT $2 OFFSET $3 + `, + endpointID, + pageSize, + (page-1)*pageSize, + ) + if err != nil { + return nil, err + } + for rows.Next() { + event := &endpoint.Event{} + _ = rows.Scan(&event.Type, &event.Timestamp) + events = append(events, event) + } + return +} + +func (s *Store) getEndpointResultsByEndpointID(tx *sql.Tx, endpointID int64, page, pageSize int) (results []*endpoint.Result, err error) { + rows, err := tx.Query( + ` + SELECT endpoint_result_id, success, errors, connected, status, dns_rcode, certificate_expiration, domain_expiration, hostname, ip, duration, timestamp + FROM endpoint_results + WHERE endpoint_id = $1 + ORDER BY endpoint_result_id DESC -- Normally, we'd sort by timestamp, but sorting by endpoint_result_id is faster + LIMIT $2 OFFSET $3 + `, + endpointID, + pageSize, + (page-1)*pageSize, + ) + if err != nil { + return nil, err + } + idResultMap := make(map[int64]*endpoint.Result) + for rows.Next() { + result := &endpoint.Result{} + var id int64 + var joinedErrors string + err = rows.Scan(&id, &result.Success, &joinedErrors, &result.Connected, &result.HTTPStatus, &result.DNSRCode, &result.CertificateExpiration, &result.DomainExpiration, &result.Hostname, &result.IP, &result.Duration, &result.Timestamp) + if err != nil { + logr.Errorf("[sql.getEndpointResultsByEndpointID] Silently failed to retrieve endpoint result for endpointID=%d: %s", endpointID, err.Error()) + err = nil + } + if len(joinedErrors) != 0 { + result.Errors = strings.Split(joinedErrors, arraySeparator) + } + // This is faster than using a subselect + results = append([]*endpoint.Result{result}, results...) + idResultMap[id] = result + } + if len(idResultMap) == 0 { + // If there's no result, we'll just return an empty/nil slice + return + } + // Get condition results + args := make([]interface{}, 0, len(idResultMap)) + query := `SELECT endpoint_result_id, condition, success + FROM endpoint_result_conditions + WHERE endpoint_result_id IN (` + index := 1 + for endpointResultID := range idResultMap { + query += "$" + strconv.Itoa(index) + "," + args = append(args, endpointResultID) + index++ + } + query = query[:len(query)-1] + ")" + rows, err = tx.Query(query, args...) + if err != nil { + return nil, err + } + defer rows.Close() // explicitly defer the close in case an error happens during the scan + for rows.Next() { + conditionResult := &endpoint.ConditionResult{} + var endpointResultID int64 + if err = rows.Scan(&endpointResultID, &conditionResult.Condition, &conditionResult.Success); err != nil { + return + } + idResultMap[endpointResultID].ConditionResults = append(idResultMap[endpointResultID].ConditionResults, conditionResult) + } + return +} + +func (s *Store) getEndpointUptime(tx *sql.Tx, endpointID int64, from, to time.Time) (uptime float64, avgResponseTime time.Duration, err error) { + rows, err := tx.Query( + ` + SELECT SUM(total_executions), SUM(successful_executions), SUM(total_response_time) + FROM endpoint_uptimes + WHERE endpoint_id = $1 + AND hour_unix_timestamp >= $2 + AND hour_unix_timestamp <= $3 + `, + endpointID, + from.Unix(), + to.Unix(), + ) + if err != nil { + return 0, 0, err + } + var totalExecutions, totalSuccessfulExecutions, totalResponseTime int + for rows.Next() { + _ = rows.Scan(&totalExecutions, &totalSuccessfulExecutions, &totalResponseTime) + } + if totalExecutions > 0 { + uptime = float64(totalSuccessfulExecutions) / float64(totalExecutions) + avgResponseTime = time.Duration(float64(totalResponseTime)/float64(totalExecutions)) * time.Millisecond + } + return +} + +func (s *Store) getEndpointAverageResponseTime(tx *sql.Tx, endpointID int64, from, to time.Time) (int, error) { + rows, err := tx.Query( + ` + SELECT SUM(total_executions), SUM(total_response_time) + FROM endpoint_uptimes + WHERE endpoint_id = $1 + AND total_executions > 0 + AND hour_unix_timestamp >= $2 + AND hour_unix_timestamp <= $3 + `, + endpointID, + from.Unix(), + to.Unix(), + ) + if err != nil { + return 0, err + } + var totalExecutions, totalResponseTime int + for rows.Next() { + _ = rows.Scan(&totalExecutions, &totalResponseTime) + } + if totalExecutions == 0 { + return 0, nil + } + return int(float64(totalResponseTime) / float64(totalExecutions)), nil +} + +func (s *Store) getEndpointHourlyAverageResponseTimes(tx *sql.Tx, endpointID int64, from, to time.Time) (map[int64]int, error) { + rows, err := tx.Query( + ` + SELECT hour_unix_timestamp, total_executions, total_response_time + FROM endpoint_uptimes + WHERE endpoint_id = $1 + AND total_executions > 0 + AND hour_unix_timestamp >= $2 + AND hour_unix_timestamp <= $3 + `, + endpointID, + from.Unix(), + to.Unix(), + ) + if err != nil { + return nil, err + } + var totalExecutions, totalResponseTime int + var unixTimestampFlooredAtHour int64 + hourlyAverageResponseTimes := make(map[int64]int) + for rows.Next() { + _ = rows.Scan(&unixTimestampFlooredAtHour, &totalExecutions, &totalResponseTime) + hourlyAverageResponseTimes[unixTimestampFlooredAtHour] = int(float64(totalResponseTime) / float64(totalExecutions)) + } + return hourlyAverageResponseTimes, nil +} + +func (s *Store) getEndpointID(tx *sql.Tx, ep *endpoint.Endpoint) (int64, error) { + var id int64 + err := tx.QueryRow("SELECT endpoint_id FROM endpoints WHERE endpoint_key = $1", ep.Key()).Scan(&id) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return 0, common.ErrEndpointNotFound + } + return 0, err + } + return id, nil +} + +func (s *Store) getNumberOfEventsByEndpointID(tx *sql.Tx, endpointID int64) (int64, error) { + var numberOfEvents int64 + err := tx.QueryRow("SELECT COUNT(1) FROM endpoint_events WHERE endpoint_id = $1", endpointID).Scan(&numberOfEvents) + return numberOfEvents, err +} + +func (s *Store) getNumberOfResultsByEndpointID(tx *sql.Tx, endpointID int64) (int64, error) { + var numberOfResults int64 + err := tx.QueryRow("SELECT COUNT(1) FROM endpoint_results WHERE endpoint_id = $1", endpointID).Scan(&numberOfResults) + return numberOfResults, err +} + +func (s *Store) getNumberOfUptimeEntriesByEndpointID(tx *sql.Tx, endpointID int64) (int64, error) { + var numberOfUptimeEntries int64 + err := tx.QueryRow("SELECT COUNT(1) FROM endpoint_uptimes WHERE endpoint_id = $1", endpointID).Scan(&numberOfUptimeEntries) + return numberOfUptimeEntries, err +} + +func (s *Store) getAgeOfOldestEndpointUptimeEntry(tx *sql.Tx, endpointID int64) (time.Duration, error) { + rows, err := tx.Query( + ` + SELECT hour_unix_timestamp + FROM endpoint_uptimes + WHERE endpoint_id = $1 + ORDER BY hour_unix_timestamp + LIMIT 1 + `, + endpointID, + ) + if err != nil { + return 0, err + } + var oldestEndpointUptimeUnixTimestamp int64 + var found bool + for rows.Next() { + _ = rows.Scan(&oldestEndpointUptimeUnixTimestamp) + found = true + } + if !found { + return 0, errNoRowsReturned + } + return time.Since(time.Unix(oldestEndpointUptimeUnixTimestamp, 0)), nil +} + +func (s *Store) getLastEndpointResultSuccessValue(tx *sql.Tx, endpointID int64) (bool, error) { + var success bool + err := tx.QueryRow("SELECT success FROM endpoint_results WHERE endpoint_id = $1 ORDER BY endpoint_result_id DESC LIMIT 1", endpointID).Scan(&success) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return false, errNoRowsReturned + } + return false, err + } + return success, nil +} + +// deleteOldEndpointEvents deletes endpoint events that are no longer needed +func (s *Store) deleteOldEndpointEvents(tx *sql.Tx, endpointID int64) error { + _, err := tx.Exec( + ` + DELETE FROM endpoint_events + WHERE endpoint_id = $1 + AND endpoint_event_id NOT IN ( + SELECT endpoint_event_id + FROM endpoint_events + WHERE endpoint_id = $1 + ORDER BY endpoint_event_id DESC + LIMIT $2 + ) + `, + endpointID, + common.MaximumNumberOfEvents, + ) + return err +} + +// deleteOldEndpointResults deletes endpoint results that are no longer needed +func (s *Store) deleteOldEndpointResults(tx *sql.Tx, endpointID int64) error { + _, err := tx.Exec( + ` + DELETE FROM endpoint_results + WHERE endpoint_id = $1 + AND endpoint_result_id NOT IN ( + SELECT endpoint_result_id + FROM endpoint_results + WHERE endpoint_id = $1 + ORDER BY endpoint_result_id DESC + LIMIT $2 + ) + `, + endpointID, + common.MaximumNumberOfResults, + ) + return err +} + +func (s *Store) deleteOldUptimeEntries(tx *sql.Tx, endpointID int64, maxAge time.Time) error { + _, err := tx.Exec("DELETE FROM endpoint_uptimes WHERE endpoint_id = $1 AND hour_unix_timestamp < $2", endpointID, maxAge.Unix()) + return err +} + +// mergeHourlyUptimeEntriesOlderThanMergeThresholdIntoDailyUptimeEntries merges all hourly uptime entries older than +// uptimeHourlyMergeThreshold from now into daily uptime entries by summing all hourly entries of the same day into a +// single entry. +// +// This effectively limits the number of uptime entries to (48+(n-2)) where 48 is for the first 48 entries with hourly +// entries (defined by uptimeHourlyBuffer) and n is the number of days for all entries older than 48 hours. +// Supporting 30d of entries would then result in far less than 24*30=720 entries. +func (s *Store) mergeHourlyUptimeEntriesOlderThanMergeThresholdIntoDailyUptimeEntries(tx *sql.Tx, endpointID int64) error { + // Calculate timestamp of the first full day of uptime entries that would not impact the uptime calculation for 24h badges + // The logic is that once at least 48 hours passed, we: + // - No longer need to worry about keeping hourly entries + // - Don't have to worry about new hourly entries being inserted, as the day has already passed + // which implies that no matter at what hour of the day we are, any timestamp + 48h floored to the current day + // will never impact the 24h uptime badge calculation + now := time.Now() + minThreshold := now.Add(-uptimeHourlyBuffer) + minThreshold = time.Date(minThreshold.Year(), minThreshold.Month(), minThreshold.Day(), 0, 0, 0, 0, minThreshold.Location()) + maxThreshold := now.Add(-uptimeRetention) + // Get all uptime entries older than uptimeHourlyMergeThreshold + rows, err := tx.Query( + ` + SELECT hour_unix_timestamp, total_executions, successful_executions, total_response_time + FROM endpoint_uptimes + WHERE endpoint_id = $1 + AND hour_unix_timestamp < $2 + AND hour_unix_timestamp >= $3 + `, + endpointID, + minThreshold.Unix(), + maxThreshold.Unix(), + ) + if err != nil { + return err + } + type Entry struct { + totalExecutions int + successfulExecutions int + totalResponseTime int + } + dailyEntries := make(map[int64]*Entry) + for rows.Next() { + var unixTimestamp int64 + entry := Entry{} + if err = rows.Scan(&unixTimestamp, &entry.totalExecutions, &entry.successfulExecutions, &entry.totalResponseTime); err != nil { + return err + } + timestamp := time.Unix(unixTimestamp, 0) + unixTimestampFlooredAtDay := time.Date(timestamp.Year(), timestamp.Month(), timestamp.Day(), 0, 0, 0, 0, timestamp.Location()).Unix() + if dailyEntry := dailyEntries[unixTimestampFlooredAtDay]; dailyEntry == nil { + dailyEntries[unixTimestampFlooredAtDay] = &entry + } else { + dailyEntries[unixTimestampFlooredAtDay].totalExecutions += entry.totalExecutions + dailyEntries[unixTimestampFlooredAtDay].successfulExecutions += entry.successfulExecutions + dailyEntries[unixTimestampFlooredAtDay].totalResponseTime += entry.totalResponseTime + } + } + // Delete older hourly uptime entries + _, err = tx.Exec("DELETE FROM endpoint_uptimes WHERE endpoint_id = $1 AND hour_unix_timestamp < $2", endpointID, minThreshold.Unix()) + if err != nil { + return err + } + // Insert new daily uptime entries + for unixTimestamp, entry := range dailyEntries { + _, err = tx.Exec( + ` + INSERT INTO endpoint_uptimes (endpoint_id, hour_unix_timestamp, total_executions, successful_executions, total_response_time) + VALUES ($1, $2, $3, $4, $5) + ON CONFLICT(endpoint_id, hour_unix_timestamp) DO UPDATE SET + total_executions = $3, + successful_executions = $4, + total_response_time = $5 + `, + endpointID, + unixTimestamp, + entry.totalExecutions, + entry.successfulExecutions, + entry.totalResponseTime, + ) + if err != nil { + return err + } + } + // TODO: Find a way to ignore entries that were already merged? + return nil +} + +func generateCacheKey(endpointKey string, p *paging.EndpointStatusParams) string { + return fmt.Sprintf("%s-%d-%d-%d-%d", endpointKey, p.EventsPage, p.EventsPageSize, p.ResultsPage, p.ResultsPageSize) +} + +func extractKeyAndParamsFromCacheKey(cacheKey string) (string, *paging.EndpointStatusParams, error) { + parts := strings.Split(cacheKey, "-") + if len(parts) < 5 { + return "", nil, fmt.Errorf("invalid cache key: %s", cacheKey) + } + params := &paging.EndpointStatusParams{} + var err error + if params.EventsPage, err = strconv.Atoi(parts[len(parts)-4]); err != nil { + return "", nil, fmt.Errorf("invalid cache key: %w", err) + } + if params.EventsPageSize, err = strconv.Atoi(parts[len(parts)-3]); err != nil { + return "", nil, fmt.Errorf("invalid cache key: %w", err) + } + if params.ResultsPage, err = strconv.Atoi(parts[len(parts)-2]); err != nil { + return "", nil, fmt.Errorf("invalid cache key: %w", err) + } + if params.ResultsPageSize, err = strconv.Atoi(parts[len(parts)-1]); err != nil { + return "", nil, fmt.Errorf("invalid cache key: %w", err) + } + return strings.Join(parts[:len(parts)-4], "-"), params, nil +} diff --git a/storage/store/sql/sql_test.go b/storage/store/sql/sql_test.go new file mode 100644 index 00000000..7261c8fa --- /dev/null +++ b/storage/store/sql/sql_test.go @@ -0,0 +1,853 @@ +package sql + +import ( + "errors" + "fmt" + "testing" + "time" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/storage/store/common" + "github.com/TwiN/gatus/v5/storage/store/common/paging" +) + +var ( + firstCondition = endpoint.Condition("[STATUS] == 200") + secondCondition = endpoint.Condition("[RESPONSE_TIME] < 500") + thirdCondition = endpoint.Condition("[CERTIFICATE_EXPIRATION] < 72h") + + now = time.Now() + + testEndpoint = endpoint.Endpoint{ + Name: "name", + Group: "group", + URL: "https://example.org/what/ever", + Method: "GET", + Body: "body", + Interval: 30 * time.Second, + Conditions: []endpoint.Condition{firstCondition, secondCondition, thirdCondition}, + Alerts: nil, + NumberOfFailuresInARow: 0, + NumberOfSuccessesInARow: 0, + } + testSuccessfulResult = endpoint.Result{ + Hostname: "example.org", + IP: "127.0.0.1", + HTTPStatus: 200, + Errors: nil, + Connected: true, + Success: true, + Timestamp: now, + Duration: 150 * time.Millisecond, + CertificateExpiration: 10 * time.Hour, + ConditionResults: []*endpoint.ConditionResult{ + { + Condition: "[STATUS] == 200", + Success: true, + }, + { + Condition: "[RESPONSE_TIME] < 500", + Success: true, + }, + { + Condition: "[CERTIFICATE_EXPIRATION] < 72h", + Success: true, + }, + }, + } + testUnsuccessfulResult = endpoint.Result{ + Hostname: "example.org", + IP: "127.0.0.1", + HTTPStatus: 200, + Errors: []string{"error-1", "error-2"}, + Connected: true, + Success: false, + Timestamp: now, + Duration: 750 * time.Millisecond, + CertificateExpiration: 10 * time.Hour, + ConditionResults: []*endpoint.ConditionResult{ + { + Condition: "[STATUS] == 200", + Success: true, + }, + { + Condition: "[RESPONSE_TIME] < 500", + Success: false, + }, + { + Condition: "[CERTIFICATE_EXPIRATION] < 72h", + Success: false, + }, + }, + } +) + +func TestNewStore(t *testing.T) { + if _, err := NewStore("", t.TempDir()+"/TestNewStore.db", false); !errors.Is(err, ErrDatabaseDriverNotSpecified) { + t.Error("expected error due to blank driver parameter") + } + if _, err := NewStore("sqlite", "", false); !errors.Is(err, ErrPathNotSpecified) { + t.Error("expected error due to blank path parameter") + } + if store, err := NewStore("sqlite", t.TempDir()+"/TestNewStore.db", true); err != nil { + t.Error("shouldn't have returned any error, got", err.Error()) + } else { + _ = store.db.Close() + } +} + +func TestStore_InsertCleansUpOldUptimeEntriesProperly(t *testing.T) { + store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_InsertCleansUpOldUptimeEntriesProperly.db", false) + defer store.Close() + now := time.Now().Truncate(time.Hour) + now = time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, now.Location()) + + store.Insert(&testEndpoint, &endpoint.Result{Timestamp: now.Add(-5 * time.Hour), Success: true}) + + tx, _ := store.db.Begin() + oldest, _ := store.getAgeOfOldestEndpointUptimeEntry(tx, 1) + _ = tx.Commit() + if oldest.Truncate(time.Hour) != 5*time.Hour { + t.Errorf("oldest endpoint uptime entry should've been ~5 hours old, was %s", oldest) + } + + // The oldest cache entry should remain at ~5 hours old, because this entry is more recent + store.Insert(&testEndpoint, &endpoint.Result{Timestamp: now.Add(-3 * time.Hour), Success: true}) + + tx, _ = store.db.Begin() + oldest, _ = store.getAgeOfOldestEndpointUptimeEntry(tx, 1) + _ = tx.Commit() + if oldest.Truncate(time.Hour) != 5*time.Hour { + t.Errorf("oldest endpoint uptime entry should've been ~5 hours old, was %s", oldest) + } + + // The oldest cache entry should now become at ~8 hours old, because this entry is older + store.Insert(&testEndpoint, &endpoint.Result{Timestamp: now.Add(-8 * time.Hour), Success: true}) + + tx, _ = store.db.Begin() + oldest, _ = store.getAgeOfOldestEndpointUptimeEntry(tx, 1) + _ = tx.Commit() + if oldest.Truncate(time.Hour) != 8*time.Hour { + t.Errorf("oldest endpoint uptime entry should've been ~8 hours old, was %s", oldest) + } + + // Since this is one hour before reaching the clean up threshold, the oldest entry should now be this one + store.Insert(&testEndpoint, &endpoint.Result{Timestamp: now.Add(-(uptimeAgeCleanUpThreshold - time.Hour)), Success: true}) + + tx, _ = store.db.Begin() + oldest, _ = store.getAgeOfOldestEndpointUptimeEntry(tx, 1) + _ = tx.Commit() + if oldest.Truncate(time.Hour) != uptimeAgeCleanUpThreshold-time.Hour { + t.Errorf("oldest endpoint uptime entry should've been ~%s hours old, was %s", uptimeAgeCleanUpThreshold-time.Hour, oldest) + } + + // Since this entry is after the uptimeAgeCleanUpThreshold, both this entry as well as the previous + // one should be deleted since they both surpass uptimeRetention + store.Insert(&testEndpoint, &endpoint.Result{Timestamp: now.Add(-(uptimeAgeCleanUpThreshold + time.Hour)), Success: true}) + + tx, _ = store.db.Begin() + oldest, _ = store.getAgeOfOldestEndpointUptimeEntry(tx, 1) + _ = tx.Commit() + if oldest.Truncate(time.Hour) != 8*time.Hour { + t.Errorf("oldest endpoint uptime entry should've been ~8 hours old, was %s", oldest) + } +} + +func TestStore_HourlyUptimeEntriesAreMergedIntoDailyUptimeEntriesProperly(t *testing.T) { + store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_HourlyUptimeEntriesAreMergedIntoDailyUptimeEntriesProperly.db", false) + defer store.Close() + now := time.Now().Truncate(time.Hour) + now = time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, now.Location()) + + scenarios := []struct { + numberOfHours int + expectedMaxUptimeEntries int64 + }{ + {numberOfHours: 1, expectedMaxUptimeEntries: 1}, + {numberOfHours: 10, expectedMaxUptimeEntries: 10}, + {numberOfHours: 50, expectedMaxUptimeEntries: 50}, + {numberOfHours: 75, expectedMaxUptimeEntries: 75}, + {numberOfHours: 99, expectedMaxUptimeEntries: 99}, + {numberOfHours: 150, expectedMaxUptimeEntries: 100}, + {numberOfHours: 300, expectedMaxUptimeEntries: 100}, + {numberOfHours: 768, expectedMaxUptimeEntries: 100}, // 32 days (in hours), which means anything beyond that won't be persisted anyway + {numberOfHours: 1000, expectedMaxUptimeEntries: 100}, + } + // Note that is not technically an accurate real world representation, because uptime entries are always added in + // the present, while this test is inserting results from the past to simulate long term uptime entries. + // Since we want to test the behavior and not the test itself, this is a "best effort" approach. + for _, scenario := range scenarios { + t.Run(fmt.Sprintf("num-hours-%d-expected-max-entries-%d", scenario.numberOfHours, scenario.expectedMaxUptimeEntries), func(t *testing.T) { + for i := scenario.numberOfHours; i > 0; i-- { + //fmt.Printf("i: %d (%s)\n", i, now.Add(-time.Duration(i)*time.Hour)) + // Create an uptime entry + err := store.Insert(&testEndpoint, &endpoint.Result{Timestamp: now.Add(-time.Duration(i) * time.Hour), Success: true}) + if err != nil { + t.Log(err) + } + //// DEBUGGING: check number of uptime entries for endpoint + //tx, _ := store.db.Begin() + //numberOfUptimeEntriesForEndpoint, err := store.getNumberOfUptimeEntriesByEndpointID(tx, 1) + //if err != nil { + // t.Log(err) + //} + //_ = tx.Commit() + //t.Logf("i=%d; numberOfHours=%d; There are currently %d uptime entries for endpointID=%d", i, scenario.numberOfHours, numberOfUptimeEntriesForEndpoint, 1) + } + // check number of uptime entries for endpoint + tx, _ := store.db.Begin() + numberOfUptimeEntriesForEndpoint, err := store.getNumberOfUptimeEntriesByEndpointID(tx, 1) + if err != nil { + t.Log(err) + } + _ = tx.Commit() + //t.Logf("numberOfHours=%d; There are currently %d uptime entries for endpointID=%d", scenario.numberOfHours, numberOfUptimeEntriesForEndpoint, 1) + if scenario.expectedMaxUptimeEntries < numberOfUptimeEntriesForEndpoint { + t.Errorf("expected %d (uptime entries) to be smaller than %d", numberOfUptimeEntriesForEndpoint, scenario.expectedMaxUptimeEntries) + } + store.Clear() + }) + } +} + +func TestStore_getEndpointUptime(t *testing.T) { + store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_InsertCleansUpEventsAndResultsProperly.db", false) + defer store.Clear() + defer store.Close() + // Add 768 hourly entries (32 days) + // Daily entries should be merged from hourly entries automatically + for i := 768; i > 0; i-- { + err := store.Insert(&testEndpoint, &endpoint.Result{Timestamp: time.Now().Add(-time.Duration(i) * time.Hour), Duration: time.Second, Success: true}) + if err != nil { + t.Log(err) + } + } + // Check the number of uptime entries + tx, _ := store.db.Begin() + numberOfUptimeEntriesForEndpoint, err := store.getNumberOfUptimeEntriesByEndpointID(tx, 1) + if err != nil { + t.Log(err) + } + if numberOfUptimeEntriesForEndpoint < 20 || numberOfUptimeEntriesForEndpoint > 200 { + t.Errorf("expected number of uptime entries to be between 20 and 200, got %d", numberOfUptimeEntriesForEndpoint) + } + // Retrieve uptime for the past 30d + uptime, avgResponseTime, err := store.getEndpointUptime(tx, 1, time.Now().Add(-(30 * 24 * time.Hour)), time.Now()) + if err != nil { + t.Log(err) + } + _ = tx.Commit() + if avgResponseTime != time.Second { + t.Errorf("expected average response time to be %s, got %s", time.Second, avgResponseTime) + } + if uptime != 1 { + t.Errorf("expected uptime to be 1, got %f", uptime) + } + // Add a new unsuccessful result, which should impact the uptime + err = store.Insert(&testEndpoint, &endpoint.Result{Timestamp: time.Now(), Duration: time.Second, Success: false}) + if err != nil { + t.Log(err) + } + // Retrieve uptime for the past 30d + tx, _ = store.db.Begin() + uptime, _, err = store.getEndpointUptime(tx, 1, time.Now().Add(-(30 * 24 * time.Hour)), time.Now()) + if err != nil { + t.Log(err) + } + _ = tx.Commit() + if uptime == 1 { + t.Errorf("expected uptime to be less than 1, got %f", uptime) + } + // Retrieve uptime for the past 30d, but excluding the last 24h + // This is not a real use case as there is no way for users to exclude the last 24h, but this is a great way + // to ensure that hourly merging works as intended + tx, _ = store.db.Begin() + uptimeExcludingLast24h, _, err := store.getEndpointUptime(tx, 1, time.Now().Add(-(30 * 24 * time.Hour)), time.Now().Add(-24*time.Hour)) + if err != nil { + t.Log(err) + } + _ = tx.Commit() + if uptimeExcludingLast24h == uptime { + t.Error("expected uptimeExcludingLast24h to to be different from uptime, got") + } +} + +func TestStore_InsertCleansUpEventsAndResultsProperly(t *testing.T) { + store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_InsertCleansUpEventsAndResultsProperly.db", false) + defer store.Clear() + defer store.Close() + for i := 0; i < resultsCleanUpThreshold+eventsCleanUpThreshold; i++ { + store.Insert(&testEndpoint, &testSuccessfulResult) + store.Insert(&testEndpoint, &testUnsuccessfulResult) + ss, _ := store.GetEndpointStatusByKey(testEndpoint.Key(), paging.NewEndpointStatusParams().WithResults(1, common.MaximumNumberOfResults*5).WithEvents(1, common.MaximumNumberOfEvents*5)) + if len(ss.Results) > resultsCleanUpThreshold+1 { + t.Errorf("number of results shouldn't have exceeded %d, reached %d", resultsCleanUpThreshold, len(ss.Results)) + } + if len(ss.Events) > eventsCleanUpThreshold+1 { + t.Errorf("number of events shouldn't have exceeded %d, reached %d", eventsCleanUpThreshold, len(ss.Events)) + } + } +} + +func TestStore_InsertWithCaching(t *testing.T) { + store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_InsertWithCaching.db", true) + defer store.Close() + // Add 2 results + store.Insert(&testEndpoint, &testSuccessfulResult) + store.Insert(&testEndpoint, &testSuccessfulResult) + // Verify that they exist + endpointStatuses, _ := store.GetAllEndpointStatuses(paging.NewEndpointStatusParams().WithResults(1, 20)) + if numberOfEndpointStatuses := len(endpointStatuses); numberOfEndpointStatuses != 1 { + t.Fatalf("expected 1 EndpointStatus, got %d", numberOfEndpointStatuses) + } + if len(endpointStatuses[0].Results) != 2 { + t.Fatalf("expected 2 results, got %d", len(endpointStatuses[0].Results)) + } + // Add 2 more results + store.Insert(&testEndpoint, &testUnsuccessfulResult) + store.Insert(&testEndpoint, &testUnsuccessfulResult) + // Verify that they exist + endpointStatuses, _ = store.GetAllEndpointStatuses(paging.NewEndpointStatusParams().WithResults(1, 20)) + if numberOfEndpointStatuses := len(endpointStatuses); numberOfEndpointStatuses != 1 { + t.Fatalf("expected 1 EndpointStatus, got %d", numberOfEndpointStatuses) + } + if len(endpointStatuses[0].Results) != 4 { + t.Fatalf("expected 4 results, got %d", len(endpointStatuses[0].Results)) + } + // Clear the store, which should also clear the cache + store.Clear() + // Verify that they no longer exist + endpointStatuses, _ = store.GetAllEndpointStatuses(paging.NewEndpointStatusParams().WithResults(1, 20)) + if numberOfEndpointStatuses := len(endpointStatuses); numberOfEndpointStatuses != 0 { + t.Fatalf("expected 0 EndpointStatus, got %d", numberOfEndpointStatuses) + } +} + +func TestStore_Persistence(t *testing.T) { + path := t.TempDir() + "/TestStore_Persistence.db" + store, _ := NewStore("sqlite", path, false) + store.Insert(&testEndpoint, &testSuccessfulResult) + store.Insert(&testEndpoint, &testUnsuccessfulResult) + if uptime, _ := store.GetUptimeByKey(testEndpoint.Key(), time.Now().Add(-time.Hour), time.Now()); uptime != 0.5 { + t.Errorf("the uptime over the past 1h should've been 0.5, got %f", uptime) + } + if uptime, _ := store.GetUptimeByKey(testEndpoint.Key(), time.Now().Add(-time.Hour*24), time.Now()); uptime != 0.5 { + t.Errorf("the uptime over the past 24h should've been 0.5, got %f", uptime) + } + if uptime, _ := store.GetUptimeByKey(testEndpoint.Key(), time.Now().Add(-time.Hour*24*7), time.Now()); uptime != 0.5 { + t.Errorf("the uptime over the past 7d should've been 0.5, got %f", uptime) + } + if uptime, _ := store.GetUptimeByKey(testEndpoint.Key(), time.Now().Add(-time.Hour*24*30), time.Now()); uptime != 0.5 { + t.Errorf("the uptime over the past 30d should've been 0.5, got %f", uptime) + } + ssFromOldStore, _ := store.GetEndpointStatus(testEndpoint.Group, testEndpoint.Name, paging.NewEndpointStatusParams().WithResults(1, common.MaximumNumberOfResults).WithEvents(1, common.MaximumNumberOfEvents)) + if ssFromOldStore == nil || ssFromOldStore.Group != "group" || ssFromOldStore.Name != "name" || len(ssFromOldStore.Events) != 3 || len(ssFromOldStore.Results) != 2 { + store.Close() + t.Fatal("sanity check failed") + } + store.Close() + store, _ = NewStore("sqlite", path, false) + defer store.Close() + ssFromNewStore, _ := store.GetEndpointStatus(testEndpoint.Group, testEndpoint.Name, paging.NewEndpointStatusParams().WithResults(1, common.MaximumNumberOfResults).WithEvents(1, common.MaximumNumberOfEvents)) + if ssFromNewStore == nil || ssFromNewStore.Group != "group" || ssFromNewStore.Name != "name" || len(ssFromNewStore.Events) != 3 || len(ssFromNewStore.Results) != 2 { + t.Fatal("failed sanity check") + } + if ssFromNewStore == ssFromOldStore { + t.Fatal("ss from the old and new store should have a different memory address") + } + for i := range ssFromNewStore.Events { + if ssFromNewStore.Events[i].Timestamp != ssFromOldStore.Events[i].Timestamp { + t.Error("new and old should've been the same") + } + if ssFromNewStore.Events[i].Type != ssFromOldStore.Events[i].Type { + t.Error("new and old should've been the same") + } + } + for i := range ssFromOldStore.Results { + if ssFromNewStore.Results[i].Timestamp != ssFromOldStore.Results[i].Timestamp { + t.Error("new and old should've been the same") + } + if ssFromNewStore.Results[i].Success != ssFromOldStore.Results[i].Success { + t.Error("new and old should've been the same") + } + if ssFromNewStore.Results[i].Connected != ssFromOldStore.Results[i].Connected { + t.Error("new and old should've been the same") + } + if ssFromNewStore.Results[i].IP != ssFromOldStore.Results[i].IP { + t.Error("new and old should've been the same") + } + if ssFromNewStore.Results[i].Hostname != ssFromOldStore.Results[i].Hostname { + t.Error("new and old should've been the same") + } + if ssFromNewStore.Results[i].HTTPStatus != ssFromOldStore.Results[i].HTTPStatus { + t.Error("new and old should've been the same") + } + if ssFromNewStore.Results[i].DNSRCode != ssFromOldStore.Results[i].DNSRCode { + t.Error("new and old should've been the same") + } + if len(ssFromNewStore.Results[i].Errors) != len(ssFromOldStore.Results[i].Errors) { + t.Error("new and old should've been the same") + } else { + for j := range ssFromOldStore.Results[i].Errors { + if ssFromNewStore.Results[i].Errors[j] != ssFromOldStore.Results[i].Errors[j] { + t.Error("new and old should've been the same") + } + } + } + if len(ssFromNewStore.Results[i].ConditionResults) != len(ssFromOldStore.Results[i].ConditionResults) { + t.Error("new and old should've been the same") + } else { + for j := range ssFromOldStore.Results[i].ConditionResults { + if ssFromNewStore.Results[i].ConditionResults[j].Condition != ssFromOldStore.Results[i].ConditionResults[j].Condition { + t.Error("new and old should've been the same") + } + if ssFromNewStore.Results[i].ConditionResults[j].Success != ssFromOldStore.Results[i].ConditionResults[j].Success { + t.Error("new and old should've been the same") + } + } + } + } +} + +func TestStore_Save(t *testing.T) { + store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_Save.db", false) + defer store.Close() + if store.Save() != nil { + t.Error("Save shouldn't do anything for this store") + } +} + +// Note that are much more extensive tests in /storage/store/store_test.go. +// This test is simply an extra sanity check +func TestStore_SanityCheck(t *testing.T) { + store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_SanityCheck.db", false) + defer store.Close() + store.Insert(&testEndpoint, &testSuccessfulResult) + endpointStatuses, _ := store.GetAllEndpointStatuses(paging.NewEndpointStatusParams()) + if numberOfEndpointStatuses := len(endpointStatuses); numberOfEndpointStatuses != 1 { + t.Fatalf("expected 1 EndpointStatus, got %d", numberOfEndpointStatuses) + } + store.Insert(&testEndpoint, &testUnsuccessfulResult) + // Both results inserted are for the same endpoint, therefore, the count shouldn't have increased + endpointStatuses, _ = store.GetAllEndpointStatuses(paging.NewEndpointStatusParams()) + if numberOfEndpointStatuses := len(endpointStatuses); numberOfEndpointStatuses != 1 { + t.Fatalf("expected 1 EndpointStatus, got %d", numberOfEndpointStatuses) + } + if hourlyAverageResponseTime, err := store.GetHourlyAverageResponseTimeByKey(testEndpoint.Key(), time.Now().Add(-24*time.Hour), time.Now()); err != nil { + t.Errorf("expected no error, got %v", err) + } else if len(hourlyAverageResponseTime) != 1 { + t.Errorf("expected 1 hour to have had a result in the past 24 hours, got %d", len(hourlyAverageResponseTime)) + } + if uptime, _ := store.GetUptimeByKey(testEndpoint.Key(), time.Now().Add(-24*time.Hour), time.Now()); uptime != 0.5 { + t.Errorf("expected uptime of last 24h to be 0.5, got %f", uptime) + } + if averageResponseTime, _ := store.GetAverageResponseTimeByKey(testEndpoint.Key(), time.Now().Add(-24*time.Hour), time.Now()); averageResponseTime != 450 { + t.Errorf("expected average response time of last 24h to be 450, got %d", averageResponseTime) + } + ss, _ := store.GetEndpointStatus(testEndpoint.Group, testEndpoint.Name, paging.NewEndpointStatusParams().WithResults(1, 20).WithEvents(1, 20)) + if ss == nil { + t.Fatalf("Store should've had key '%s', but didn't", testEndpoint.Key()) + } + if len(ss.Events) != 3 { + t.Errorf("Endpoint '%s' should've had 3 events, got %d", ss.Name, len(ss.Events)) + } + if len(ss.Results) != 2 { + t.Errorf("Endpoint '%s' should've had 2 results, got %d", ss.Name, len(ss.Results)) + } + if deleted := store.DeleteAllEndpointStatusesNotInKeys([]string{"invalid-key-which-means-everything-should-get-deleted"}); deleted != 1 { + t.Errorf("%d entries should've been deleted, got %d", 1, deleted) + } + if deleted := store.DeleteAllEndpointStatusesNotInKeys([]string{}); deleted != 0 { + t.Errorf("There should've been no entries left to delete, got %d", deleted) + } +} + +// TestStore_InvalidTransaction tests what happens if an invalid transaction is passed as parameter +func TestStore_InvalidTransaction(t *testing.T) { + store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_InvalidTransaction.db", false) + defer store.Close() + tx, _ := store.db.Begin() + tx.Commit() + if _, err := store.insertEndpoint(tx, &testEndpoint); err == nil { + t.Error("should've returned an error, because the transaction was already committed") + } + if err := store.insertEndpointEvent(tx, 1, endpoint.NewEventFromResult(&testSuccessfulResult)); err == nil { + t.Error("should've returned an error, because the transaction was already committed") + } + if err := store.insertEndpointResult(tx, 1, &testSuccessfulResult); err == nil { + t.Error("should've returned an error, because the transaction was already committed") + } + if err := store.insertConditionResults(tx, 1, testSuccessfulResult.ConditionResults); err == nil { + t.Error("should've returned an error, because the transaction was already committed") + } + if err := store.updateEndpointUptime(tx, 1, &testSuccessfulResult); err == nil { + t.Error("should've returned an error, because the transaction was already committed") + } + if _, err := store.getAllEndpointKeys(tx); err == nil { + t.Error("should've returned an error, because the transaction was already committed") + } + if _, err := store.getEndpointStatusByKey(tx, testEndpoint.Key(), paging.NewEndpointStatusParams().WithResults(1, 20)); err == nil { + t.Error("should've returned an error, because the transaction was already committed") + } + if _, err := store.getEndpointEventsByEndpointID(tx, 1, 1, 50); err == nil { + t.Error("should've returned an error, because the transaction was already committed") + } + if _, err := store.getEndpointResultsByEndpointID(tx, 1, 1, 50); err == nil { + t.Error("should've returned an error, because the transaction was already committed") + } + if err := store.deleteOldEndpointEvents(tx, 1); err == nil { + t.Error("should've returned an error, because the transaction was already committed") + } + if err := store.deleteOldEndpointResults(tx, 1); err == nil { + t.Error("should've returned an error, because the transaction was already committed") + } + if _, _, err := store.getEndpointUptime(tx, 1, time.Now(), time.Now()); err == nil { + t.Error("should've returned an error, because the transaction was already committed") + } + if _, err := store.getEndpointID(tx, &testEndpoint); err == nil { + t.Error("should've returned an error, because the transaction was already committed") + } + if _, err := store.getNumberOfEventsByEndpointID(tx, 1); err == nil { + t.Error("should've returned an error, because the transaction was already committed") + } + if _, err := store.getNumberOfResultsByEndpointID(tx, 1); err == nil { + t.Error("should've returned an error, because the transaction was already committed") + } + if _, err := store.getAgeOfOldestEndpointUptimeEntry(tx, 1); err == nil { + t.Error("should've returned an error, because the transaction was already committed") + } + if _, err := store.getLastEndpointResultSuccessValue(tx, 1); err == nil { + t.Error("should've returned an error, because the transaction was already committed") + } +} + +func TestStore_NoRows(t *testing.T) { + store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_NoRows.db", false) + defer store.Close() + tx, _ := store.db.Begin() + defer tx.Rollback() + if _, err := store.getLastEndpointResultSuccessValue(tx, 1); !errors.Is(err, errNoRowsReturned) { + t.Errorf("should've %v, got %v", errNoRowsReturned, err) + } + if _, err := store.getAgeOfOldestEndpointUptimeEntry(tx, 1); !errors.Is(err, errNoRowsReturned) { + t.Errorf("should've %v, got %v", errNoRowsReturned, err) + } +} + +// This tests very unlikely cases where a table is deleted. +func TestStore_BrokenSchema(t *testing.T) { + store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_BrokenSchema.db", false) + defer store.Close() + if err := store.Insert(&testEndpoint, &testSuccessfulResult); err != nil { + t.Fatal("expected no error, got", err.Error()) + } + if _, err := store.GetAverageResponseTimeByKey(testEndpoint.Key(), time.Now().Add(-time.Hour), time.Now()); err != nil { + t.Fatal("expected no error, got", err.Error()) + } + if _, err := store.GetAllEndpointStatuses(paging.NewEndpointStatusParams()); err != nil { + t.Fatal("expected no error, got", err.Error()) + } + // Break + _, _ = store.db.Exec("DROP TABLE endpoints") + // And now we'll try to insert something in our broken schema + if err := store.Insert(&testEndpoint, &testSuccessfulResult); err == nil { + t.Fatal("expected an error") + } + if _, err := store.GetAverageResponseTimeByKey(testEndpoint.Key(), time.Now().Add(-time.Hour), time.Now()); err == nil { + t.Fatal("expected an error") + } + if _, err := store.GetHourlyAverageResponseTimeByKey(testEndpoint.Key(), time.Now().Add(-time.Hour), time.Now()); err == nil { + t.Fatal("expected an error") + } + if _, err := store.GetAllEndpointStatuses(paging.NewEndpointStatusParams()); err == nil { + t.Fatal("expected an error") + } + if _, err := store.GetUptimeByKey(testEndpoint.Key(), time.Now().Add(-time.Hour), time.Now()); err == nil { + t.Fatal("expected an error") + } + if _, err := store.GetEndpointStatusByKey(testEndpoint.Key(), paging.NewEndpointStatusParams()); err == nil { + t.Fatal("expected an error") + } + // Repair + if err := store.createSchema(); err != nil { + t.Fatal("schema should've been repaired") + } + store.Clear() + if err := store.Insert(&testEndpoint, &testSuccessfulResult); err != nil { + t.Fatal("expected no error, got", err.Error()) + } + // Break + _, _ = store.db.Exec("DROP TABLE endpoint_events") + if err := store.Insert(&testEndpoint, &testSuccessfulResult); err != nil { + t.Fatal("expected no error, because this should silently fails, got", err.Error()) + } + if _, err := store.GetAllEndpointStatuses(paging.NewEndpointStatusParams().WithResults(1, 1).WithEvents(1, 1)); err != nil { + t.Fatal("expected no error, because this should silently fail, got", err.Error()) + } + // Repair + if err := store.createSchema(); err != nil { + t.Fatal("schema should've been repaired") + } + store.Clear() + if err := store.Insert(&testEndpoint, &testSuccessfulResult); err != nil { + t.Fatal("expected no error, got", err.Error()) + } + // Break + _, _ = store.db.Exec("DROP TABLE endpoint_results") + if err := store.Insert(&testEndpoint, &testSuccessfulResult); err == nil { + t.Fatal("expected an error") + } + if _, err := store.GetAllEndpointStatuses(paging.NewEndpointStatusParams().WithResults(1, 1).WithEvents(1, 1)); err != nil { + t.Fatal("expected no error, because this should silently fail, got", err.Error()) + } + // Repair + if err := store.createSchema(); err != nil { + t.Fatal("schema should've been repaired") + } + store.Clear() + if err := store.Insert(&testEndpoint, &testSuccessfulResult); err != nil { + t.Fatal("expected no error, got", err.Error()) + } + // Break + _, _ = store.db.Exec("DROP TABLE endpoint_result_conditions") + if err := store.Insert(&testEndpoint, &testSuccessfulResult); err == nil { + t.Fatal("expected an error") + } + // Repair + if err := store.createSchema(); err != nil { + t.Fatal("schema should've been repaired") + } + store.Clear() + if err := store.Insert(&testEndpoint, &testSuccessfulResult); err != nil { + t.Fatal("expected no error, got", err.Error()) + } + // Break + _, _ = store.db.Exec("DROP TABLE endpoint_uptimes") + if err := store.Insert(&testEndpoint, &testSuccessfulResult); err != nil { + t.Fatal("expected no error, because this should silently fails, got", err.Error()) + } + if _, err := store.GetAverageResponseTimeByKey(testEndpoint.Key(), time.Now().Add(-time.Hour), time.Now()); err == nil { + t.Fatal("expected an error") + } + if _, err := store.GetHourlyAverageResponseTimeByKey(testEndpoint.Key(), time.Now().Add(-time.Hour), time.Now()); err == nil { + t.Fatal("expected an error") + } + if _, err := store.GetUptimeByKey(testEndpoint.Key(), time.Now().Add(-time.Hour), time.Now()); err == nil { + t.Fatal("expected an error") + } +} + +func TestCacheKey(t *testing.T) { + scenarios := []struct { + endpointKey string + params paging.EndpointStatusParams + overrideCacheKey string + expectedCacheKey string + wantErr bool + }{ + { + endpointKey: "simple", + params: paging.EndpointStatusParams{EventsPage: 1, EventsPageSize: 2, ResultsPage: 3, ResultsPageSize: 4}, + expectedCacheKey: "simple-1-2-3-4", + wantErr: false, + }, + { + endpointKey: "with-hyphen", + params: paging.EndpointStatusParams{EventsPage: 0, EventsPageSize: 0, ResultsPage: 1, ResultsPageSize: 20}, + expectedCacheKey: "with-hyphen-0-0-1-20", + wantErr: false, + }, + { + endpointKey: "with-multiple-hyphens", + params: paging.EndpointStatusParams{EventsPage: 0, EventsPageSize: 0, ResultsPage: 2, ResultsPageSize: 20}, + expectedCacheKey: "with-multiple-hyphens-0-0-2-20", + wantErr: false, + }, + { + overrideCacheKey: "invalid-a-2-3-4", + wantErr: true, + }, + { + overrideCacheKey: "invalid-1-a-3-4", + wantErr: true, + }, + { + overrideCacheKey: "invalid-1-2-a-4", + wantErr: true, + }, + { + overrideCacheKey: "invalid-1-2-3-a", + wantErr: true, + }, + { + overrideCacheKey: "notenoughhyphen1-2-3-4", + wantErr: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.expectedCacheKey+scenario.overrideCacheKey, func(t *testing.T) { + var cacheKey string + if len(scenario.overrideCacheKey) > 0 { + cacheKey = scenario.overrideCacheKey + } else { + cacheKey = generateCacheKey(scenario.endpointKey, &scenario.params) + if cacheKey != scenario.expectedCacheKey { + t.Errorf("expected %s, got %s", scenario.expectedCacheKey, cacheKey) + } + } + extractedEndpointKey, extractedParams, err := extractKeyAndParamsFromCacheKey(cacheKey) + if (err != nil) != scenario.wantErr { + t.Errorf("expected error %v, got %v", scenario.wantErr, err) + return + } + if err != nil { + // If there's an error, we don't need to check the extracted values + return + } + if extractedEndpointKey != scenario.endpointKey { + t.Errorf("expected endpointKey %s, got %s", scenario.endpointKey, extractedEndpointKey) + } + if extractedParams.EventsPage != scenario.params.EventsPage { + t.Errorf("expected EventsPage %d, got %d", scenario.params.EventsPage, extractedParams.EventsPage) + } + if extractedParams.EventsPageSize != scenario.params.EventsPageSize { + t.Errorf("expected EventsPageSize %d, got %d", scenario.params.EventsPageSize, extractedParams.EventsPageSize) + } + if extractedParams.ResultsPage != scenario.params.ResultsPage { + t.Errorf("expected ResultsPage %d, got %d", scenario.params.ResultsPage, extractedParams.ResultsPage) + } + if extractedParams.ResultsPageSize != scenario.params.ResultsPageSize { + t.Errorf("expected ResultsPageSize %d, got %d", scenario.params.ResultsPageSize, extractedParams.ResultsPageSize) + } + }) + } +} + +func TestTriggeredEndpointAlertsPersistence(t *testing.T) { + store, _ := NewStore("sqlite", t.TempDir()+"/TestTriggeredEndpointAlertsPersistence.db", false) + defer store.Close() + yes, desc := false, "description" + ep := testEndpoint + ep.NumberOfSuccessesInARow = 0 + alrt := &alert.Alert{ + Type: alert.TypePagerDuty, + Enabled: &yes, + FailureThreshold: 4, + SuccessThreshold: 2, + Description: &desc, + SendOnResolved: &yes, + Triggered: true, + ResolveKey: "1234567", + } + // Alert just triggered, so NumberOfSuccessesInARow is 0 + if err := store.UpsertTriggeredEndpointAlert(&ep, alrt); err != nil { + t.Fatal("expected no error, got", err.Error()) + } + exists, resolveKey, numberOfSuccessesInARow, err := store.GetTriggeredEndpointAlert(&ep, alrt) + if err != nil { + t.Fatal("expected no error, got", err.Error()) + } + if !exists { + t.Error("expected triggered alert to exist") + } + if resolveKey != alrt.ResolveKey { + t.Errorf("expected resolveKey %s, got %s", alrt.ResolveKey, resolveKey) + } + if numberOfSuccessesInARow != ep.NumberOfSuccessesInARow { + t.Errorf("expected persisted NumberOfSuccessesInARow to be %d, got %d", ep.NumberOfSuccessesInARow, numberOfSuccessesInARow) + } + // Endpoint just had a successful evaluation, so NumberOfSuccessesInARow is now 1 + ep.NumberOfSuccessesInARow++ + if err := store.UpsertTriggeredEndpointAlert(&ep, alrt); err != nil { + t.Fatal("expected no error, got", err.Error()) + } + exists, resolveKey, numberOfSuccessesInARow, err = store.GetTriggeredEndpointAlert(&ep, alrt) + if err != nil { + t.Error("expected no error, got", err.Error()) + } + if !exists { + t.Error("expected triggered alert to exist") + } + if resolveKey != alrt.ResolveKey { + t.Errorf("expected resolveKey %s, got %s", alrt.ResolveKey, resolveKey) + } + if numberOfSuccessesInARow != ep.NumberOfSuccessesInARow { + t.Errorf("expected persisted NumberOfSuccessesInARow to be %d, got %d", ep.NumberOfSuccessesInARow, numberOfSuccessesInARow) + } + // Simulate the endpoint having another successful evaluation, which means the alert is now resolved, + // and we should delete the triggered alert from the store + ep.NumberOfSuccessesInARow++ + if err := store.DeleteTriggeredEndpointAlert(&ep, alrt); err != nil { + t.Fatal("expected no error, got", err.Error()) + } + exists, _, _, err = store.GetTriggeredEndpointAlert(&ep, alrt) + if err != nil { + t.Error("expected no error, got", err.Error()) + } + if exists { + t.Error("expected triggered alert to no longer exist as it has been deleted") + } +} + +func TestStore_DeleteAllTriggeredAlertsNotInChecksumsByEndpoint(t *testing.T) { + store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_DeleteAllTriggeredAlertsNotInChecksumsByEndpoint.db", false) + defer store.Close() + yes, desc := false, "description" + ep1 := testEndpoint + ep1.Name = "ep1" + ep2 := testEndpoint + ep2.Name = "ep2" + alert1 := alert.Alert{ + Type: alert.TypePagerDuty, + Enabled: &yes, + FailureThreshold: 4, + SuccessThreshold: 2, + Description: &desc, + SendOnResolved: &yes, + Triggered: true, + ResolveKey: "1234567", + } + alert2 := alert1 + alert2.Type, alert2.ResolveKey = alert.TypeSlack, "" + alert3 := alert2 + if err := store.UpsertTriggeredEndpointAlert(&ep1, &alert1); err != nil { + t.Fatal("expected no error, got", err.Error()) + } + if err := store.UpsertTriggeredEndpointAlert(&ep1, &alert2); err != nil { + t.Fatal("expected no error, got", err.Error()) + } + if err := store.UpsertTriggeredEndpointAlert(&ep2, &alert3); err != nil { + t.Fatal("expected no error, got", err.Error()) + } + if exists, _, _, _ := store.GetTriggeredEndpointAlert(&ep1, &alert1); !exists { + t.Error("expected alert1 to have been deleted") + } + if exists, _, _, _ := store.GetTriggeredEndpointAlert(&ep1, &alert2); !exists { + t.Error("expected alert2 to exist for ep1") + } + if exists, _, _, _ := store.GetTriggeredEndpointAlert(&ep2, &alert3); !exists { + t.Error("expected alert3 to exist for ep2") + } + // Now we simulate the alert configuration being updated, and the alert being resolved + if deleted := store.DeleteAllTriggeredAlertsNotInChecksumsByEndpoint(&ep1, []string{alert2.Checksum()}); deleted != 1 { + t.Errorf("expected 1 triggered alert to be deleted, got %d", deleted) + } + if exists, _, _, _ := store.GetTriggeredEndpointAlert(&ep1, &alert1); exists { + t.Error("expected alert1 to have been deleted") + } + if exists, _, _, _ := store.GetTriggeredEndpointAlert(&ep1, &alert2); !exists { + t.Error("expected alert2 to exist for ep1") + } + if exists, _, _, _ := store.GetTriggeredEndpointAlert(&ep2, &alert3); !exists { + t.Error("expected alert3 to exist for ep2") + } + // Now let's just assume all alerts for ep1 were removed + if deleted := store.DeleteAllTriggeredAlertsNotInChecksumsByEndpoint(&ep1, []string{}); deleted != 1 { + t.Errorf("expected 1 triggered alert to be deleted, got %d", deleted) + } + // Make sure the alert for ep2 still exists + if exists, _, _, _ := store.GetTriggeredEndpointAlert(&ep2, &alert3); !exists { + t.Error("expected alert3 to exist for ep2") + } +} diff --git a/storage/store/store.go b/storage/store/store.go new file mode 100644 index 00000000..729e6482 --- /dev/null +++ b/storage/store/store.go @@ -0,0 +1,150 @@ +package store + +import ( + "context" + "time" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/storage" + "github.com/TwiN/gatus/v5/storage/store/common/paging" + "github.com/TwiN/gatus/v5/storage/store/memory" + "github.com/TwiN/gatus/v5/storage/store/sql" + "github.com/TwiN/logr" +) + +// Store is the interface that each store should implement +type Store interface { + // GetAllEndpointStatuses returns the JSON encoding of all monitored endpoint.Status + // with a subset of endpoint.Result defined by the page and pageSize parameters + GetAllEndpointStatuses(params *paging.EndpointStatusParams) ([]*endpoint.Status, error) + + // GetEndpointStatus returns the endpoint status for a given endpoint name in the given group + GetEndpointStatus(groupName, endpointName string, params *paging.EndpointStatusParams) (*endpoint.Status, error) + + // GetEndpointStatusByKey returns the endpoint status for a given key + GetEndpointStatusByKey(key string, params *paging.EndpointStatusParams) (*endpoint.Status, error) + + // GetUptimeByKey returns the uptime percentage during a time range + GetUptimeByKey(key string, from, to time.Time) (float64, error) + + // GetAverageResponseTimeByKey returns the average response time in milliseconds (value) during a time range + GetAverageResponseTimeByKey(key string, from, to time.Time) (int, error) + + // GetHourlyAverageResponseTimeByKey returns a map of hourly (key) average response time in milliseconds (value) during a time range + GetHourlyAverageResponseTimeByKey(key string, from, to time.Time) (map[int64]int, error) + + // Insert adds the observed result for the specified endpoint into the store + Insert(ep *endpoint.Endpoint, result *endpoint.Result) error + + // DeleteAllEndpointStatusesNotInKeys removes all Status that are not within the keys provided + // + // Used to delete endpoints that have been persisted but are no longer part of the configured endpoints + DeleteAllEndpointStatusesNotInKeys(keys []string) int + + // GetTriggeredEndpointAlert returns whether the triggered alert for the specified endpoint as well as the necessary information to resolve it + GetTriggeredEndpointAlert(ep *endpoint.Endpoint, alert *alert.Alert) (exists bool, resolveKey string, numberOfSuccessesInARow int, err error) + + // UpsertTriggeredEndpointAlert inserts/updates a triggered alert for an endpoint + // Used for persistence of triggered alerts across application restarts + UpsertTriggeredEndpointAlert(ep *endpoint.Endpoint, triggeredAlert *alert.Alert) error + + // DeleteTriggeredEndpointAlert deletes a triggered alert for an endpoint + DeleteTriggeredEndpointAlert(ep *endpoint.Endpoint, triggeredAlert *alert.Alert) error + + // DeleteAllTriggeredAlertsNotInChecksumsByEndpoint removes all triggered alerts owned by an endpoint whose alert + // configurations are not provided in the checksums list. + // This prevents triggered alerts that have been removed or modified from lingering in the database. + DeleteAllTriggeredAlertsNotInChecksumsByEndpoint(ep *endpoint.Endpoint, checksums []string) int + + // Clear deletes everything from the store + Clear() + + // Save persists the data if and where it needs to be persisted + Save() error + + // Close terminates every connection and closes the store, if applicable. + // Should only be used before stopping the application. + Close() +} + +// TODO: add method to check state of store (by keeping track of silent errors) + +var ( + // Validate interface implementation on compile + _ Store = (*memory.Store)(nil) + _ Store = (*sql.Store)(nil) +) + +var ( + store Store + + // initialized keeps track of whether the storage provider was initialized + // Because store.Store is an interface, a nil check wouldn't be sufficient, so instead of doing reflection + // every single time Get is called, we'll just lazily keep track of its existence through this variable + initialized bool + + ctx context.Context + cancelFunc context.CancelFunc +) + +func Get() Store { + if !initialized { + // This only happens in tests + logr.Info("[store.Get] Provider requested before it was initialized, automatically initializing") + err := Initialize(nil) + if err != nil { + panic("failed to automatically initialize store: " + err.Error()) + } + } + return store +} + +// Initialize instantiates the storage provider based on the Config provider +func Initialize(cfg *storage.Config) error { + initialized = true + var err error + if cancelFunc != nil { + // Stop the active autoSave task, if there's already one + cancelFunc() + } + if cfg == nil { + // This only happens in tests + logr.Warn("[store.Initialize] nil storage config passed as parameter. This should only happen in tests. Defaulting to an empty config.") + cfg = &storage.Config{} + } + if len(cfg.Path) == 0 && cfg.Type != storage.TypePostgres { + logr.Infof("[store.Initialize] Creating storage provider of type=%s", cfg.Type) + } + ctx, cancelFunc = context.WithCancel(context.Background()) + switch cfg.Type { + case storage.TypeSQLite, storage.TypePostgres: + store, err = sql.NewStore(string(cfg.Type), cfg.Path, cfg.Caching) + if err != nil { + return err + } + case storage.TypeMemory: + fallthrough + default: + store, _ = memory.NewStore() + } + return nil +} + +// autoSave automatically calls the Save function of the provider at every interval +func autoSave(ctx context.Context, store Store, interval time.Duration) { + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + logr.Info("[store.autoSave] Stopping active job") + return + case <-ticker.C: + logr.Info("[store.autoSave] Saving") + if err := store.Save(); err != nil { + logr.Errorf("[store.autoSave] Save failed: %s", err.Error()) + } + } + } +} diff --git a/storage/store/store_bench_test.go b/storage/store/store_bench_test.go new file mode 100644 index 00000000..0ed4c2db --- /dev/null +++ b/storage/store/store_bench_test.go @@ -0,0 +1,213 @@ +package store + +import ( + "strconv" + "testing" + "time" + + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/storage/store/common/paging" + "github.com/TwiN/gatus/v5/storage/store/memory" + "github.com/TwiN/gatus/v5/storage/store/sql" +) + +func BenchmarkStore_GetAllEndpointStatuses(b *testing.B) { + memoryStore, err := memory.NewStore() + if err != nil { + b.Fatal("failed to create store:", err.Error()) + } + sqliteStore, err := sql.NewStore("sqlite", b.TempDir()+"/BenchmarkStore_GetAllEndpointStatuses.db", false) + if err != nil { + b.Fatal("failed to create store:", err.Error()) + } + defer sqliteStore.Close() + type Scenario struct { + Name string + Store Store + Parallel bool + } + scenarios := []Scenario{ + { + Name: "memory", + Store: memoryStore, + Parallel: false, + }, + { + Name: "memory-parallel", + Store: memoryStore, + Parallel: true, + }, + { + Name: "sqlite", + Store: sqliteStore, + Parallel: false, + }, + { + Name: "sqlite-parallel", + Store: sqliteStore, + Parallel: true, + }, + } + for _, scenario := range scenarios { + numberOfEndpoints := []int{10, 25, 50, 100} + for _, numberOfEndpointsToCreate := range numberOfEndpoints { + // Create endpoints and insert results + for i := 0; i < numberOfEndpointsToCreate; i++ { + ep := testEndpoint + ep.Name = "endpoint" + strconv.Itoa(i) + // Insert 20 results for each endpoint + for j := 0; j < 20; j++ { + scenario.Store.Insert(&ep, &testSuccessfulResult) + } + } + // Run the scenarios + b.Run(scenario.Name+"-with-"+strconv.Itoa(numberOfEndpointsToCreate)+"-endpoints", func(b *testing.B) { + if scenario.Parallel { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _, _ = scenario.Store.GetAllEndpointStatuses(paging.NewEndpointStatusParams().WithResults(1, 20)) + } + }) + } else { + for n := 0; n < b.N; n++ { + _, _ = scenario.Store.GetAllEndpointStatuses(paging.NewEndpointStatusParams().WithResults(1, 20)) + } + } + b.ReportAllocs() + }) + scenario.Store.Clear() + } + } +} + +func BenchmarkStore_Insert(b *testing.B) { + memoryStore, err := memory.NewStore() + if err != nil { + b.Fatal("failed to create store:", err.Error()) + } + sqliteStore, err := sql.NewStore("sqlite", b.TempDir()+"/BenchmarkStore_Insert.db", false) + if err != nil { + b.Fatal("failed to create store:", err.Error()) + } + defer sqliteStore.Close() + type Scenario struct { + Name string + Store Store + Parallel bool + } + scenarios := []Scenario{ + { + Name: "memory", + Store: memoryStore, + Parallel: false, + }, + { + Name: "memory-parallel", + Store: memoryStore, + Parallel: true, + }, + { + Name: "sqlite", + Store: sqliteStore, + Parallel: false, + }, + { + Name: "sqlite-parallel", + Store: sqliteStore, + Parallel: false, + }, + } + for _, scenario := range scenarios { + b.Run(scenario.Name, func(b *testing.B) { + if scenario.Parallel { + b.RunParallel(func(pb *testing.PB) { + n := 0 + for pb.Next() { + var result endpoint.Result + if n%10 == 0 { + result = testUnsuccessfulResult + } else { + result = testSuccessfulResult + } + result.Timestamp = time.Now() + scenario.Store.Insert(&testEndpoint, &result) + n++ + } + }) + } else { + for n := 0; n < b.N; n++ { + var result endpoint.Result + if n%10 == 0 { + result = testUnsuccessfulResult + } else { + result = testSuccessfulResult + } + result.Timestamp = time.Now() + scenario.Store.Insert(&testEndpoint, &result) + } + } + b.ReportAllocs() + scenario.Store.Clear() + }) + } +} + +func BenchmarkStore_GetEndpointStatusByKey(b *testing.B) { + memoryStore, err := memory.NewStore() + if err != nil { + b.Fatal("failed to create store:", err.Error()) + } + sqliteStore, err := sql.NewStore("sqlite", b.TempDir()+"/BenchmarkStore_GetEndpointStatusByKey.db", false) + if err != nil { + b.Fatal("failed to create store:", err.Error()) + } + defer sqliteStore.Close() + type Scenario struct { + Name string + Store Store + Parallel bool + } + scenarios := []Scenario{ + { + Name: "memory", + Store: memoryStore, + Parallel: false, + }, + { + Name: "memory-parallel", + Store: memoryStore, + Parallel: true, + }, + { + Name: "sqlite", + Store: sqliteStore, + Parallel: false, + }, + { + Name: "sqlite-parallel", + Store: sqliteStore, + Parallel: true, + }, + } + for _, scenario := range scenarios { + for i := 0; i < 50; i++ { + scenario.Store.Insert(&testEndpoint, &testSuccessfulResult) + scenario.Store.Insert(&testEndpoint, &testUnsuccessfulResult) + } + b.Run(scenario.Name, func(b *testing.B) { + if scenario.Parallel { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + scenario.Store.GetEndpointStatusByKey(testEndpoint.Key(), paging.NewEndpointStatusParams().WithResults(1, 20)) + } + }) + } else { + for n := 0; n < b.N; n++ { + scenario.Store.GetEndpointStatusByKey(testEndpoint.Key(), paging.NewEndpointStatusParams().WithResults(1, 20)) + } + } + b.ReportAllocs() + }) + scenario.Store.Clear() + } +} diff --git a/storage/store/store_test.go b/storage/store/store_test.go new file mode 100644 index 00000000..6f18f446 --- /dev/null +++ b/storage/store/store_test.go @@ -0,0 +1,653 @@ +package store + +import ( + "errors" + "path/filepath" + "testing" + "time" + + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/storage" + "github.com/TwiN/gatus/v5/storage/store/common" + "github.com/TwiN/gatus/v5/storage/store/common/paging" + "github.com/TwiN/gatus/v5/storage/store/memory" + "github.com/TwiN/gatus/v5/storage/store/sql" +) + +var ( + firstCondition = endpoint.Condition("[STATUS] == 200") + secondCondition = endpoint.Condition("[RESPONSE_TIME] < 500") + thirdCondition = endpoint.Condition("[CERTIFICATE_EXPIRATION] < 72h") + + now = time.Now().Truncate(time.Hour) + + testEndpoint = endpoint.Endpoint{ + Name: "name", + Group: "group", + URL: "https://example.org/what/ever", + Method: "GET", + Body: "body", + Interval: 30 * time.Second, + Conditions: []endpoint.Condition{firstCondition, secondCondition, thirdCondition}, + Alerts: nil, + NumberOfFailuresInARow: 0, + NumberOfSuccessesInARow: 0, + } + testSuccessfulResult = endpoint.Result{ + Timestamp: now, + Success: true, + Hostname: "example.org", + IP: "127.0.0.1", + HTTPStatus: 200, + Errors: nil, + Connected: true, + Duration: 150 * time.Millisecond, + CertificateExpiration: 10 * time.Hour, + ConditionResults: []*endpoint.ConditionResult{ + { + Condition: "[STATUS] == 200", + Success: true, + }, + { + Condition: "[RESPONSE_TIME] < 500", + Success: true, + }, + { + Condition: "[CERTIFICATE_EXPIRATION] < 72h", + Success: true, + }, + }, + } + testUnsuccessfulResult = endpoint.Result{ + Timestamp: now, + Success: false, + Hostname: "example.org", + IP: "127.0.0.1", + HTTPStatus: 200, + Errors: []string{"error-1", "error-2"}, + Connected: true, + Duration: 750 * time.Millisecond, + CertificateExpiration: 10 * time.Hour, + ConditionResults: []*endpoint.ConditionResult{ + { + Condition: "[STATUS] == 200", + Success: true, + }, + { + Condition: "[RESPONSE_TIME] < 500", + Success: false, + }, + { + Condition: "[CERTIFICATE_EXPIRATION] < 72h", + Success: false, + }, + }, + } +) + +type Scenario struct { + Name string + Store Store +} + +func initStoresAndBaseScenarios(t *testing.T, testName string) []*Scenario { + memoryStore, err := memory.NewStore() + if err != nil { + t.Fatal("failed to create store:", err.Error()) + } + sqliteStore, err := sql.NewStore("sqlite", t.TempDir()+"/"+testName+".db", false) + if err != nil { + t.Fatal("failed to create store:", err.Error()) + } + sqliteStoreWithCaching, err := sql.NewStore("sqlite", t.TempDir()+"/"+testName+"-with-caching.db", true) + if err != nil { + t.Fatal("failed to create store:", err.Error()) + } + return []*Scenario{ + { + Name: "memory", + Store: memoryStore, + }, + { + Name: "sqlite", + Store: sqliteStore, + }, + { + Name: "sqlite-with-caching", + Store: sqliteStoreWithCaching, + }, + } +} + +func cleanUp(scenarios []*Scenario) { + for _, scenario := range scenarios { + scenario.Store.Close() + } +} + +func TestStore_GetEndpointStatusByKey(t *testing.T) { + scenarios := initStoresAndBaseScenarios(t, "TestStore_GetEndpointStatusByKey") + defer cleanUp(scenarios) + firstResult := testSuccessfulResult + firstResult.Timestamp = now.Add(-time.Minute) + secondResult := testUnsuccessfulResult + secondResult.Timestamp = now + thirdResult := testSuccessfulResult + thirdResult.Timestamp = now + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + scenario.Store.Insert(&testEndpoint, &firstResult) + scenario.Store.Insert(&testEndpoint, &secondResult) + endpointStatus, err := scenario.Store.GetEndpointStatusByKey(testEndpoint.Key(), paging.NewEndpointStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults)) + if err != nil { + t.Fatal("shouldn't have returned an error, got", err.Error()) + } + if endpointStatus == nil { + t.Fatalf("endpointStatus shouldn't have been nil") + } + if endpointStatus.Name != testEndpoint.Name { + t.Fatalf("endpointStatus.Name should've been %s, got %s", testEndpoint.Name, endpointStatus.Name) + } + if endpointStatus.Group != testEndpoint.Group { + t.Fatalf("endpointStatus.Group should've been %s, got %s", testEndpoint.Group, endpointStatus.Group) + } + if len(endpointStatus.Results) != 2 { + t.Fatalf("endpointStatus.Results should've had 2 entries") + } + if endpointStatus.Results[0].Timestamp.After(endpointStatus.Results[1].Timestamp) { + t.Error("The result at index 0 should've been older than the result at index 1") + } + scenario.Store.Insert(&testEndpoint, &thirdResult) + endpointStatus, err = scenario.Store.GetEndpointStatusByKey(testEndpoint.Key(), paging.NewEndpointStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults)) + if err != nil { + t.Fatal("shouldn't have returned an error, got", err.Error()) + } + if len(endpointStatus.Results) != 3 { + t.Fatalf("endpointStatus.Results should've had 3 entries") + } + scenario.Store.Clear() + }) + } +} + +func TestStore_GetEndpointStatusForMissingStatusReturnsNil(t *testing.T) { + scenarios := initStoresAndBaseScenarios(t, "TestStore_GetEndpointStatusForMissingStatusReturnsNil") + defer cleanUp(scenarios) + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + scenario.Store.Insert(&testEndpoint, &testSuccessfulResult) + endpointStatus, err := scenario.Store.GetEndpointStatus("nonexistantgroup", "nonexistantname", paging.NewEndpointStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults)) + if !errors.Is(err, common.ErrEndpointNotFound) { + t.Error("should've returned ErrEndpointNotFound, got", err) + } + if endpointStatus != nil { + t.Errorf("Returned endpoint status for group '%s' and name '%s' not nil after inserting the endpoint into the store", testEndpoint.Group, testEndpoint.Name) + } + endpointStatus, err = scenario.Store.GetEndpointStatus(testEndpoint.Group, "nonexistantname", paging.NewEndpointStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults)) + if !errors.Is(err, common.ErrEndpointNotFound) { + t.Error("should've returned ErrEndpointNotFound, got", err) + } + if endpointStatus != nil { + t.Errorf("Returned endpoint status for group '%s' and name '%s' not nil after inserting the endpoint into the store", testEndpoint.Group, "nonexistantname") + } + endpointStatus, err = scenario.Store.GetEndpointStatus("nonexistantgroup", testEndpoint.Name, paging.NewEndpointStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults)) + if !errors.Is(err, common.ErrEndpointNotFound) { + t.Error("should've returned ErrEndpointNotFound, got", err) + } + if endpointStatus != nil { + t.Errorf("Returned endpoint status for group '%s' and name '%s' not nil after inserting the endpoint into the store", "nonexistantgroup", testEndpoint.Name) + } + }) + } +} + +func TestStore_GetAllEndpointStatuses(t *testing.T) { + scenarios := initStoresAndBaseScenarios(t, "TestStore_GetAllEndpointStatuses") + defer cleanUp(scenarios) + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + scenario.Store.Insert(&testEndpoint, &testSuccessfulResult) + scenario.Store.Insert(&testEndpoint, &testUnsuccessfulResult) + endpointStatuses, err := scenario.Store.GetAllEndpointStatuses(paging.NewEndpointStatusParams().WithResults(1, 20)) + if err != nil { + t.Error("shouldn't have returned an error, got", err.Error()) + } + if len(endpointStatuses) != 1 { + t.Fatal("expected 1 endpoint status") + } + actual := endpointStatuses[0] + if actual == nil { + t.Fatal("expected endpoint status to exist") + } + if len(actual.Results) != 2 { + t.Error("expected 2 results, got", len(actual.Results)) + } + if len(actual.Events) != 0 { + t.Error("expected 0 events, got", len(actual.Events)) + } + scenario.Store.Clear() + }) + t.Run(scenario.Name+"-page-2", func(t *testing.T) { + otherEndpoint := testEndpoint + otherEndpoint.Name = testEndpoint.Name + "-other" + scenario.Store.Insert(&testEndpoint, &testSuccessfulResult) + scenario.Store.Insert(&otherEndpoint, &testSuccessfulResult) + scenario.Store.Insert(&otherEndpoint, &testSuccessfulResult) + scenario.Store.Insert(&otherEndpoint, &testSuccessfulResult) + endpointStatuses, err := scenario.Store.GetAllEndpointStatuses(paging.NewEndpointStatusParams().WithResults(2, 2)) + if err != nil { + t.Error("shouldn't have returned an error, got", err.Error()) + } + if len(endpointStatuses) != 2 { + t.Fatal("expected 2 endpoint statuses") + } + if endpointStatuses[0] == nil || endpointStatuses[1] == nil { + t.Fatal("expected endpoint status to exist") + } + if len(endpointStatuses[0].Results) != 0 { + t.Error("expected 0 results on the first endpoint, got", len(endpointStatuses[0].Results)) + } + if len(endpointStatuses[1].Results) != 1 { + t.Error("expected 1 result on the second endpoint, got", len(endpointStatuses[1].Results)) + } + if len(endpointStatuses[0].Events) != 0 { + t.Error("expected 0 events on the first endpoint, got", len(endpointStatuses[0].Events)) + } + if len(endpointStatuses[1].Events) != 0 { + t.Error("expected 0 events on the second endpoint, got", len(endpointStatuses[1].Events)) + } + scenario.Store.Clear() + }) + } +} + +func TestStore_GetAllEndpointStatusesWithResultsAndEvents(t *testing.T) { + scenarios := initStoresAndBaseScenarios(t, "TestStore_GetAllEndpointStatusesWithResultsAndEvents") + defer cleanUp(scenarios) + firstResult := testSuccessfulResult + secondResult := testUnsuccessfulResult + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + scenario.Store.Insert(&testEndpoint, &firstResult) + scenario.Store.Insert(&testEndpoint, &secondResult) + // Can't be bothered dealing with timezone issues on the worker that runs the automated tests + endpointStatuses, err := scenario.Store.GetAllEndpointStatuses(paging.NewEndpointStatusParams().WithResults(1, 20).WithEvents(1, 50)) + if err != nil { + t.Error("shouldn't have returned an error, got", err.Error()) + } + if len(endpointStatuses) != 1 { + t.Fatal("expected 1 endpoint status") + } + actual := endpointStatuses[0] + if actual == nil { + t.Fatal("expected endpoint status to exist") + } + if len(actual.Results) != 2 { + t.Error("expected 2 results, got", len(actual.Results)) + } + if len(actual.Events) != 3 { + t.Error("expected 3 events, got", len(actual.Events)) + } + scenario.Store.Clear() + }) + } +} + +func TestStore_GetEndpointStatusPage1IsHasMoreRecentResultsThanPage2(t *testing.T) { + scenarios := initStoresAndBaseScenarios(t, "TestStore_GetEndpointStatusPage1IsHasMoreRecentResultsThanPage2") + defer cleanUp(scenarios) + firstResult := testSuccessfulResult + firstResult.Timestamp = now.Add(-time.Minute) + secondResult := testUnsuccessfulResult + secondResult.Timestamp = now + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + scenario.Store.Insert(&testEndpoint, &firstResult) + scenario.Store.Insert(&testEndpoint, &secondResult) + endpointStatusPage1, err := scenario.Store.GetEndpointStatusByKey(testEndpoint.Key(), paging.NewEndpointStatusParams().WithResults(1, 1)) + if err != nil { + t.Error("shouldn't have returned an error, got", err.Error()) + } + if endpointStatusPage1 == nil { + t.Fatalf("endpointStatusPage1 shouldn't have been nil") + } + if len(endpointStatusPage1.Results) != 1 { + t.Fatalf("endpointStatusPage1 should've had 1 result") + } + endpointStatusPage2, err := scenario.Store.GetEndpointStatusByKey(testEndpoint.Key(), paging.NewEndpointStatusParams().WithResults(2, 1)) + if err != nil { + t.Error("shouldn't have returned an error, got", err.Error()) + } + if endpointStatusPage2 == nil { + t.Fatalf("endpointStatusPage2 shouldn't have been nil") + } + if len(endpointStatusPage2.Results) != 1 { + t.Fatalf("endpointStatusPage2 should've had 1 result") + } + // Compare the timestamp of both pages + if !endpointStatusPage1.Results[0].Timestamp.After(endpointStatusPage2.Results[0].Timestamp) { + t.Errorf("The result from the first page should've been more recent than the results from the second page") + } + scenario.Store.Clear() + }) + } +} + +func TestStore_GetUptimeByKey(t *testing.T) { + scenarios := initStoresAndBaseScenarios(t, "TestStore_GetUptimeByKey") + defer cleanUp(scenarios) + firstResult := testSuccessfulResult + firstResult.Timestamp = now.Add(-time.Minute) + secondResult := testUnsuccessfulResult + secondResult.Timestamp = now + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + if _, err := scenario.Store.GetUptimeByKey(testEndpoint.Key(), time.Now().Add(-time.Hour), time.Now()); err != common.ErrEndpointNotFound { + t.Errorf("should've returned not found because there's nothing yet, got %v", err) + } + scenario.Store.Insert(&testEndpoint, &firstResult) + scenario.Store.Insert(&testEndpoint, &secondResult) + if uptime, _ := scenario.Store.GetUptimeByKey(testEndpoint.Key(), now.Add(-time.Hour), time.Now()); uptime != 0.5 { + t.Errorf("the uptime over the past 1h should've been 0.5, got %f", uptime) + } + if uptime, _ := scenario.Store.GetUptimeByKey(testEndpoint.Key(), now.Add(-time.Hour*24), time.Now()); uptime != 0.5 { + t.Errorf("the uptime over the past 24h should've been 0.5, got %f", uptime) + } + if uptime, _ := scenario.Store.GetUptimeByKey(testEndpoint.Key(), now.Add(-time.Hour*24*7), time.Now()); uptime != 0.5 { + t.Errorf("the uptime over the past 7d should've been 0.5, got %f", uptime) + } + if _, err := scenario.Store.GetUptimeByKey(testEndpoint.Key(), now, time.Now().Add(-time.Hour)); err == nil { + t.Error("should've returned an error because the parameter 'from' cannot be older than 'to'") + } + }) + } +} + +func TestStore_GetAverageResponseTimeByKey(t *testing.T) { + scenarios := initStoresAndBaseScenarios(t, "TestStore_GetAverageResponseTimeByKey") + defer cleanUp(scenarios) + firstResult := testSuccessfulResult + firstResult.Timestamp = now.Add(-(2 * time.Hour)) + firstResult.Duration = 300 * time.Millisecond + secondResult := testSuccessfulResult + secondResult.Duration = 150 * time.Millisecond + secondResult.Timestamp = now.Add(-(1*time.Hour + 30*time.Minute)) + thirdResult := testUnsuccessfulResult + thirdResult.Duration = 200 * time.Millisecond + thirdResult.Timestamp = now.Add(-(1 * time.Hour)) + fourthResult := testSuccessfulResult + fourthResult.Duration = 500 * time.Millisecond + fourthResult.Timestamp = now + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + scenario.Store.Insert(&testEndpoint, &firstResult) + scenario.Store.Insert(&testEndpoint, &secondResult) + scenario.Store.Insert(&testEndpoint, &thirdResult) + scenario.Store.Insert(&testEndpoint, &fourthResult) + if averageResponseTime, err := scenario.Store.GetAverageResponseTimeByKey(testEndpoint.Key(), now.Add(-48*time.Hour), now.Add(-24*time.Hour)); err == nil { + if averageResponseTime != 0 { + t.Errorf("expected average response time to be 0ms, got %v", averageResponseTime) + } + } else { + t.Error("shouldn't have returned an error, got", err) + } + if averageResponseTime, err := scenario.Store.GetAverageResponseTimeByKey(testEndpoint.Key(), now.Add(-24*time.Hour), now); err == nil { + if averageResponseTime != 287 { + t.Errorf("expected average response time to be 287ms, got %v", averageResponseTime) + } + } else { + t.Error("shouldn't have returned an error, got", err) + } + if averageResponseTime, err := scenario.Store.GetAverageResponseTimeByKey(testEndpoint.Key(), now.Add(-time.Hour), now); err == nil { + if averageResponseTime != 350 { + t.Errorf("expected average response time to be 350ms, got %v", averageResponseTime) + } + } else { + t.Error("shouldn't have returned an error, got", err) + } + if averageResponseTime, err := scenario.Store.GetAverageResponseTimeByKey(testEndpoint.Key(), now.Add(-2*time.Hour), now.Add(-time.Hour)); err == nil { + if averageResponseTime != 216 { + t.Errorf("expected average response time to be 216ms, got %v", averageResponseTime) + } + } else { + t.Error("shouldn't have returned an error, got", err) + } + if _, err := scenario.Store.GetAverageResponseTimeByKey(testEndpoint.Key(), now, now.Add(-2*time.Hour)); err == nil { + t.Error("expected an error because from > to, got nil") + } + scenario.Store.Clear() + }) + } +} + +func TestStore_GetHourlyAverageResponseTimeByKey(t *testing.T) { + scenarios := initStoresAndBaseScenarios(t, "TestStore_GetHourlyAverageResponseTimeByKey") + defer cleanUp(scenarios) + firstResult := testSuccessfulResult + firstResult.Timestamp = now.Add(-(2 * time.Hour)) + firstResult.Duration = 300 * time.Millisecond + secondResult := testSuccessfulResult + secondResult.Duration = 150 * time.Millisecond + secondResult.Timestamp = now.Add(-(1*time.Hour + 30*time.Minute)) + thirdResult := testUnsuccessfulResult + thirdResult.Duration = 200 * time.Millisecond + thirdResult.Timestamp = now.Add(-(1 * time.Hour)) + fourthResult := testSuccessfulResult + fourthResult.Duration = 500 * time.Millisecond + fourthResult.Timestamp = now + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + scenario.Store.Insert(&testEndpoint, &firstResult) + scenario.Store.Insert(&testEndpoint, &secondResult) + scenario.Store.Insert(&testEndpoint, &thirdResult) + scenario.Store.Insert(&testEndpoint, &fourthResult) + hourlyAverageResponseTime, err := scenario.Store.GetHourlyAverageResponseTimeByKey(testEndpoint.Key(), now.Add(-24*time.Hour), now) + if err != nil { + t.Error("shouldn't have returned an error, got", err) + } + if key := now.Truncate(time.Hour).Unix(); hourlyAverageResponseTime[key] != 500 { + t.Errorf("expected average response time to be 500ms at %d, got %v", key, hourlyAverageResponseTime[key]) + } + if key := now.Truncate(time.Hour).Add(-time.Hour).Unix(); hourlyAverageResponseTime[key] != 200 { + t.Errorf("expected average response time to be 200ms at %d, got %v", key, hourlyAverageResponseTime[key]) + } + if key := now.Truncate(time.Hour).Add(-2 * time.Hour).Unix(); hourlyAverageResponseTime[key] != 225 { + t.Errorf("expected average response time to be 225ms at %d, got %v", key, hourlyAverageResponseTime[key]) + } + scenario.Store.Clear() + }) + } +} + +func TestStore_Insert(t *testing.T) { + scenarios := initStoresAndBaseScenarios(t, "TestStore_Insert") + defer cleanUp(scenarios) + firstResult := testSuccessfulResult + firstResult.Timestamp = now.Add(-time.Minute) + secondResult := testUnsuccessfulResult + secondResult.Timestamp = now + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + scenario.Store.Insert(&testEndpoint, &firstResult) + scenario.Store.Insert(&testEndpoint, &secondResult) + ss, err := scenario.Store.GetEndpointStatusByKey(testEndpoint.Key(), paging.NewEndpointStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults)) + if err != nil { + t.Error("shouldn't have returned an error, got", err) + } + if ss == nil { + t.Fatalf("Store should've had key '%s', but didn't", testEndpoint.Key()) + } + if len(ss.Events) != 3 { + t.Fatalf("Endpoint '%s' should've had 3 events, got %d", ss.Name, len(ss.Events)) + } + if len(ss.Results) != 2 { + t.Fatalf("Endpoint '%s' should've had 2 results, got %d", ss.Name, len(ss.Results)) + } + for i, expectedResult := range []endpoint.Result{firstResult, secondResult} { + if expectedResult.HTTPStatus != ss.Results[i].HTTPStatus { + t.Errorf("Result at index %d should've had a HTTPStatus of %d, got %d", i, ss.Results[i].HTTPStatus, expectedResult.HTTPStatus) + } + if expectedResult.DNSRCode != ss.Results[i].DNSRCode { + t.Errorf("Result at index %d should've had a DNSRCode of %s, got %s", i, ss.Results[i].DNSRCode, expectedResult.DNSRCode) + } + if expectedResult.Hostname != ss.Results[i].Hostname { + t.Errorf("Result at index %d should've had a Hostname of %s, got %s", i, ss.Results[i].Hostname, expectedResult.Hostname) + } + if expectedResult.IP != ss.Results[i].IP { + t.Errorf("Result at index %d should've had a IP of %s, got %s", i, ss.Results[i].IP, expectedResult.IP) + } + if expectedResult.Connected != ss.Results[i].Connected { + t.Errorf("Result at index %d should've had a Connected value of %t, got %t", i, ss.Results[i].Connected, expectedResult.Connected) + } + if expectedResult.Duration != ss.Results[i].Duration { + t.Errorf("Result at index %d should've had a Duration of %s, got %s", i, ss.Results[i].Duration.String(), expectedResult.Duration.String()) + } + if len(expectedResult.Errors) != len(ss.Results[i].Errors) { + t.Errorf("Result at index %d should've had %d errors, but actually had %d errors", i, len(ss.Results[i].Errors), len(expectedResult.Errors)) + } else { + for j := range expectedResult.Errors { + if ss.Results[i].Errors[j] != expectedResult.Errors[j] { + t.Error("should've been the same") + } + } + } + if len(expectedResult.ConditionResults) != len(ss.Results[i].ConditionResults) { + t.Errorf("Result at index %d should've had %d ConditionResults, but actually had %d ConditionResults", i, len(ss.Results[i].ConditionResults), len(expectedResult.ConditionResults)) + } else { + for j := range expectedResult.ConditionResults { + if ss.Results[i].ConditionResults[j].Condition != expectedResult.ConditionResults[j].Condition { + t.Error("should've been the same") + } + if ss.Results[i].ConditionResults[j].Success != expectedResult.ConditionResults[j].Success { + t.Error("should've been the same") + } + } + } + if expectedResult.Success != ss.Results[i].Success { + t.Errorf("Result at index %d should've had a Success of %t, got %t", i, ss.Results[i].Success, expectedResult.Success) + } + if expectedResult.Timestamp.Unix() != ss.Results[i].Timestamp.Unix() { + t.Errorf("Result at index %d should've had a Timestamp of %d, got %d", i, ss.Results[i].Timestamp.Unix(), expectedResult.Timestamp.Unix()) + } + if expectedResult.CertificateExpiration != ss.Results[i].CertificateExpiration { + t.Errorf("Result at index %d should've had a CertificateExpiration of %s, got %s", i, ss.Results[i].CertificateExpiration.String(), expectedResult.CertificateExpiration.String()) + } + } + }) + } +} + +func TestStore_DeleteAllEndpointStatusesNotInKeys(t *testing.T) { + scenarios := initStoresAndBaseScenarios(t, "TestStore_DeleteAllEndpointStatusesNotInKeys") + defer cleanUp(scenarios) + firstEndpoint := endpoint.Endpoint{Name: "endpoint-1", Group: "group"} + secondEndpoint := endpoint.Endpoint{Name: "endpoint-2", Group: "group"} + r := &testSuccessfulResult + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + scenario.Store.Insert(&firstEndpoint, r) + scenario.Store.Insert(&secondEndpoint, r) + if ss, _ := scenario.Store.GetEndpointStatusByKey(firstEndpoint.Key(), paging.NewEndpointStatusParams()); ss == nil { + t.Fatal("firstEndpoint should exist, got", ss) + } + if ss, _ := scenario.Store.GetEndpointStatusByKey(secondEndpoint.Key(), paging.NewEndpointStatusParams()); ss == nil { + t.Fatal("secondEndpoint should exist, got", ss) + } + scenario.Store.DeleteAllEndpointStatusesNotInKeys([]string{firstEndpoint.Key()}) + if ss, _ := scenario.Store.GetEndpointStatusByKey(firstEndpoint.Key(), paging.NewEndpointStatusParams()); ss == nil { + t.Error("secondEndpoint should still exist, got", ss) + } + if ss, _ := scenario.Store.GetEndpointStatusByKey(secondEndpoint.Key(), paging.NewEndpointStatusParams()); ss != nil { + t.Error("firstEndpoint should have been deleted, got", ss) + } + // Delete everything + scenario.Store.DeleteAllEndpointStatusesNotInKeys([]string{}) + endpointStatuses, _ := scenario.Store.GetAllEndpointStatuses(paging.NewEndpointStatusParams()) + if len(endpointStatuses) != 0 { + t.Errorf("everything should've been deleted") + } + }) + } +} + +func TestGet(t *testing.T) { + store := Get() + if store == nil { + t.Error("store should've been automatically initialized") + } +} + +func TestInitialize(t *testing.T) { + dir := t.TempDir() + type Scenario struct { + Name string + Cfg *storage.Config + ExpectedErr error + } + scenarios := []Scenario{ + { + Name: "nil", + Cfg: nil, + ExpectedErr: nil, + }, + { + Name: "blank", + Cfg: &storage.Config{}, + ExpectedErr: nil, + }, + { + Name: "memory-no-path", + Cfg: &storage.Config{Type: storage.TypeMemory}, + ExpectedErr: nil, + }, + { + Name: "sqlite-no-path", + Cfg: &storage.Config{Type: storage.TypeSQLite}, + ExpectedErr: sql.ErrPathNotSpecified, + }, + { + Name: "sqlite-with-path", + Cfg: &storage.Config{Type: storage.TypeSQLite, Path: filepath.Join(dir, "TestInitialize_sqlite-with-path.db")}, + ExpectedErr: nil, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + err := Initialize(scenario.Cfg) + if err != scenario.ExpectedErr { + t.Errorf("expected %v, got %v", scenario.ExpectedErr, err) + } + if err != nil { + return + } + if cancelFunc == nil { + t.Error("cancelFunc shouldn't have been nil") + } + if ctx == nil { + t.Error("ctx shouldn't have been nil") + } + if store == nil { + t.Fatal("provider shouldn't have been nit") + } + store.Close() + // Try to initialize it again + err = Initialize(scenario.Cfg) + if !errors.Is(err, scenario.ExpectedErr) { + t.Errorf("expected %v, got %v", scenario.ExpectedErr, err) + return + } + store.Close() + }) + } +} + +func TestAutoSave(t *testing.T) { + file := filepath.Join(t.TempDir(), "/TestAutoSave.db") + if err := Initialize(&storage.Config{Path: file}); err != nil { + t.Fatal("shouldn't have returned an error") + } + go autoSave(ctx, store, 3*time.Millisecond) + time.Sleep(15 * time.Millisecond) + cancelFunc() + time.Sleep(50 * time.Millisecond) +} diff --git a/storage/type.go b/storage/type.go new file mode 100644 index 00000000..4c25350c --- /dev/null +++ b/storage/type.go @@ -0,0 +1,10 @@ +package storage + +// Type of the store. +type Type string + +const ( + TypeMemory Type = "memory" // In-memory store + TypeSQLite Type = "sqlite" // SQLite store + TypePostgres Type = "postgres" // Postgres store +) diff --git a/test/mock.go b/test/mock.go new file mode 100644 index 00000000..8050f4e1 --- /dev/null +++ b/test/mock.go @@ -0,0 +1,9 @@ +package test + +import "net/http" + +type MockRoundTripper func(r *http.Request) *http.Response + +func (f MockRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { + return f(r), nil +} diff --git a/testdata/badcert.key b/testdata/badcert.key new file mode 100644 index 00000000..d7a9a79b --- /dev/null +++ b/testdata/badcert.key @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +wat +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/testdata/badcert.pem b/testdata/badcert.pem new file mode 100644 index 00000000..002a8474 --- /dev/null +++ b/testdata/badcert.pem @@ -0,0 +1,3 @@ +-----BEGIN CERTIFICATE----- +wat +-----END CERTIFICATE----- \ No newline at end of file diff --git a/testdata/cert.key b/testdata/cert.key new file mode 100644 index 00000000..20522ab5 --- /dev/null +++ b/testdata/cert.key @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgJh67FWpz8wrN1mM/ +CebkZN0zF83691ZVD83XlbNLRUqhRANCAAScfyPxScqz+Z/yNtAID/FOORy9J6LM +DUAJevGDvAZCMp/nh+Ps3nLrMoRlykcux3mq+N8HPlJ8R3eetB4S1tHY +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/testdata/cert.pem b/testdata/cert.pem new file mode 100644 index 00000000..54c84fed --- /dev/null +++ b/testdata/cert.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBaDCCAQ2gAwIBAgICBNIwCgYIKoZIzj0EAwIwFTETMBEGA1UEChMKR2F0dXMg +dGVzdDAgFw0yMzA0MjIxODUwMDVaGA8yMjk3MDIwNDE4NTAwNVowFTETMBEGA1UE +ChMKR2F0dXMgdGVzdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJx/I/FJyrP5 +n/I20AgP8U45HL0noswNQAl68YO8BkIyn+eH4+zecusyhGXKRy7Hear43wc+UnxH +d560HhLW0dijSzBJMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcD +ATAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDAKBggqhkjOPQQD +AgNJADBGAiEA/SdthKOoNw3azSHuPid7XJsXYB8DisIC9LBwcb/QTMECIQCAB36Y +OI15ao+J/RUz2sXdPXCAN8hlohi6OnmZmJB32g== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/watchdog/alerting.go b/watchdog/alerting.go new file mode 100644 index 00000000..866ed667 --- /dev/null +++ b/watchdog/alerting.go @@ -0,0 +1,97 @@ +package watchdog + +import ( + "errors" + "os" + + "github.com/TwiN/gatus/v5/alerting" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/storage/store" + "github.com/TwiN/logr" +) + +// HandleAlerting takes care of alerts to resolve and alerts to trigger based on result success or failure +func HandleAlerting(ep *endpoint.Endpoint, result *endpoint.Result, alertingConfig *alerting.Config) { + if alertingConfig == nil { + return + } + if result.Success { + handleAlertsToResolve(ep, result, alertingConfig) + } else { + handleAlertsToTrigger(ep, result, alertingConfig) + } +} + +func handleAlertsToTrigger(ep *endpoint.Endpoint, result *endpoint.Result, alertingConfig *alerting.Config) { + ep.NumberOfSuccessesInARow = 0 + ep.NumberOfFailuresInARow++ + for _, endpointAlert := range ep.Alerts { + // If the alert hasn't been triggered, move to the next one + if !endpointAlert.IsEnabled() || endpointAlert.FailureThreshold > ep.NumberOfFailuresInARow { + continue + } + if endpointAlert.Triggered { + logr.Debugf("[watchdog.handleAlertsToTrigger] Alert for endpoint with key=%s with description='%s' has already been TRIGGERED, skipping", ep.Key(), endpointAlert.GetDescription()) + continue + } + alertProvider := alertingConfig.GetAlertingProviderByAlertType(endpointAlert.Type) + if alertProvider != nil { + logr.Infof("[watchdog.handleAlertsToTrigger] Sending %s alert because alert for endpoint with key=%s with description='%s' has been TRIGGERED", endpointAlert.Type, ep.Key(), endpointAlert.GetDescription()) + var err error + if os.Getenv("MOCK_ALERT_PROVIDER") == "true" { + if os.Getenv("MOCK_ALERT_PROVIDER_ERROR") == "true" { + err = errors.New("error") + } + } else { + err = alertProvider.Send(ep, endpointAlert, result, false) + } + if err != nil { + logr.Errorf("[watchdog.handleAlertsToTrigger] Failed to send an alert for endpoint with key=%s: %s", ep.Key(), err.Error()) + } else { + endpointAlert.Triggered = true + if err := store.Get().UpsertTriggeredEndpointAlert(ep, endpointAlert); err != nil { + logr.Errorf("[watchdog.handleAlertsToTrigger] Failed to persist triggered endpoint alert for endpoint with key=%s: %s", ep.Key(), err.Error()) + } + } + } else { + logr.Warnf("[watchdog.handleAlertsToTrigger] Not sending alert of type=%s endpoint with key=%s despite being TRIGGERED, because the provider wasn't configured properly", endpointAlert.Type, ep.Key()) + } + } +} + +func handleAlertsToResolve(ep *endpoint.Endpoint, result *endpoint.Result, alertingConfig *alerting.Config) { + ep.NumberOfSuccessesInARow++ + for _, endpointAlert := range ep.Alerts { + isStillBelowSuccessThreshold := endpointAlert.SuccessThreshold > ep.NumberOfSuccessesInARow + if isStillBelowSuccessThreshold && endpointAlert.IsEnabled() && endpointAlert.Triggered { + // Persist NumberOfSuccessesInARow + if err := store.Get().UpsertTriggeredEndpointAlert(ep, endpointAlert); err != nil { + logr.Errorf("[watchdog.handleAlertsToResolve] Failed to update triggered endpoint alert for endpoint with key=%s: %s", ep.Key(), err.Error()) + } + } + if !endpointAlert.IsEnabled() || !endpointAlert.Triggered || isStillBelowSuccessThreshold { + continue + } + // Even if the alert provider returns an error, we still set the alert's Triggered variable to false. + // Further explanation can be found on Alert's Triggered field. + endpointAlert.Triggered = false + if err := store.Get().DeleteTriggeredEndpointAlert(ep, endpointAlert); err != nil { + logr.Errorf("[watchdog.handleAlertsToResolve] Failed to delete persisted triggered endpoint alert for endpoint with key=%s: %s", ep.Key(), err.Error()) + } + if !endpointAlert.IsSendingOnResolved() { + logr.Debugf("[watchdog.handleAlertsToResolve] Not sending request to provider of alert with type=%s for endpoint with key=%s despite being RESOLVED, because send-on-resolved is set to false", endpointAlert.Type, ep.Key()) + continue + } + alertProvider := alertingConfig.GetAlertingProviderByAlertType(endpointAlert.Type) + if alertProvider != nil { + logr.Infof("[watchdog.handleAlertsToResolve] Sending %s alert because alert for endpoint with key=%s with description='%s' has been RESOLVED", endpointAlert.Type, ep.Key(), endpointAlert.GetDescription()) + err := alertProvider.Send(ep, endpointAlert, result, true) + if err != nil { + logr.Errorf("[watchdog.handleAlertsToResolve] Failed to send an alert for endpoint with key=%s: %s", ep.Key(), err.Error()) + } + } else { + logr.Warnf("[watchdog.handleAlertsToResolve] Not sending alert of type=%s for endpoint with key=%s despite being RESOLVED, because the provider wasn't configured properly", endpointAlert.Type, ep.Key()) + } + } + ep.NumberOfFailuresInARow = 0 +} diff --git a/watchdog/alerting_test.go b/watchdog/alerting_test.go new file mode 100644 index 00000000..2dffd909 --- /dev/null +++ b/watchdog/alerting_test.go @@ -0,0 +1,538 @@ +package watchdog + +import ( + "os" + "testing" + + "github.com/TwiN/gatus/v5/alerting" + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/alerting/provider/custom" + "github.com/TwiN/gatus/v5/alerting/provider/discord" + "github.com/TwiN/gatus/v5/alerting/provider/email" + "github.com/TwiN/gatus/v5/alerting/provider/jetbrainsspace" + "github.com/TwiN/gatus/v5/alerting/provider/matrix" + "github.com/TwiN/gatus/v5/alerting/provider/mattermost" + "github.com/TwiN/gatus/v5/alerting/provider/messagebird" + "github.com/TwiN/gatus/v5/alerting/provider/pagerduty" + "github.com/TwiN/gatus/v5/alerting/provider/pushover" + "github.com/TwiN/gatus/v5/alerting/provider/slack" + "github.com/TwiN/gatus/v5/alerting/provider/teams" + "github.com/TwiN/gatus/v5/alerting/provider/telegram" + "github.com/TwiN/gatus/v5/alerting/provider/twilio" + "github.com/TwiN/gatus/v5/config" + "github.com/TwiN/gatus/v5/config/endpoint" +) + +func TestHandleAlerting(t *testing.T) { + _ = os.Setenv("MOCK_ALERT_PROVIDER", "true") + defer os.Clearenv() + + cfg := &config.Config{ + Alerting: &alerting.Config{ + Custom: &custom.AlertProvider{ + DefaultConfig: custom.Config{ + URL: "https://twin.sh/health", + Method: "GET", + }, + }, + }, + } + enabled := true + ep := &endpoint.Endpoint{ + URL: "https://example.com", + Alerts: []*alert.Alert{ + { + Type: alert.TypeCustom, + Enabled: &enabled, + FailureThreshold: 2, + SuccessThreshold: 3, + SendOnResolved: &enabled, + Triggered: false, + }, + }, + } + + verify(t, ep, 0, 0, false, "The alert shouldn't start triggered") + HandleAlerting(ep, &endpoint.Result{Success: false}, cfg.Alerting) + verify(t, ep, 1, 0, false, "The alert shouldn't have triggered") + HandleAlerting(ep, &endpoint.Result{Success: false}, cfg.Alerting) + verify(t, ep, 2, 0, true, "The alert should've triggered") + HandleAlerting(ep, &endpoint.Result{Success: false}, cfg.Alerting) + verify(t, ep, 3, 0, true, "The alert should still be triggered") + HandleAlerting(ep, &endpoint.Result{Success: false}, cfg.Alerting) + verify(t, ep, 4, 0, true, "The alert should still be triggered") + HandleAlerting(ep, &endpoint.Result{Success: true}, cfg.Alerting) + verify(t, ep, 0, 1, true, "The alert should still be triggered (because endpoint.Alerts[0].SuccessThreshold is 3)") + HandleAlerting(ep, &endpoint.Result{Success: true}, cfg.Alerting) + verify(t, ep, 0, 2, true, "The alert should still be triggered (because endpoint.Alerts[0].SuccessThreshold is 3)") + HandleAlerting(ep, &endpoint.Result{Success: true}, cfg.Alerting) + verify(t, ep, 0, 3, false, "The alert should've been resolved") + HandleAlerting(ep, &endpoint.Result{Success: true}, cfg.Alerting) + verify(t, ep, 0, 4, false, "The alert should no longer be triggered") +} + +func TestHandleAlertingWhenAlertingConfigIsNil(t *testing.T) { + _ = os.Setenv("MOCK_ALERT_PROVIDER", "true") + defer os.Clearenv() + HandleAlerting(nil, nil, nil) +} + +func TestHandleAlertingWithBadAlertProvider(t *testing.T) { + _ = os.Setenv("MOCK_ALERT_PROVIDER", "true") + defer os.Clearenv() + + enabled := true + ep := &endpoint.Endpoint{ + URL: "http://example.com", + Alerts: []*alert.Alert{ + { + Type: alert.TypeCustom, + Enabled: &enabled, + FailureThreshold: 1, + SuccessThreshold: 1, + SendOnResolved: &enabled, + Triggered: false, + }, + }, + } + + verify(t, ep, 0, 0, false, "The alert shouldn't start triggered") + HandleAlerting(ep, &endpoint.Result{Success: false}, &alerting.Config{}) + verify(t, ep, 1, 0, false, "The alert shouldn't have triggered") + HandleAlerting(ep, &endpoint.Result{Success: false}, &alerting.Config{}) + verify(t, ep, 2, 0, false, "The alert shouldn't have triggered, because the provider wasn't configured properly") +} + +func TestHandleAlertingWhenTriggeredAlertIsAlmostResolvedButendpointStartFailingAgain(t *testing.T) { + _ = os.Setenv("MOCK_ALERT_PROVIDER", "true") + defer os.Clearenv() + + cfg := &config.Config{ + Alerting: &alerting.Config{ + Custom: &custom.AlertProvider{ + DefaultConfig: custom.Config{ + URL: "https://twin.sh/health", + Method: "GET", + }, + }, + }, + } + enabled := true + ep := &endpoint.Endpoint{ + URL: "https://example.com", + Alerts: []*alert.Alert{ + { + Type: alert.TypeCustom, + Enabled: &enabled, + FailureThreshold: 2, + SuccessThreshold: 3, + SendOnResolved: &enabled, + Triggered: true, + }, + }, + NumberOfFailuresInARow: 1, + } + + // This test simulate an alert that was already triggered + HandleAlerting(ep, &endpoint.Result{Success: false}, cfg.Alerting) + verify(t, ep, 2, 0, true, "The alert was already triggered at the beginning of this test") +} + +func TestHandleAlertingWhenTriggeredAlertIsResolvedButSendOnResolvedIsFalse(t *testing.T) { + _ = os.Setenv("MOCK_ALERT_PROVIDER", "true") + defer os.Clearenv() + + cfg := &config.Config{ + Alerting: &alerting.Config{ + Custom: &custom.AlertProvider{ + DefaultConfig: custom.Config{ + URL: "https://twin.sh/health", + Method: "GET", + }, + }, + }, + } + enabled := true + disabled := false + ep := &endpoint.Endpoint{ + URL: "https://example.com", + Alerts: []*alert.Alert{ + { + Type: alert.TypeCustom, + Enabled: &enabled, + FailureThreshold: 1, + SuccessThreshold: 1, + SendOnResolved: &disabled, + Triggered: true, + }, + }, + NumberOfFailuresInARow: 1, + } + + HandleAlerting(ep, &endpoint.Result{Success: true}, cfg.Alerting) + verify(t, ep, 0, 1, false, "The alert should've been resolved") +} + +func TestHandleAlertingWhenTriggeredAlertIsResolvedPagerDuty(t *testing.T) { + _ = os.Setenv("MOCK_ALERT_PROVIDER", "true") + defer os.Clearenv() + + cfg := &config.Config{ + Alerting: &alerting.Config{ + PagerDuty: &pagerduty.AlertProvider{ + DefaultConfig: pagerduty.Config{ + IntegrationKey: "00000000000000000000000000000000", + }, + }, + }, + } + enabled := true + ep := &endpoint.Endpoint{ + URL: "https://example.com", + Alerts: []*alert.Alert{ + { + Type: alert.TypePagerDuty, + Enabled: &enabled, + FailureThreshold: 1, + SuccessThreshold: 1, + SendOnResolved: &enabled, + Triggered: false, + }, + }, + NumberOfFailuresInARow: 0, + } + + HandleAlerting(ep, &endpoint.Result{Success: false}, cfg.Alerting) + verify(t, ep, 1, 0, true, "") + + HandleAlerting(ep, &endpoint.Result{Success: true}, cfg.Alerting) + verify(t, ep, 0, 1, false, "The alert should've been resolved") +} + +func TestHandleAlertingWhenTriggeredAlertIsResolvedPushover(t *testing.T) { + _ = os.Setenv("MOCK_ALERT_PROVIDER", "true") + defer os.Clearenv() + + cfg := &config.Config{ + Alerting: &alerting.Config{ + Pushover: &pushover.AlertProvider{ + DefaultConfig: pushover.Config{ + ApplicationToken: "000000000000000000000000000000", + UserKey: "000000000000000000000000000000", + }, + }, + }, + } + enabled := true + ep := &endpoint.Endpoint{ + URL: "https://example.com", + Alerts: []*alert.Alert{ + { + Type: alert.TypePushover, + Enabled: &enabled, + FailureThreshold: 1, + SuccessThreshold: 1, + SendOnResolved: &enabled, + Triggered: false, + }, + }, + NumberOfFailuresInARow: 0, + } + + HandleAlerting(ep, &endpoint.Result{Success: false}, cfg.Alerting) + verify(t, ep, 1, 0, true, "") + + HandleAlerting(ep, &endpoint.Result{Success: true}, cfg.Alerting) + verify(t, ep, 0, 1, false, "The alert should've been resolved") +} + +func TestHandleAlertingWithProviderThatReturnsAnError(t *testing.T) { + _ = os.Setenv("MOCK_ALERT_PROVIDER", "true") + defer os.Clearenv() + enabled := true + scenarios := []struct { + Name string + AlertingConfig *alerting.Config + AlertType alert.Type + }{ + { + Name: "custom", + AlertType: alert.TypeCustom, + AlertingConfig: &alerting.Config{ + Custom: &custom.AlertProvider{ + DefaultConfig: custom.Config{ + URL: "https://twin.sh/health", + Method: "GET", + }, + }, + }, + }, + { + Name: "discord", + AlertType: alert.TypeDiscord, + AlertingConfig: &alerting.Config{ + Discord: &discord.AlertProvider{ + DefaultConfig: discord.Config{ + WebhookURL: "https://example.com", + }, + }, + }, + }, + { + Name: "email", + AlertType: alert.TypeEmail, + AlertingConfig: &alerting.Config{ + Email: &email.AlertProvider{ + DefaultConfig: email.Config{ + From: "from@example.com", + Password: "hunter2", + Host: "mail.example.com", + Port: 587, + To: "to@example.com", + }, + }, + }, + }, + { + Name: "jetbrainsspace", + AlertType: alert.TypeJetBrainsSpace, + AlertingConfig: &alerting.Config{ + JetBrainsSpace: &jetbrainsspace.AlertProvider{ + DefaultConfig: jetbrainsspace.Config{ + Project: "foo", + ChannelID: "bar", + Token: "baz", + }, + }, + }, + }, + { + Name: "mattermost", + AlertType: alert.TypeMattermost, + AlertingConfig: &alerting.Config{ + Mattermost: &mattermost.AlertProvider{ + DefaultConfig: mattermost.Config{ + WebhookURL: "https://example.com", + }, + }, + }, + }, + { + Name: "messagebird", + AlertType: alert.TypeMessagebird, + AlertingConfig: &alerting.Config{ + Messagebird: &messagebird.AlertProvider{ + DefaultConfig: messagebird.Config{ + AccessKey: "1", + Originator: "2", + Recipients: "3", + }, + }, + }, + }, + { + Name: "pagerduty", + AlertType: alert.TypePagerDuty, + AlertingConfig: &alerting.Config{ + PagerDuty: &pagerduty.AlertProvider{ + DefaultConfig: pagerduty.Config{ + IntegrationKey: "00000000000000000000000000000000", + }, + }, + }, + }, + { + Name: "pushover", + AlertType: alert.TypePushover, + AlertingConfig: &alerting.Config{ + Pushover: &pushover.AlertProvider{ + DefaultConfig: pushover.Config{ + ApplicationToken: "000000000000000000000000000000", + UserKey: "000000000000000000000000000000", + }, + }, + }, + }, + { + Name: "slack", + AlertType: alert.TypeSlack, + AlertingConfig: &alerting.Config{ + Slack: &slack.AlertProvider{ + DefaultConfig: slack.Config{ + WebhookURL: "https://example.com", + }, + }, + }, + }, + { + Name: "teams", + AlertType: alert.TypeTeams, + AlertingConfig: &alerting.Config{ + Teams: &teams.AlertProvider{ + DefaultConfig: teams.Config{ + WebhookURL: "https://example.com", + }, + }, + }, + }, + { + Name: "telegram", + AlertType: alert.TypeTelegram, + AlertingConfig: &alerting.Config{ + Telegram: &telegram.AlertProvider{ + DefaultConfig: telegram.Config{ + Token: "1", + ID: "2", + }, + }, + }, + }, + { + Name: "twilio", + AlertType: alert.TypeTwilio, + AlertingConfig: &alerting.Config{ + Twilio: &twilio.AlertProvider{ + DefaultConfig: twilio.Config{ + SID: "1", + Token: "2", + From: "3", + To: "4", + }, + }, + }, + }, + { + Name: "matrix", + AlertType: alert.TypeMatrix, + AlertingConfig: &alerting.Config{ + Matrix: &matrix.AlertProvider{ + DefaultConfig: matrix.Config{ + ServerURL: "https://example.com", + AccessToken: "1", + InternalRoomID: "!a:example.com", + }, + }, + }, + }, + } + + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + ep := &endpoint.Endpoint{ + URL: "https://example.com", + Alerts: []*alert.Alert{ + { + Type: scenario.AlertType, + Enabled: &enabled, + FailureThreshold: 2, + SuccessThreshold: 2, + SendOnResolved: &enabled, + Triggered: false, + }, + }, + } + _ = os.Setenv("MOCK_ALERT_PROVIDER_ERROR", "true") + HandleAlerting(ep, &endpoint.Result{Success: false}, scenario.AlertingConfig) + verify(t, ep, 1, 0, false, "") + HandleAlerting(ep, &endpoint.Result{Success: false}, scenario.AlertingConfig) + verify(t, ep, 2, 0, false, "The alert should have failed to trigger, because the alert provider is returning an error") + HandleAlerting(ep, &endpoint.Result{Success: false}, scenario.AlertingConfig) + verify(t, ep, 3, 0, false, "The alert should still not be triggered, because the alert provider is still returning an error") + HandleAlerting(ep, &endpoint.Result{Success: false}, scenario.AlertingConfig) + verify(t, ep, 4, 0, false, "The alert should still not be triggered, because the alert provider is still returning an error") + _ = os.Setenv("MOCK_ALERT_PROVIDER_ERROR", "false") + HandleAlerting(ep, &endpoint.Result{Success: false}, scenario.AlertingConfig) + verify(t, ep, 5, 0, true, "The alert should've been triggered because the alert provider is no longer returning an error") + HandleAlerting(ep, &endpoint.Result{Success: true}, scenario.AlertingConfig) + verify(t, ep, 0, 1, true, "The alert should've still been triggered") + _ = os.Setenv("MOCK_ALERT_PROVIDER_ERROR", "true") + HandleAlerting(ep, &endpoint.Result{Success: true}, scenario.AlertingConfig) + verify(t, ep, 0, 2, false, "The alert should've been resolved DESPITE THE ALERT PROVIDER RETURNING AN ERROR. See Alert.Triggered for further explanation.") + _ = os.Setenv("MOCK_ALERT_PROVIDER_ERROR", "false") + + // Make sure that everything's working as expected after a rough patch + HandleAlerting(ep, &endpoint.Result{Success: false}, scenario.AlertingConfig) + verify(t, ep, 1, 0, false, "") + HandleAlerting(ep, &endpoint.Result{Success: false}, scenario.AlertingConfig) + verify(t, ep, 2, 0, true, "The alert should have triggered") + HandleAlerting(ep, &endpoint.Result{Success: true}, scenario.AlertingConfig) + verify(t, ep, 0, 1, true, "The alert should still be triggered") + HandleAlerting(ep, &endpoint.Result{Success: true}, scenario.AlertingConfig) + verify(t, ep, 0, 2, false, "The alert should have been resolved") + }) + } + +} + +func TestHandleAlertingWithProviderThatOnlyReturnsErrorOnResolve(t *testing.T) { + _ = os.Setenv("MOCK_ALERT_PROVIDER", "true") + defer os.Clearenv() + + cfg := &config.Config{ + Alerting: &alerting.Config{ + Custom: &custom.AlertProvider{ + DefaultConfig: custom.Config{ + URL: "https://twin.sh/health", + Method: "GET", + }, + }, + }, + } + enabled := true + ep := &endpoint.Endpoint{ + URL: "https://example.com", + Alerts: []*alert.Alert{ + { + Type: alert.TypeCustom, + Enabled: &enabled, + FailureThreshold: 1, + SuccessThreshold: 1, + SendOnResolved: &enabled, + Triggered: false, + }, + }, + } + + HandleAlerting(ep, &endpoint.Result{Success: false}, cfg.Alerting) + verify(t, ep, 1, 0, true, "") + _ = os.Setenv("MOCK_ALERT_PROVIDER_ERROR", "true") + HandleAlerting(ep, &endpoint.Result{Success: true}, cfg.Alerting) + verify(t, ep, 0, 1, false, "") + _ = os.Setenv("MOCK_ALERT_PROVIDER_ERROR", "false") + HandleAlerting(ep, &endpoint.Result{Success: false}, cfg.Alerting) + verify(t, ep, 1, 0, true, "") + _ = os.Setenv("MOCK_ALERT_PROVIDER_ERROR", "true") + HandleAlerting(ep, &endpoint.Result{Success: true}, cfg.Alerting) + verify(t, ep, 0, 1, false, "") + _ = os.Setenv("MOCK_ALERT_PROVIDER_ERROR", "false") + + // Make sure that everything's working as expected after a rough patch + HandleAlerting(ep, &endpoint.Result{Success: false}, cfg.Alerting) + verify(t, ep, 1, 0, true, "") + HandleAlerting(ep, &endpoint.Result{Success: false}, cfg.Alerting) + verify(t, ep, 2, 0, true, "") + HandleAlerting(ep, &endpoint.Result{Success: true}, cfg.Alerting) + verify(t, ep, 0, 1, false, "") + HandleAlerting(ep, &endpoint.Result{Success: true}, cfg.Alerting) + verify(t, ep, 0, 2, false, "") +} + +func verify(t *testing.T, ep *endpoint.Endpoint, expectedNumberOfFailuresInARow, expectedNumberOfSuccessInARow int, expectedTriggered bool, expectedTriggeredReason string) { + if ep.NumberOfFailuresInARow != expectedNumberOfFailuresInARow { + t.Errorf("endpoint.NumberOfFailuresInARow should've been %d, got %d", expectedNumberOfFailuresInARow, ep.NumberOfFailuresInARow) + } + if ep.NumberOfSuccessesInARow != expectedNumberOfSuccessInARow { + t.Errorf("endpoint.NumberOfSuccessesInARow should've been %d, got %d", expectedNumberOfSuccessInARow, ep.NumberOfSuccessesInARow) + } + if ep.Alerts[0].Triggered != expectedTriggered { + if len(expectedTriggeredReason) != 0 { + t.Error(expectedTriggeredReason) + } else { + if expectedTriggered { + t.Error("The alert should've been triggered") + } else { + t.Error("The alert shouldn't have been triggered") + } + } + } +} diff --git a/watchdog/watchdog.go b/watchdog/watchdog.go new file mode 100644 index 00000000..7c5a1548 --- /dev/null +++ b/watchdog/watchdog.go @@ -0,0 +1,113 @@ +package watchdog + +import ( + "context" + "sync" + "time" + + "github.com/TwiN/gatus/v5/alerting" + "github.com/TwiN/gatus/v5/config" + "github.com/TwiN/gatus/v5/config/connectivity" + "github.com/TwiN/gatus/v5/config/endpoint" + "github.com/TwiN/gatus/v5/config/maintenance" + "github.com/TwiN/gatus/v5/metrics" + "github.com/TwiN/gatus/v5/storage/store" + "github.com/TwiN/logr" +) + +var ( + // monitoringMutex is used to prevent multiple endpoint from being evaluated at the same time. + // Without this, conditions using response time may become inaccurate. + monitoringMutex sync.Mutex + + ctx context.Context + cancelFunc context.CancelFunc +) + +// Monitor loops over each endpoint and starts a goroutine to monitor each endpoint separately +func Monitor(cfg *config.Config) { + ctx, cancelFunc = context.WithCancel(context.Background()) + for _, endpoint := range cfg.Endpoints { + if endpoint.IsEnabled() { + // To prevent multiple requests from running at the same time, we'll wait for a little before each iteration + time.Sleep(777 * time.Millisecond) + go monitor(endpoint, cfg.Alerting, cfg.Maintenance, cfg.Connectivity, cfg.DisableMonitoringLock, cfg.Metrics, ctx) + } + } +} + +// monitor a single endpoint in a loop +func monitor(ep *endpoint.Endpoint, alertingConfig *alerting.Config, maintenanceConfig *maintenance.Config, connectivityConfig *connectivity.Config, disableMonitoringLock bool, enabledMetrics bool, ctx context.Context) { + // Run it immediately on start + execute(ep, alertingConfig, maintenanceConfig, connectivityConfig, disableMonitoringLock, enabledMetrics) + // Loop for the next executions + ticker := time.NewTicker(ep.Interval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + logr.Warnf("[watchdog.monitor] Canceling current execution of group=%s; endpoint=%s; key=%s", ep.Group, ep.Name, ep.Key()) + return + case <-ticker.C: + execute(ep, alertingConfig, maintenanceConfig, connectivityConfig, disableMonitoringLock, enabledMetrics) + } + } + // Just in case somebody wandered all the way to here and wonders, "what about ExternalEndpoints?" + // Alerting is checked every time an external endpoint is pushed to Gatus, so they're not monitored + // periodically like they are for normal endpoints. +} + +func execute(ep *endpoint.Endpoint, alertingConfig *alerting.Config, maintenanceConfig *maintenance.Config, connectivityConfig *connectivity.Config, disableMonitoringLock bool, enabledMetrics bool) { + if !disableMonitoringLock { + // By placing the lock here, we prevent multiple endpoints from being monitored at the exact same time, which + // could cause performance issues and return inaccurate results + monitoringMutex.Lock() + defer monitoringMutex.Unlock() + } + // If there's a connectivity checker configured, check if Gatus has internet connectivity + if connectivityConfig != nil && connectivityConfig.Checker != nil && !connectivityConfig.Checker.IsConnected() { + logr.Infof("[watchdog.execute] No connectivity; skipping execution") + return + } + logr.Debugf("[watchdog.execute] Monitoring group=%s; endpoint=%s; key=%s", ep.Group, ep.Name, ep.Key()) + result := ep.EvaluateHealth() + if enabledMetrics { + metrics.PublishMetricsForEndpoint(ep, result) + } + UpdateEndpointStatuses(ep, result) + if logr.GetThreshold() == logr.LevelDebug && !result.Success { + logr.Debugf("[watchdog.execute] Monitored group=%s; endpoint=%s; key=%s; success=%v; errors=%d; duration=%s; body=%s", ep.Group, ep.Name, ep.Key(), result.Success, len(result.Errors), result.Duration.Round(time.Millisecond), result.Body) + } else { + logr.Infof("[watchdog.execute] Monitored group=%s; endpoint=%s; key=%s; success=%v; errors=%d; duration=%s", ep.Group, ep.Name, ep.Key(), result.Success, len(result.Errors), result.Duration.Round(time.Millisecond)) + } + inEndpointMaintenanceWindow := false + for _, maintenanceWindow := range ep.MaintenanceWindows { + if maintenanceWindow.IsUnderMaintenance() { + logr.Debug("[watchdog.execute] Under endpoint maintenance window") + inEndpointMaintenanceWindow = true + } + } + if !maintenanceConfig.IsUnderMaintenance() && !inEndpointMaintenanceWindow { + // TODO: Consider moving this after the monitoring lock is unlocked? I mean, how much noise can a single alerting provider cause... + HandleAlerting(ep, result, alertingConfig) + } else { + logr.Debug("[watchdog.execute] Not handling alerting because currently in the maintenance window") + } + logr.Debugf("[watchdog.execute] Waiting for interval=%s before monitoring group=%s endpoint=%s (key=%s) again", ep.Interval, ep.Group, ep.Name, ep.Key()) +} + +// UpdateEndpointStatuses updates the slice of endpoint statuses +func UpdateEndpointStatuses(ep *endpoint.Endpoint, result *endpoint.Result) { + if err := store.Get().Insert(ep, result); err != nil { + logr.Errorf("[watchdog.UpdateEndpointStatuses] Failed to insert result in storage: %s", err.Error()) + } +} + +// Shutdown stops monitoring all endpoints +func Shutdown(cfg *config.Config) { + // Disable all the old HTTP connections + for _, ep := range cfg.Endpoints { + ep.Close() + } + cancelFunc() +} diff --git a/web/app/.gitignore b/web/app/.gitignore new file mode 100644 index 00000000..403adbc1 --- /dev/null +++ b/web/app/.gitignore @@ -0,0 +1,23 @@ +.DS_Store +node_modules +/dist + + +# local env files +.env.local +.env.*.local + +# Log files +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/web/app/README.md b/web/app/README.md new file mode 100644 index 00000000..e0d1daf1 --- /dev/null +++ b/web/app/README.md @@ -0,0 +1,24 @@ +# app + +## Project setup +``` +npm install +``` + +### Compiles and hot-reloads for development +``` +npm run serve +``` + +### Compiles and minifies for production +``` +npm run build +``` + +### Lints and fixes files +``` +npm run lint +``` + +### Customize configuration +See [Configuration Reference](https://cli.vuejs.org/config/). diff --git a/web/app/babel.config.js b/web/app/babel.config.js new file mode 100644 index 00000000..e9558405 --- /dev/null +++ b/web/app/babel.config.js @@ -0,0 +1,5 @@ +module.exports = { + presets: [ + '@vue/cli-plugin-babel/preset' + ] +} diff --git a/web/app/package-lock.json b/web/app/package-lock.json new file mode 100644 index 00000000..fe1e1d78 --- /dev/null +++ b/web/app/package-lock.json @@ -0,0 +1,20218 @@ +{ + "name": "gatus", + "version": "4.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "gatus", + "version": "4.0.0", + "dependencies": { + "@headlessui/vue": "^1.7.3", + "@heroicons/vue": "^2.0.12", + "core-js": "3.22.8", + "vue": "3.2.37", + "vue-router": "4.0.16" + }, + "devDependencies": { + "@vue/cli-plugin-babel": "5.0.4", + "@vue/cli-plugin-eslint": "5.0.4", + "@vue/cli-plugin-router": "5.0.4", + "@vue/cli-service": "5.0.4", + "@vue/compiler-sfc": "3.2.37", + "autoprefixer": "10.4.7", + "babel-eslint": "10.1.0", + "eslint": "7.32.0", + "eslint-plugin-vue": "7.20.0", + "postcss": "8.4.14", + "tailwindcss": "^3.1.8" + } + }, + "node_modules/@achrinza/node-ipc": { + "version": "9.2.5", + "resolved": "https://registry.npmjs.org/@achrinza/node-ipc/-/node-ipc-9.2.5.tgz", + "integrity": "sha512-kBX7Ay911iXZ3VZ1pYltj3Rfu7Ow9H7sK4H4RSfWIfWR2JKNB40K808wppoRIEzE2j2hXLU+r6TJgCAliCGhyQ==", + "dev": true, + "dependencies": { + "@node-ipc/js-queue": "2.0.3", + "event-pubsub": "4.3.0", + "js-message": "1.0.7" + }, + "engines": { + "node": "8 || 9 || 10 || 11 || 12 || 13 || 14 || 15 || 16 || 17 || 18" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.8.tgz", + "integrity": "sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.10.tgz", + "integrity": "sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.10", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-module-transforms": "^7.18.9", + "@babel/helpers": "^7.18.9", + "@babel/parser": "^7.18.10", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.18.10", + "@babel/types": "^7.18.10", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.18.12", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.12.tgz", + "integrity": "sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.10", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", + "dev": true, + "dependencies": { + "@babel/helper-explode-assignable-expression": "^7.18.6", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz", + "integrity": "sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.18.8", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.20.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.9.tgz", + "integrity": "sha512-WvypNAYaVh23QcjpMR24CwZY2Nz6hqdOcFdPbNpV56hL5H6KiFheO7Xm1aPdlLQ7d5emYZX7VZwPp9x3z+2opw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.9", + "@babel/helper-split-export-declaration": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.18.6.tgz", + "integrity": "sha512-7LcpH1wnQLGrI+4v+nPp+zUvIkF9x0ddv1Hkdue10tg3gmRnLy97DXh4STiOf1qeIInyD69Qv5kKSZzKD8B/7A==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.2.tgz", + "integrity": "sha512-r9QJJ+uDWrd+94BSPcP6/de67ygLtvVy6cK4luE6MOuDsZIdoaPBnfSpbO/+LTifjPckbKXRuI9BB/Z2/y3iTg==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-explode-assignable-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz", + "integrity": "sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==", + "dev": true, + "dependencies": { + "@babel/template": "^7.18.6", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", + "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz", + "integrity": "sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.18.6", + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz", + "integrity": "sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.18.9.tgz", + "integrity": "sha512-dNsWibVI4lNT6HiuOIBr1oyxo40HvIVmbwPUm3XZ7wMh4k2WxrxTqZwSqw/eEmXDS9np0ey5M2bz9tBmO9c+YQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", + "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz", + "integrity": "sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", + "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", + "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.18.11", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.18.11.tgz", + "integrity": "sha512-oBUlbv+rjZLh2Ks9SKi4aL7eKaAXBWleHzU89mP0G6BMUlRxSckk9tSIkgDGydhgFxHuGSlBQZfnaD47oBEB7w==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.18.9", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.18.11", + "@babel/types": "^7.18.10" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz", + "integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.18.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.11.tgz", + "integrity": "sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", + "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-proposal-optional-chaining": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.10.tgz", + "integrity": "sha512-1mFuY2TOsR1hxbjCo4QL+qlIjV07p4H4EUYw2J/WCqsvFV6V9X9z9YhXbWndc/4fw+hYGlDT7egYxliMp5O6Ew==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-static-block": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", + "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.18.10.tgz", + "integrity": "sha512-wdGTwWF5QtpTY/gbBtQLAiCnoxfD4qMbN87NYZle1dOZ9Os8Y6zXcKrIaOU8W+TIvFUWVGG9tUgNww3CjXRVVw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-replace-supers": "^7.18.9", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/plugin-syntax-decorators": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-dynamic-import": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-json-strings": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", + "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz", + "integrity": "sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.18.8", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.18.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", + "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", + "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.18.6.tgz", + "integrity": "sha512-fqyLgjcxf/1yhyZ6A+yo1u9gJ7eleFQod2lkaUsF9DQ7sbbY3Ligym3L0+I2c0WmqNKDpoD9UTb1AKP3qRMOAQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz", + "integrity": "sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", + "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", + "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-remap-async-to-generator": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz", + "integrity": "sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.9.tgz", + "integrity": "sha512-EkRQxsxoytpTlKJmSPYrsOMjCILacAjtSVkd4gChEe2kXjFCun3yohhW5I7plXJhCemM0gKsaGMcO8tinvCA5g==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-replace-supers": "^7.18.9", + "@babel/helper-split-export-declaration": "^7.18.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", + "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.9.tgz", + "integrity": "sha512-p5VCYNddPLkZTq4XymQIaIfZNJwT9YsjkPOhkVEqt6QIpQFZVM9IltqqYpOEkJoN1DPznmxUDyZ5CTZs/ZCuHA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", + "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz", + "integrity": "sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz", + "integrity": "sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-simple-access": "^7.18.6", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.9.tgz", + "integrity": "sha512-zY/VSIbbqtoRoJKo2cDTewL364jSlZGvn0LKOf9ntbfxOvjfmyrdtEEOAdswOswhZEb8UH3jDkCKHd1sPgsS0A==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-validator-identifier": "^7.18.6", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.18.6.tgz", + "integrity": "sha512-UmEOGF8XgaIqD74bC8g7iV3RYj8lMf0Bw7NJzvnS9qQhM4mg+1WHKotUIdjxgD2RGrgFLZZPCFPFj3P/kVDYhg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz", + "integrity": "sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", + "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "regenerator-transform": "^0.15.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz", + "integrity": "sha512-q5mMeYAdfEbpBAgzl7tBre/la3LeCxmDO1+wMXRdPWbcoMjR3GiXlCLk7JBZVVye0bqTGNMbt0yYVXX1B1jEWQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.9", + "babel-plugin-polyfill-corejs2": "^0.3.2", + "babel-plugin-polyfill-corejs3": "^0.5.3", + "babel-plugin-polyfill-regenerator": "^0.4.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.18.9.tgz", + "integrity": "sha512-39Q814wyoOPtIB/qGopNIL9xDChOE1pNU0ZY5dO0owhiVt/5kFm4li+/bBtwc7QotG0u5EPzqhZdjMtmqBqyQA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", + "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.10.tgz", + "integrity": "sha512-wVxs1yjFdW3Z/XkNfXKoblxoHgbtUF7/l3PvvP4m02Qz9TZ6uZGxRVYjSQeR87oQmHco9zWitW5J82DJ7sCjvA==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.18.8", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-async-generator-functions": "^7.18.10", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.18.9", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.18.6", + "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.18.9", + "@babel/plugin-transform-classes": "^7.18.9", + "@babel/plugin-transform-computed-properties": "^7.18.9", + "@babel/plugin-transform-destructuring": "^7.18.9", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.18.8", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.18.6", + "@babel/plugin-transform-modules-commonjs": "^7.18.6", + "@babel/plugin-transform-modules-systemjs": "^7.18.9", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.18.6", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.18.8", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.18.9", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.18.10", + "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.18.10", + "babel-plugin-polyfill-corejs2": "^0.3.2", + "babel-plugin-polyfill-corejs3": "^0.5.3", + "babel-plugin-polyfill-regenerator": "^0.4.0", + "core-js-compat": "^3.22.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", + "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.18.11", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.11.tgz", + "integrity": "sha512-TG9PiM2R/cWCAy6BPJKeHzNbu4lPzOSZpeMfeNErskGpTJx6trEvFaVCbDvpcxwy49BKWmEPwiW8mrysNiDvIQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.10", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.18.11", + "@babel/types": "^7.18.10", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.10.tgz", + "integrity": "sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.18.10", + "@babel/helper-validator-identifier": "^7.18.6", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "dev": true + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dev": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@headlessui/vue": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.7.3.tgz", + "integrity": "sha512-Is4iakKts9u9E0+jEZNzoJpBjwq2SamwEIoEl2RlyYSu6Zco536GsPXaQEfg/o7Eyc1GUUlcL+dJd4Rt7qyf7A==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@heroicons/vue": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@heroicons/vue/-/vue-2.0.12.tgz", + "integrity": "sha512-ypuQl/Wei7BoShO65AdRVikXDaSt6hJBRuKs2pVd/6HvxfEz18oXraH+J5G/QPIwObMEVhMb6nkOs9l3WZv/gA==", + "peerDependencies": { + "vue": ">= 3" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", + "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "dev": true + }, + "node_modules/@node-ipc/js-queue": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@node-ipc/js-queue/-/js-queue-2.0.3.tgz", + "integrity": "sha512-fL1wpr8hhD5gT2dA1qifeVaoDFlQR5es8tFuKqjHX+kdOtdNHnxkVZbtIrR2rxnMFvehkjaZRNV2H/gPXlb0hw==", + "dev": true, + "dependencies": { + "easy-stack": "1.0.1" + }, + "engines": { + "node": ">=1.0.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.21", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", + "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", + "dev": true + }, + "node_modules/@sideway/address": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", + "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "dev": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", + "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==", + "dev": true + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "dev": true + }, + "node_modules/@soda/friendly-errors-webpack-plugin": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.1.tgz", + "integrity": "sha512-h2ooWqP8XuFqTXT+NyAFbrArzfQA7R6HTezADrvD9Re8fxMLTPPniLdqVTdDaO0eIoLaAwKT+d6w+5GeTk7Vbg==", + "dev": true, + "dependencies": { + "chalk": "^3.0.0", + "error-stack-parser": "^2.0.6", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/@soda/friendly-errors-webpack-plugin/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@soda/friendly-errors-webpack-plugin/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@soda/friendly-errors-webpack-plugin/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@soda/friendly-errors-webpack-plugin/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@soda/friendly-errors-webpack-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@soda/friendly-errors-webpack-plugin/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@soda/get-current-script": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@soda/get-current-script/-/get-current-script-1.0.2.tgz", + "integrity": "sha512-T7VNNlYVM1SgQ+VsMYhnDkcGmWhQdL0bDyGm5TlQ3GBXnJscEClUUOKduWTmm2zCnvNLC1hc3JpuXjs/nFOc5w==", + "dev": true + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", + "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", + "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", + "dev": true, + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.4.5", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz", + "integrity": "sha512-dhsC09y1gpJWnK+Ff4SGvCuSnk9DaU0BJZSzOwa6GVSg65XtTugLBITDAAzRU5duGBoXBHpdR/9jHGxJjNflJQ==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", + "dev": true + }, + "node_modules/@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.30", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.30.tgz", + "integrity": "sha512-gstzbTWro2/nFed1WXtf+TtrpwxH7Ggs4RLYTLbeVgIkUQOI3WG/JKjgeOU1zXDvezllupjrf8OPIdvTbIaVOQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "dev": true + }, + "node_modules/@types/http-proxy": { + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", + "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", + "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", + "dev": true + }, + "node_modules/@types/minimist": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.7.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.6.tgz", + "integrity": "sha512-EdxgKRXgYsNITy5mjjXjVE/CS8YENSdhiagGrLqjG0pvA2owgJ6i4l7wy/PFZGC0B1/H20lWKN7ONVDNYDZm7A==", + "dev": true + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "dev": true + }, + "node_modules/@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, + "node_modules/@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", + "dev": true, + "dependencies": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", + "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vue/babel-helper-vue-jsx-merge-props": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.2.1.tgz", + "integrity": "sha512-QOi5OW45e2R20VygMSNhyQHvpdUwQZqGPc748JLGCYEy+yp8fNFNdbNIGAgZmi9e+2JHPd6i6idRuqivyicIkA==", + "dev": true + }, + "node_modules/@vue/babel-helper-vue-transform-on": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.0.2.tgz", + "integrity": "sha512-hz4R8tS5jMn8lDq6iD+yWL6XNB699pGIVLk7WSJnn1dbpjaazsjZQkieJoRX6gW5zpYSCFqQ7jUquPNY65tQYA==", + "dev": true + }, + "node_modules/@vue/babel-plugin-jsx": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.1.1.tgz", + "integrity": "sha512-j2uVfZjnB5+zkcbc/zsOc0fSNGCMMjaEXP52wdwdIfn0qjFfEYpYZBFKFg+HHnQeJCVrjOeO0YxgaL7DMrym9w==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/plugin-syntax-jsx": "^7.0.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "@vue/babel-helper-vue-transform-on": "^1.0.2", + "camelcase": "^6.0.0", + "html-tags": "^3.1.0", + "svg-tags": "^1.0.0" + } + }, + "node_modules/@vue/babel-plugin-transform-vue-jsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-1.2.1.tgz", + "integrity": "sha512-HJuqwACYehQwh1fNT8f4kyzqlNMpBuUK4rSiSES5D4QsYncv5fxFsLyrxFPG2ksO7t5WP+Vgix6tt6yKClwPzA==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/plugin-syntax-jsx": "^7.2.0", + "@vue/babel-helper-vue-jsx-merge-props": "^1.2.1", + "html-tags": "^2.0.0", + "lodash.kebabcase": "^4.1.1", + "svg-tags": "^1.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@vue/babel-plugin-transform-vue-jsx/node_modules/html-tags": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-2.0.0.tgz", + "integrity": "sha512-+Il6N8cCo2wB/Vd3gqy/8TZhTD3QvcVeQLCnZiGkGCH3JP28IgGAY41giccp2W4R3jfyJPAP318FQTa1yU7K7g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@vue/babel-preset-app": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@vue/babel-preset-app/-/babel-preset-app-5.0.8.tgz", + "integrity": "sha512-yl+5qhpjd8e1G4cMXfORkkBlvtPCIgmRf3IYCWYDKIQ7m+PPa5iTm4feiNmCMD6yGqQWMhhK/7M3oWGL9boKwg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.16", + "@babel/helper-compilation-targets": "^7.12.16", + "@babel/helper-module-imports": "^7.12.13", + "@babel/plugin-proposal-class-properties": "^7.12.13", + "@babel/plugin-proposal-decorators": "^7.12.13", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-jsx": "^7.12.13", + "@babel/plugin-transform-runtime": "^7.12.15", + "@babel/preset-env": "^7.12.16", + "@babel/runtime": "^7.12.13", + "@vue/babel-plugin-jsx": "^1.0.3", + "@vue/babel-preset-jsx": "^1.1.2", + "babel-plugin-dynamic-import-node": "^2.3.3", + "core-js": "^3.8.3", + "core-js-compat": "^3.8.3", + "semver": "^7.3.4" + }, + "peerDependencies": { + "@babel/core": "*", + "core-js": "^3", + "vue": "^2 || ^3.2.13" + }, + "peerDependenciesMeta": { + "core-js": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/@vue/babel-preset-app/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@vue/babel-preset-jsx": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@vue/babel-preset-jsx/-/babel-preset-jsx-1.3.1.tgz", + "integrity": "sha512-ml+nqcSKp8uAqFZLNc7OWLMzR7xDBsUfkomF98DtiIBlLqlq4jCQoLINARhgqRIyKdB+mk/94NWpIb4pL6D3xw==", + "dev": true, + "dependencies": { + "@vue/babel-helper-vue-jsx-merge-props": "^1.2.1", + "@vue/babel-plugin-transform-vue-jsx": "^1.2.1", + "@vue/babel-sugar-composition-api-inject-h": "^1.3.0", + "@vue/babel-sugar-composition-api-render-instance": "^1.3.0", + "@vue/babel-sugar-functional-vue": "^1.2.2", + "@vue/babel-sugar-inject-h": "^1.2.2", + "@vue/babel-sugar-v-model": "^1.3.0", + "@vue/babel-sugar-v-on": "^1.3.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0", + "vue": "*" + }, + "peerDependenciesMeta": { + "vue": { + "optional": true + } + } + }, + "node_modules/@vue/babel-sugar-composition-api-inject-h": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vue/babel-sugar-composition-api-inject-h/-/babel-sugar-composition-api-inject-h-1.3.0.tgz", + "integrity": "sha512-pIDOutEpqbURdVw7xhgxmuDW8Tl+lTgzJZC5jdlUu0lY2+izT9kz3Umd/Tbu0U5cpCJ2Yhu87BZFBzWpS0Xemg==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-jsx": "^7.2.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@vue/babel-sugar-composition-api-render-instance": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vue/babel-sugar-composition-api-render-instance/-/babel-sugar-composition-api-render-instance-1.3.0.tgz", + "integrity": "sha512-NYNnU2r7wkJLMV5p9Zj4pswmCs037O/N2+/Fs6SyX7aRFzXJRP1/2CZh5cIwQxWQajHXuCUd5mTb7DxoBVWyTg==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-jsx": "^7.2.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@vue/babel-sugar-functional-vue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vue/babel-sugar-functional-vue/-/babel-sugar-functional-vue-1.2.2.tgz", + "integrity": "sha512-JvbgGn1bjCLByIAU1VOoepHQ1vFsroSA/QkzdiSs657V79q6OwEWLCQtQnEXD/rLTA8rRit4rMOhFpbjRFm82w==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-jsx": "^7.2.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@vue/babel-sugar-inject-h": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vue/babel-sugar-inject-h/-/babel-sugar-inject-h-1.2.2.tgz", + "integrity": "sha512-y8vTo00oRkzQTgufeotjCLPAvlhnpSkcHFEp60+LJUwygGcd5Chrpn5480AQp/thrxVm8m2ifAk0LyFel9oCnw==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-jsx": "^7.2.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@vue/babel-sugar-v-model": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vue/babel-sugar-v-model/-/babel-sugar-v-model-1.3.0.tgz", + "integrity": "sha512-zcsabmdX48JmxTObn3xmrvvdbEy8oo63DphVyA3WRYGp4SEvJRpu/IvZCVPl/dXLuob2xO/QRuncqPgHvZPzpA==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-jsx": "^7.2.0", + "@vue/babel-helper-vue-jsx-merge-props": "^1.2.1", + "@vue/babel-plugin-transform-vue-jsx": "^1.2.1", + "camelcase": "^5.0.0", + "html-tags": "^2.0.0", + "svg-tags": "^1.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@vue/babel-sugar-v-model/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@vue/babel-sugar-v-model/node_modules/html-tags": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-2.0.0.tgz", + "integrity": "sha512-+Il6N8cCo2wB/Vd3gqy/8TZhTD3QvcVeQLCnZiGkGCH3JP28IgGAY41giccp2W4R3jfyJPAP318FQTa1yU7K7g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@vue/babel-sugar-v-on": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vue/babel-sugar-v-on/-/babel-sugar-v-on-1.3.0.tgz", + "integrity": "sha512-8VZgrS0G5bh7+Prj7oJkzg9GvhSPnuW5YT6MNaVAEy4uwxRLJ8GqHenaStfllChTao4XZ3EZkNtHB4Xbr/ePdA==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-jsx": "^7.2.0", + "@vue/babel-plugin-transform-vue-jsx": "^1.2.1", + "camelcase": "^5.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@vue/babel-sugar-v-on/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@vue/cli-overlay": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@vue/cli-overlay/-/cli-overlay-5.0.8.tgz", + "integrity": "sha512-KmtievE/B4kcXp6SuM2gzsnSd8WebkQpg3XaB6GmFh1BJGRqa1UiW9up7L/Q67uOdTigHxr5Ar2lZms4RcDjwQ==", + "dev": true + }, + "node_modules/@vue/cli-plugin-babel": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@vue/cli-plugin-babel/-/cli-plugin-babel-5.0.4.tgz", + "integrity": "sha512-413ZwOWLtgw5vWJoMbrv36crW3qTas4Iru8sU7cRb0IqEZbS28R9X4PVtO8Pek2NYFbrs2XKRYOB7GblB6hVqg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.16", + "@vue/babel-preset-app": "^5.0.4", + "@vue/cli-shared-utils": "^5.0.4", + "babel-loader": "^8.2.2", + "thread-loader": "^3.0.0", + "webpack": "^5.54.0" + }, + "peerDependencies": { + "@vue/cli-service": "^3.0.0 || ^4.0.0 || ^5.0.0-0" + } + }, + "node_modules/@vue/cli-plugin-eslint": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@vue/cli-plugin-eslint/-/cli-plugin-eslint-5.0.4.tgz", + "integrity": "sha512-NLBEKFTFJhYHMzQ3z+sb6xlkcNaN/RcbZ3hiCY72aJe4YOO8jEAp0XkPzlHd4xYkMW7jrmOwwLOPw+3BJ4b77Q==", + "dev": true, + "dependencies": { + "@vue/cli-shared-utils": "^5.0.4", + "eslint-webpack-plugin": "^3.1.0", + "globby": "^11.0.2", + "webpack": "^5.54.0", + "yorkie": "^2.0.0" + }, + "peerDependencies": { + "@vue/cli-service": "^3.0.0 || ^4.0.0 || ^5.0.0-0", + "eslint": ">=7.5.0" + } + }, + "node_modules/@vue/cli-plugin-router": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@vue/cli-plugin-router/-/cli-plugin-router-5.0.4.tgz", + "integrity": "sha512-lylzCuH3Br0BcTz5IxxSffpyoF9dQ2k4jTdK8QlWrnRanWGw7P9C0kYMr9rohHaXpvAlu6bio392gbNIWpEepg==", + "dev": true, + "dependencies": { + "@vue/cli-shared-utils": "^5.0.4" + }, + "peerDependencies": { + "@vue/cli-service": "^3.0.0 || ^4.0.0 || ^5.0.0-0" + } + }, + "node_modules/@vue/cli-plugin-vuex": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@vue/cli-plugin-vuex/-/cli-plugin-vuex-5.0.8.tgz", + "integrity": "sha512-HSYWPqrunRE5ZZs8kVwiY6oWcn95qf/OQabwLfprhdpFWAGtLStShjsGED2aDpSSeGAskQETrtR/5h7VqgIlBA==", + "dev": true, + "peerDependencies": { + "@vue/cli-service": "^3.0.0 || ^4.0.0 || ^5.0.0-0" + } + }, + "node_modules/@vue/cli-service": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@vue/cli-service/-/cli-service-5.0.4.tgz", + "integrity": "sha512-xRiLNTFYmMCT9edZpyYXHijW5xot3gbZpcWDOXUOhKPHN4qs4XqWALnZlU97JYjZOr3XIr/ZvyciyEfrlUVqSA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.12.16", + "@soda/friendly-errors-webpack-plugin": "^1.8.0", + "@soda/get-current-script": "^1.0.2", + "@types/minimist": "^1.2.0", + "@vue/cli-overlay": "^5.0.4", + "@vue/cli-plugin-router": "^5.0.4", + "@vue/cli-plugin-vuex": "^5.0.4", + "@vue/cli-shared-utils": "^5.0.4", + "@vue/component-compiler-utils": "^3.3.0", + "@vue/vue-loader-v15": "npm:vue-loader@^15.9.7", + "@vue/web-component-wrapper": "^1.3.0", + "acorn": "^8.0.5", + "acorn-walk": "^8.0.2", + "address": "^1.1.2", + "autoprefixer": "^10.2.4", + "browserslist": "^4.16.3", + "case-sensitive-paths-webpack-plugin": "^2.3.0", + "cli-highlight": "^2.1.10", + "clipboardy": "^2.3.0", + "cliui": "^7.0.4", + "copy-webpack-plugin": "^9.0.1", + "css-loader": "^6.5.0", + "css-minimizer-webpack-plugin": "^3.0.2", + "cssnano": "^5.0.0", + "debug": "^4.1.1", + "default-gateway": "^6.0.3", + "dotenv": "^10.0.0", + "dotenv-expand": "^5.1.0", + "fs-extra": "^9.1.0", + "globby": "^11.0.2", + "hash-sum": "^2.0.0", + "html-webpack-plugin": "^5.1.0", + "is-file-esm": "^1.0.0", + "launch-editor-middleware": "^2.2.1", + "lodash.defaultsdeep": "^4.6.1", + "lodash.mapvalues": "^4.6.0", + "mini-css-extract-plugin": "^2.5.3", + "minimist": "^1.2.5", + "module-alias": "^2.2.2", + "portfinder": "^1.0.26", + "postcss": "^8.2.6", + "postcss-loader": "^6.1.1", + "progress-webpack-plugin": "^1.0.12", + "ssri": "^8.0.1", + "terser-webpack-plugin": "^5.1.1", + "thread-loader": "^3.0.0", + "vue-loader": "^17.0.0", + "vue-style-loader": "^4.1.3", + "webpack": "^5.54.0", + "webpack-bundle-analyzer": "^4.4.0", + "webpack-chain": "^6.5.1", + "webpack-dev-server": "^4.7.3", + "webpack-merge": "^5.7.3", + "webpack-virtual-modules": "^0.4.2", + "whatwg-fetch": "^3.6.2" + }, + "bin": { + "vue-cli-service": "bin/vue-cli-service.js" + }, + "engines": { + "node": "^12.0.0 || >= 14.0.0" + }, + "peerDependencies": { + "vue-template-compiler": "^2.0.0", + "webpack-sources": "*" + }, + "peerDependenciesMeta": { + "cache-loader": { + "optional": true + }, + "less-loader": { + "optional": true + }, + "pug-plain-loader": { + "optional": true + }, + "raw-loader": { + "optional": true + }, + "sass-loader": { + "optional": true + }, + "stylus-loader": { + "optional": true + }, + "vue-template-compiler": { + "optional": true + }, + "webpack-sources": { + "optional": true + } + } + }, + "node_modules/@vue/cli-shared-utils": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@vue/cli-shared-utils/-/cli-shared-utils-5.0.8.tgz", + "integrity": "sha512-uK2YB7bBVuQhjOJF+O52P9yFMXeJVj7ozqJkwYE9PlMHL1LMHjtCYm4cSdOebuPzyP+/9p0BimM/OqxsevIopQ==", + "dev": true, + "dependencies": { + "@achrinza/node-ipc": "^9.2.5", + "chalk": "^4.1.2", + "execa": "^1.0.0", + "joi": "^17.4.0", + "launch-editor": "^2.2.1", + "lru-cache": "^6.0.0", + "node-fetch": "^2.6.7", + "open": "^8.0.2", + "ora": "^5.3.0", + "read-pkg": "^5.1.1", + "semver": "^7.3.4", + "strip-ansi": "^6.0.0" + } + }, + "node_modules/@vue/cli-shared-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@vue/cli-shared-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@vue/cli-shared-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@vue/cli-shared-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@vue/cli-shared-utils/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@vue/cli-shared-utils/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@vue/cli-shared-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.2.37", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.37.tgz", + "integrity": "sha512-81KhEjo7YAOh0vQJoSmAD68wLfYqJvoiD4ulyedzF+OEk/bk6/hx3fTNVfuzugIIaTrOx4PGx6pAiBRe5e9Zmg==", + "dependencies": { + "@babel/parser": "^7.16.4", + "@vue/shared": "3.2.37", + "estree-walker": "^2.0.2", + "source-map": "^0.6.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.2.37", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.37.tgz", + "integrity": "sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ==", + "dependencies": { + "@vue/compiler-core": "3.2.37", + "@vue/shared": "3.2.37" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.2.37", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.37.tgz", + "integrity": "sha512-+7i/2+9LYlpqDv+KTtWhOZH+pa8/HnX/905MdVmAcI/mPQOBwkHHIzrsEsucyOIZQYMkXUiTkmZq5am/NyXKkg==", + "dependencies": { + "@babel/parser": "^7.16.4", + "@vue/compiler-core": "3.2.37", + "@vue/compiler-dom": "3.2.37", + "@vue/compiler-ssr": "3.2.37", + "@vue/reactivity-transform": "3.2.37", + "@vue/shared": "3.2.37", + "estree-walker": "^2.0.2", + "magic-string": "^0.25.7", + "postcss": "^8.1.10", + "source-map": "^0.6.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.2.37", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.37.tgz", + "integrity": "sha512-7mQJD7HdXxQjktmsWp/J67lThEIcxLemz1Vb5I6rYJHR5vI+lON3nPGOH3ubmbvYGt8xEUaAr1j7/tIFWiEOqw==", + "dependencies": { + "@vue/compiler-dom": "3.2.37", + "@vue/shared": "3.2.37" + } + }, + "node_modules/@vue/component-compiler-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.3.0.tgz", + "integrity": "sha512-97sfH2mYNU+2PzGrmK2haqffDpVASuib9/w2/noxiFi31Z54hW+q3izKQXXQZSNhtiUpAI36uSuYepeBe4wpHQ==", + "dev": true, + "dependencies": { + "consolidate": "^0.15.1", + "hash-sum": "^1.0.2", + "lru-cache": "^4.1.2", + "merge-source-map": "^1.1.0", + "postcss": "^7.0.36", + "postcss-selector-parser": "^6.0.2", + "source-map": "~0.6.1", + "vue-template-es2015-compiler": "^1.9.0" + }, + "optionalDependencies": { + "prettier": "^1.18.2 || ^2.0.0" + } + }, + "node_modules/@vue/component-compiler-utils/node_modules/hash-sum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", + "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", + "dev": true + }, + "node_modules/@vue/component-compiler-utils/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/@vue/component-compiler-utils/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "node_modules/@vue/component-compiler-utils/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/@vue/component-compiler-utils/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + }, + "node_modules/@vue/devtools-api": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.2.1.tgz", + "integrity": "sha512-OEgAMeQXvCoJ+1x8WyQuVZzFo0wcyCmUR3baRVLmKBo1LmYZWMlRiXlux5jd0fqVJu6PfDbOrZItVqUEzLobeQ==" + }, + "node_modules/@vue/reactivity": { + "version": "3.2.37", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.37.tgz", + "integrity": "sha512-/7WRafBOshOc6m3F7plwzPeCu/RCVv9uMpOwa/5PiY1Zz+WLVRWiy0MYKwmg19KBdGtFWsmZ4cD+LOdVPcs52A==", + "dependencies": { + "@vue/shared": "3.2.37" + } + }, + "node_modules/@vue/reactivity-transform": { + "version": "3.2.37", + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.37.tgz", + "integrity": "sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg==", + "dependencies": { + "@babel/parser": "^7.16.4", + "@vue/compiler-core": "3.2.37", + "@vue/shared": "3.2.37", + "estree-walker": "^2.0.2", + "magic-string": "^0.25.7" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.2.37", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.37.tgz", + "integrity": "sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ==", + "dependencies": { + "@vue/reactivity": "3.2.37", + "@vue/shared": "3.2.37" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.2.37", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.37.tgz", + "integrity": "sha512-HimKdh9BepShW6YozwRKAYjYQWg9mQn63RGEiSswMbW+ssIht1MILYlVGkAGGQbkhSh31PCdoUcfiu4apXJoPw==", + "dependencies": { + "@vue/runtime-core": "3.2.37", + "@vue/shared": "3.2.37", + "csstype": "^2.6.8" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.2.37", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.37.tgz", + "integrity": "sha512-kLITEJvaYgZQ2h47hIzPh2K3jG8c1zCVbp/o/bzQOyvzaKiCquKS7AaioPI28GNxIsE/zSx+EwWYsNxDCX95MA==", + "dependencies": { + "@vue/compiler-ssr": "3.2.37", + "@vue/shared": "3.2.37" + }, + "peerDependencies": { + "vue": "3.2.37" + } + }, + "node_modules/@vue/shared": { + "version": "3.2.37", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.37.tgz", + "integrity": "sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw==" + }, + "node_modules/@vue/vue-loader-v15": { + "name": "vue-loader", + "version": "15.10.0", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.10.0.tgz", + "integrity": "sha512-VU6tuO8eKajrFeBzMssFUP9SvakEeeSi1BxdTH5o3+1yUyrldp8IERkSdXlMI2t4kxF2sqYUDsQY+WJBxzBmZg==", + "dev": true, + "dependencies": { + "@vue/component-compiler-utils": "^3.1.0", + "hash-sum": "^1.0.2", + "loader-utils": "^1.1.0", + "vue-hot-reload-api": "^2.3.0", + "vue-style-loader": "^4.1.0" + }, + "peerDependencies": { + "css-loader": "*", + "webpack": "^3.0.0 || ^4.1.0 || ^5.0.0-0" + }, + "peerDependenciesMeta": { + "cache-loader": { + "optional": true + }, + "vue-template-compiler": { + "optional": true + } + } + }, + "node_modules/@vue/vue-loader-v15/node_modules/hash-sum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", + "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", + "dev": true + }, + "node_modules/@vue/web-component-wrapper": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vue/web-component-wrapper/-/web-component-wrapper-1.3.0.tgz", + "integrity": "sha512-Iu8Tbg3f+emIIMmI2ycSI8QcEuAUgPTgHwesDU1eKMLE4YC/c/sFbGc70QgMq31ijRftV0R7vCm9co6rldCeOA==", + "dev": true + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "dev": true, + "dependencies": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + } + }, + "node_modules/acorn-node/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-node/node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/address": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.0.tgz", + "integrity": "sha512-tNEZYz5G/zYunxFm7sfhAxkXEuLj3K6BKwv6ZURlsF6yiUQ65z0Q2wZW9L5cPUl9ocofGvXOdFYbFHp0+6MOig==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.7", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.7.tgz", + "integrity": "sha512-ypHju4Y2Oav95SipEcCcI5J7CGPuvz8oat7sUtYj3ClK44bldfvtvcxK6IEK++7rqB7YchDGzweZIBG+SD0ZAA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + ], + "dependencies": { + "browserslist": "^4.20.3", + "caniuse-lite": "^1.0.30001335", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/babel-eslint": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", + "deprecated": "babel-eslint is now @babel/eslint-parser. This package will no longer receive updates.", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + }, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "eslint": ">= 4.12.1" + } + }, + "node_modules/babel-loader": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz", + "integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==", + "dev": true, + "dependencies": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "engines": { + "node": ">= 8.9" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "webpack": ">=2" + } + }, + "node_modules/babel-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dev": true, + "dependencies": { + "object.assign": "^4.1.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.2.tgz", + "integrity": "sha512-LPnodUl3lS0/4wN3Rb+m+UK8s7lj2jcLRrjho4gLw+OJs+I4bvGXshINesY5xx/apM+biTnQ9reDI8yj+0M5+Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.2", + "semver": "^6.1.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz", + "integrity": "sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.2", + "core-js-compat": "^3.21.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.0.tgz", + "integrity": "sha512-RW1cnryiADFeHmfLS+WW/G431p1PsW5qdRdz0SDRi7TKcUgc7Oh/uXkT7MZ/+tGsT1BkczEAmD5XjUyJ5SWDTw==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "node_modules/body-parser": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/bonjour-service": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.0.13.tgz", + "integrity": "sha512-LWKRU/7EqDUC9CTAQtuZl5HzBALoCYwtLhffW3et7vZMwv3bWLpJf8bRYlMD5OCcDpTfnPgNCV4yo9ZIaJGMiA==", + "dev": true, + "dependencies": { + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", + "integrity": "sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001370", + "electron-to-chromium": "^1.4.202", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.5" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001378", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001378.tgz", + "integrity": "sha512-JVQnfoO7FK7WvU4ZkBRbPjaot4+YqxogSDosHv0Hv5mWpUESmN+UubMU6L/hGz8QlQ2aY5U0vR6MOs6j/CXpNA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/case-sensitive-paths-webpack-plugin": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", + "integrity": "sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", + "dev": true + }, + "node_modules/clean-css": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz", + "integrity": "sha512-lCr8OHhiWCTw4v8POJovCoh4T7I9U11yVsPjMWWnnMmp9ZowCxyad1Pathle/9HjaDp+fdQKjO9fQydE6RHTZg==", + "dev": true, + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-highlight/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cli-highlight/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-highlight/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/cli-highlight/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/cli-highlight/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-highlight/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", + "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz", + "integrity": "sha512-mKhiIL2DrQIsuXMgBgnfEHOZOryC7kY7YO//TN6c63wlEm3NG5tz+YgY5rVi29KCmq/QQjKYvM7a19+MDOTHOQ==", + "dev": true, + "dependencies": { + "arch": "^2.1.1", + "execa": "^1.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true + }, + "node_modules/colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "dev": true + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/consolidate": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz", + "integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==", + "dev": true, + "dependencies": { + "bluebird": "^3.1.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/copy-webpack-plugin": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-9.1.0.tgz", + "integrity": "sha512-rxnR7PaGigJzhqETHGmAcxKnLZSR5u1Y3/bcIv/1FnqXedcL/E2ewK7ZCNrArJKCiSv8yVXhTqetJh8inDvfsA==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.7", + "glob-parent": "^6.0.1", + "globby": "^11.0.3", + "normalize-path": "^3.0.0", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/core-js": { + "version": "3.22.8", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.22.8.tgz", + "integrity": "sha512-UoGQ/cfzGYIuiq6Z7vWL1HfkE9U9IZ4Ub+0XSiJTCzvbZzgPA69oDF2f+lgJ6dFFLEdjW5O6svvoKzXX23xFkA==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.24.1.tgz", + "integrity": "sha512-XhdNAGeRnTpp8xbD+sR/HFDK9CbeeeqXT6TuofXh3urqEevzkWmLRgrVoykodsw8okqo2pu1BOmuCKrHx63zdw==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.3", + "semver": "7.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cosmiconfig": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", + "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "dev": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-declaration-sorter": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.0.tgz", + "integrity": "sha512-OGT677UGHJTAVMRhPO+HJ4oKln3wkBTwtDFH0ojbqm+MJm6xuDMHp2nkhh/ThaBqq20IbraBQSWKfSLNHQO9Og==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-loader": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", + "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.7", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.5" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/css-loader/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/css-minimizer-webpack-plugin": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz", + "integrity": "sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==", + "dev": true, + "dependencies": { + "cssnano": "^5.0.6", + "jest-worker": "^27.0.2", + "postcss": "^8.3.5", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@parcel/css": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "5.1.13", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.13.tgz", + "integrity": "sha512-S2SL2ekdEz6w6a2epXn4CmMKU4K3KpcyXLKfAYc9UQQqJRkD/2eLUG0vJ3Db/9OvO5GuAdgXw3pFbR6abqghDQ==", + "dev": true, + "dependencies": { + "cssnano-preset-default": "^5.2.12", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-preset-default": { + "version": "5.2.12", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.12.tgz", + "integrity": "sha512-OyCBTZi+PXgylz9HAA5kHyoYhfGcYdwFmyaJzWnzxuGRtnMw/kR6ilW9XzlzlRAtB6PLT/r+prYgkef7hngFew==", + "dev": true, + "dependencies": { + "css-declaration-sorter": "^6.3.0", + "cssnano-utils": "^3.1.0", + "postcss-calc": "^8.2.3", + "postcss-colormin": "^5.3.0", + "postcss-convert-values": "^5.1.2", + "postcss-discard-comments": "^5.1.2", + "postcss-discard-duplicates": "^5.1.0", + "postcss-discard-empty": "^5.1.1", + "postcss-discard-overridden": "^5.1.0", + "postcss-merge-longhand": "^5.1.6", + "postcss-merge-rules": "^5.1.2", + "postcss-minify-font-values": "^5.1.0", + "postcss-minify-gradients": "^5.1.1", + "postcss-minify-params": "^5.1.3", + "postcss-minify-selectors": "^5.2.1", + "postcss-normalize-charset": "^5.1.0", + "postcss-normalize-display-values": "^5.1.0", + "postcss-normalize-positions": "^5.1.1", + "postcss-normalize-repeat-style": "^5.1.1", + "postcss-normalize-string": "^5.1.0", + "postcss-normalize-timing-functions": "^5.1.0", + "postcss-normalize-unicode": "^5.1.0", + "postcss-normalize-url": "^5.1.0", + "postcss-normalize-whitespace": "^5.1.1", + "postcss-ordered-values": "^5.1.3", + "postcss-reduce-initial": "^5.1.0", + "postcss-reduce-transforms": "^5.1.0", + "postcss-svgo": "^5.1.0", + "postcss-unique-selectors": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", + "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/csstype": { + "version": "2.6.20", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz", + "integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-1.5.2.tgz", + "integrity": "sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/default-gateway/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/default-gateway/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==", + "dev": true + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true + }, + "node_modules/detective": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz", + "integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==", + "dev": true, + "dependencies": { + "acorn-node": "^1.8.2", + "defined": "^1.0.0", + "minimist": "^1.2.6" + }, + "bin": { + "detective": "bin/detective.js" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "node_modules/dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", + "dev": true + }, + "node_modules/dns-packet": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz", + "integrity": "sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==", + "dev": true, + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "dev": true + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "node_modules/easy-stack": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/easy-stack/-/easy-stack-1.0.1.tgz", + "integrity": "sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.224", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.224.tgz", + "integrity": "sha512-dOujC5Yzj0nOVE23iD5HKqrRSDj2SD7RazpZS/b/WX85MtO6/LzKDF4TlYZTBteB+7fvSg5JpWh0sN7fImNF8w==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", + "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "dev": true, + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-vue": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.20.0.tgz", + "integrity": "sha512-oVNDqzBC9h3GO+NTgWeLMhhGigy6/bQaQbHS+0z7C4YEu/qK/yxHvca/2PTZtGNPsCrHwOTgKMrwu02A9iPBmw==", + "dev": true, + "dependencies": { + "eslint-utils": "^2.1.0", + "natural-compare": "^1.4.0", + "semver": "^6.3.0", + "vue-eslint-parser": "^7.10.0" + }, + "engines": { + "node": ">=8.10" + }, + "peerDependencies": { + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-webpack-plugin": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-3.2.0.tgz", + "integrity": "sha512-avrKcGncpPbPSUHX6B3stNGzkKFto3eL+DKM4+VyMrVnhPc3vRczVlCq3uhuFOdRvDHTVXuzwk1ZKUrqDQHQ9w==", + "dev": true, + "dependencies": { + "@types/eslint": "^7.29.0 || ^8.4.1", + "jest-worker": "^28.0.2", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0", + "webpack": "^5.0.0" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/jest-worker": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", + "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/eslint-webpack-plugin/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/eslint/node_modules/@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "dependencies": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/espree/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-pubsub": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/event-pubsub/-/event-pubsub-4.3.0.tgz", + "integrity": "sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/execa/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/execa/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/execa/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/express": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.0", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.10.3", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", + "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs-monkey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", + "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", + "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-sum": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz", + "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==", + "dev": true + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", + "dev": true + }, + "node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dev": true, + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-tags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz", + "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", + "integrity": "sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==", + "dev": true, + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "webpack": "^5.20.0" + } + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dev": true, + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ipaddr.js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", + "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-ci": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", + "dev": true, + "dependencies": { + "ci-info": "^1.5.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-core-module": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-file-esm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-file-esm/-/is-file-esm-1.0.0.tgz", + "integrity": "sha512-rZlaNKb4Mr8WlRu2A9XdeoKgnO5aA53XdPHgCKVyCrQ/rWi89RET1+bq37Ru46obaQXeiX4vmFIm1vks41hoSA==", + "dev": true, + "dependencies": { + "read-pkg-up": "^7.0.1" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/javascript-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz", + "integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==", + "dev": true + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/joi": { + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.6.0.tgz", + "integrity": "sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw==", + "dev": true, + "dependencies": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.0", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/js-message": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.7.tgz", + "integrity": "sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==", + "dev": true, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/klona": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz", + "integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/launch-editor": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.5.0.tgz", + "integrity": "sha512-coRiIMBJ3JF7yX8nZE4Fr+xxUy+3WTRsDSwIzHghU28gjXwkAWsvac3BpZrL/jHtbiqQ4TiRAyTJmsgErNk1jQ==", + "dev": true, + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.7.3" + } + }, + "node_modules/launch-editor-middleware": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/launch-editor-middleware/-/launch-editor-middleware-2.5.0.tgz", + "integrity": "sha512-kv9MMO81pbYjznk9j/DBu0uBGxIpT6uYhGajq6fxdGEPb+DCRBoS96jGkhe3MJumdY3zZFkuS8CFPTZI9DaBNw==", + "dev": true, + "dependencies": { + "launch-editor": "^2.5.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", + "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/loader-utils/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lodash.defaultsdeep": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz", + "integrity": "sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==", + "dev": true + }, + "node_modules/lodash.kebabcase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", + "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", + "dev": true + }, + "node_modules/lodash.mapvalues": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz", + "integrity": "sha512-JPFqXFeZQ7BfS00H58kClY7SPVeHertPE0lNuCyZ26/XlN8TvakYD7b9bGyNmXbT/D3BbtPAAmq90gPWqLkxlQ==", + "dev": true + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-update": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz", + "integrity": "sha512-vlP11XfFGyeNQlmEn9tJ66rEW1coA/79m5z6BCkudjbAGE83uhAcGYrBFwfs3AdLiLzGRusRPAbSPK9xZteCmg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^3.0.0", + "cli-cursor": "^2.0.0", + "wrap-ansi": "^3.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/log-update/node_modules/cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", + "dev": true, + "dependencies": { + "restore-cursor": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/log-update/node_modules/mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/log-update/node_modules/onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/log-update/node_modules/restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", + "dev": true, + "dependencies": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", + "integrity": "sha512-iXR3tDXpbnTpzjKSylUJRkLuOrEC7hwEB221cgn6wtF8wpmz28puFXAEfPT5zrjM3wahygB//VuWEr1vTkDcNQ==", + "dev": true, + "dependencies": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.7.tgz", + "integrity": "sha512-ygaiUSNalBX85388uskeCyhSAoOSgzBbtVCr9jA2RROssFL9Q19/ZXFqS+2Th2sr1ewNIWgFdLzLC3Yl1Zv+lw==", + "dev": true, + "dependencies": { + "fs-monkey": "^1.0.3" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "node_modules/merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "dev": true, + "dependencies": { + "source-map": "^0.6.1" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.1.tgz", + "integrity": "sha512-wd+SD57/K6DiV7jIR34P+s3uckTRuQvx0tKPcvjFlrEylk6P4mQ2KSWk1hblj1Kxaqok7LogKOieygXqBczNlg==", + "dev": true, + "dependencies": { + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "node_modules/minipass": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", + "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/module-alias": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", + "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==", + "dev": true + }, + "node_modules/mrmime": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", + "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", + "dev": true + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "dev": true, + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "dev": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ora/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/ora/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/portfinder": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", + "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==", + "dev": true, + "dependencies": { + "async": "^2.6.4", + "debug": "^3.2.7", + "mkdirp": "^0.5.6" + }, + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/portfinder/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/postcss": { + "version": "8.4.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", + "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-calc": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", + "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.9", + "postcss-value-parser": "^4.2.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-colormin": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.0.tgz", + "integrity": "sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==", + "dev": true, + "dependencies": { + "browserslist": "^4.16.6", + "caniuse-api": "^3.0.0", + "colord": "^2.9.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-convert-values": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.2.tgz", + "integrity": "sha512-c6Hzc4GAv95B7suy4udszX9Zy4ETyMCgFPUDtWjdFTKH1SE9eFY/jEpHSwTH1QPuwxHpWslhckUQWbNRM4ho5g==", + "dev": true, + "dependencies": { + "browserslist": "^4.20.3", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-comments": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", + "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", + "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-empty": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", + "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", + "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-import": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", + "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.0.tgz", + "integrity": "sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==", + "dev": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.3.3" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-loader": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", + "integrity": "sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==", + "dev": true, + "dependencies": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.5" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/postcss-loader/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postcss-merge-longhand": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.6.tgz", + "integrity": "sha512-6C/UGF/3T5OE2CEbOuX7iNO63dnvqhGZeUnKkDeifebY0XqkkvrctYSZurpNE902LDf2yKwwPFgotnfSoPhQiw==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^5.1.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-merge-rules": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.2.tgz", + "integrity": "sha512-zKMUlnw+zYCWoPN6yhPjtcEdlJaMUZ0WyVcxTAmw3lkkN/NDMRkOkiuctQEoWAOvH7twaxUUdvBWl0d4+hifRQ==", + "dev": true, + "dependencies": { + "browserslist": "^4.16.6", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^3.1.0", + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", + "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", + "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", + "dev": true, + "dependencies": { + "colord": "^2.9.1", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-params": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.3.tgz", + "integrity": "sha512-bkzpWcjykkqIujNL+EVEPOlLYi/eZ050oImVtHU7b4lFS82jPnsCb44gvC6pxaNt38Els3jWYDHTjHKf0koTgg==", + "dev": true, + "dependencies": { + "browserslist": "^4.16.6", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", + "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-nested": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz", + "integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.6" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", + "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", + "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", + "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", + "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-string": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", + "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", + "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.0.tgz", + "integrity": "sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ==", + "dev": true, + "dependencies": { + "browserslist": "^4.16.6", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", + "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", + "dev": true, + "dependencies": { + "normalize-url": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", + "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-ordered-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", + "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", + "dev": true, + "dependencies": { + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.0.tgz", + "integrity": "sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw==", + "dev": true, + "dependencies": { + "browserslist": "^4.16.6", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", + "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-svgo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", + "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^2.7.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", + "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true, + "optional": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/progress-webpack-plugin": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/progress-webpack-plugin/-/progress-webpack-plugin-1.0.16.tgz", + "integrity": "sha512-sdiHuuKOzELcBANHfrupYo+r99iPRyOnw15qX+rNlVUqXGfjXdH4IgxriKwG1kNJwVswKQHMdj1hYZMcb9jFaA==", + "dev": true, + "dependencies": { + "chalk": "^2.1.0", + "figures": "^2.0.0", + "log-update": "^2.3.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "peerDependencies": { + "webpack": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", + "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", + "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/regexpu-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.1.0.tgz", + "integrity": "sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.0.1", + "regjsgen": "^0.6.0", + "regjsparser": "^0.8.2", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", + "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==", + "dev": true + }, + "node_modules/regjsparser": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", + "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dev": true, + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true + }, + "node_modules/selfsigned": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.0.1.tgz", + "integrity": "sha512-LmME957M1zOsUhG+67rAjKfiWFox3SBxE/yymatMZsAx+oMrJ0YQ8AToOnyCm7xbeg2ep37IHLxdu0o2MavQOQ==", + "dev": true, + "dependencies": { + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", + "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", + "dev": true + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sirv": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz", + "integrity": "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==", + "dev": true, + "dependencies": { + "@polka/url": "^1.0.0-next.20", + "mrmime": "^1.0.0", + "totalist": "^1.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" + }, + "node_modules/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", + "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", + "dev": true + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "dev": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility", + "dev": true + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "dev": true + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha512-RsSNPLpq6YUL7QYy44RnPVTn/lcVZtb48Uof3X5JLbF4zD/Gs7ZFDv2HWol+leoQN2mT86LAzSshGfkTlSOpsA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylehacks": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.0.tgz", + "integrity": "sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q==", + "dev": true, + "dependencies": { + "browserslist": "^4.16.6", + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "dev": true + }, + "node_modules/svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dev": true, + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/table": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", + "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/tailwindcss": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.1.8.tgz", + "integrity": "sha512-YSneUCZSFDYMwk+TGq8qYFdCA3yfBRdBlS7txSq0LUmzyeqRe3a8fBQzbz9M3WS/iFT4BNf/nmw9mEzrnSaC0g==", + "dev": true, + "dependencies": { + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "color-name": "^1.1.4", + "detective": "^5.2.1", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "lilconfig": "^2.0.6", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.14", + "postcss-import": "^14.1.0", + "postcss-js": "^4.0.0", + "postcss-load-config": "^3.1.4", + "postcss-nested": "5.0.6", + "postcss-selector-parser": "^6.0.10", + "postcss-value-parser": "^4.2.0", + "quick-lru": "^5.1.1", + "resolve": "^1.22.1" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=12.13.0" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/tailwindcss/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.14.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", + "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.5.tgz", + "integrity": "sha512-AOEDLDxD2zylUGf/wxHxklEkOe2/r+seuyOWujejFrIxHf11brA1/dWQNIgXa1c6/Wkxgu7zvv0JhOWfc2ELEA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.14", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0", + "terser": "^5.14.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/thread-loader": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/thread-loader/-/thread-loader-3.0.4.tgz", + "integrity": "sha512-ByaL2TPb+m6yArpqQUZvP+5S1mZtXsEP7nWKKlAUTm7fCml8kB5s1uI3+eHRP2bk5mVYfRSBI7FFf+tWEyLZwA==", + "dev": true, + "dependencies": { + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^4.1.0", + "loader-utils": "^2.0.0", + "neo-async": "^2.6.2", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.27.0 || ^5.0.0" + } + }, + "node_modules/thread-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/thread-loader/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/totalist": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", + "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", + "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", + "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz", + "integrity": "sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vue": { + "version": "3.2.37", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.37.tgz", + "integrity": "sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ==", + "dependencies": { + "@vue/compiler-dom": "3.2.37", + "@vue/compiler-sfc": "3.2.37", + "@vue/runtime-dom": "3.2.37", + "@vue/server-renderer": "3.2.37", + "@vue/shared": "3.2.37" + } + }, + "node_modules/vue-eslint-parser": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.11.0.tgz", + "integrity": "sha512-qh3VhDLeh773wjgNTl7ss0VejY9bMMa0GoDG2fQVyDzRFdiU3L7fw74tWZDHNQXdZqxO3EveQroa9ct39D2nqg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "eslint-scope": "^5.1.1", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.2.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8.10" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "dev": true, + "dependencies": { + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/vue-hot-reload-api": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz", + "integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==", + "dev": true + }, + "node_modules/vue-loader": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-17.0.0.tgz", + "integrity": "sha512-OWSXjrzIvbF2LtOUmxT3HYgwwubbfFelN8PAP9R9dwpIkj48TVioHhWWSx7W7fk+iF5cgg3CBJRxwTdtLU4Ecg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "hash-sum": "^2.0.0", + "loader-utils": "^2.0.0" + }, + "peerDependencies": { + "webpack": "^4.1.0 || ^5.0.0-0" + } + }, + "node_modules/vue-loader/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/vue-loader/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/vue-loader/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/vue-loader/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/vue-loader/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/vue-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/vue-loader/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/vue-router": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.16.tgz", + "integrity": "sha512-JcO7cb8QJLBWE+DfxGUL3xUDOae/8nhM1KVdnudadTAORbuxIC/xAydC5Zr/VLHUDQi1ppuTF5/rjBGzgzrJNA==", + "dependencies": { + "@vue/devtools-api": "^6.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/vue-style-loader": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz", + "integrity": "sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg==", + "dev": true, + "dependencies": { + "hash-sum": "^1.0.2", + "loader-utils": "^1.0.2" + } + }, + "node_modules/vue-style-loader/node_modules/hash-sum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", + "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", + "dev": true + }, + "node_modules/vue-template-es2015-compiler": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz", + "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", + "dev": true + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/webpack": { + "version": "5.74.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", + "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.10.0", + "es-module-lexer": "^0.9.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.1.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.1.3", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-bundle-analyzer": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.5.0.tgz", + "integrity": "sha512-GUMZlM3SKwS8Z+CKeIFx7CVoHn3dXFcUAjT/dcZQQmfSZGvitPfMob2ipjai7ovFFqPvTqkEZ/leL4O0YOdAYQ==", + "dev": true, + "dependencies": { + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "chalk": "^4.1.0", + "commander": "^7.2.0", + "gzip-size": "^6.0.0", + "lodash": "^4.17.20", + "opener": "^1.5.2", + "sirv": "^1.0.7", + "ws": "^7.3.1" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/webpack-bundle-analyzer/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/webpack-chain": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/webpack-chain/-/webpack-chain-6.5.1.tgz", + "integrity": "sha512-7doO/SRtLu8q5WM0s7vPKPWX580qhi0/yBHkOxNkv50f6qB76Zy9o2wRTrrPULqYTvQlVHuvbA8v+G5ayuUDsA==", + "dev": true, + "dependencies": { + "deepmerge": "^1.5.2", + "javascript-stringify": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "dev": true, + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-middleware/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack-dev-middleware/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/webpack-dev-middleware/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-dev-server": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.10.0.tgz", + "integrity": "sha512-7dezwAs+k6yXVFZ+MaL8VnE+APobiO3zvpp3rBHe/HmWQ+avwh0Q3d0xxacOiBybZZ3syTZw9HXzpa3YNbAZDQ==", + "dev": true, + "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.1", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.0.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.4.2" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack-dev-server/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/webpack-dev-server/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", + "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webpack-merge": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.4.4.tgz", + "integrity": "sha512-h9atBP/bsZohWpHnr+2sic8Iecb60GxftXsWNLLLSqewgIsGzByd2gcIID4nXcG+3tNe4GQG3dLcff3kXupdRA==", + "dev": true + }, + "node_modules/webpack/node_modules/@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "dev": true + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-fetch": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", + "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yorkie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yorkie/-/yorkie-2.0.0.tgz", + "integrity": "sha512-jcKpkthap6x63MB4TxwCyuIGkV0oYP/YRyuQU5UO0Yz/E/ZAu+653/uov+phdmO54n6BcvFRyyt0RRrWdN2mpw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "execa": "^0.8.0", + "is-ci": "^1.0.10", + "normalize-path": "^1.0.0", + "strip-indent": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/yorkie/node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "dev": true, + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/yorkie/node_modules/execa": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz", + "integrity": "sha512-zDWS+Rb1E8BlqqhALSt9kUhss8Qq4nN3iof3gsOdyINksElaPyNBtKUMTR62qhvgVWR0CqCX7sdnKe4MnUbFEA==", + "dev": true, + "dependencies": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/yorkie/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/yorkie/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/yorkie/node_modules/normalize-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-1.0.0.tgz", + "integrity": "sha512-7WyT0w8jhpDStXRq5836AMmihQwq2nrUVQrgjvUo/p/NZf9uy/MeJ246lBJVmWuYXMlJuG9BNZHF0hWjfTbQUA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yorkie/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yorkie/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yorkie/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/yorkie/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + } + }, + "dependencies": { + "@achrinza/node-ipc": { + "version": "9.2.5", + "resolved": "https://registry.npmjs.org/@achrinza/node-ipc/-/node-ipc-9.2.5.tgz", + "integrity": "sha512-kBX7Ay911iXZ3VZ1pYltj3Rfu7Ow9H7sK4H4RSfWIfWR2JKNB40K808wppoRIEzE2j2hXLU+r6TJgCAliCGhyQ==", + "dev": true, + "requires": { + "@node-ipc/js-queue": "2.0.3", + "event-pubsub": "4.3.0", + "js-message": "1.0.7" + } + }, + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/compat-data": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.8.tgz", + "integrity": "sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ==", + "dev": true + }, + "@babel/core": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.10.tgz", + "integrity": "sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.10", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-module-transforms": "^7.18.9", + "@babel/helpers": "^7.18.9", + "@babel/parser": "^7.18.10", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.18.10", + "@babel/types": "^7.18.10", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + } + }, + "@babel/generator": { + "version": "7.18.12", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.12.tgz", + "integrity": "sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg==", + "dev": true, + "requires": { + "@babel/types": "^7.18.10", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.18.6", + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz", + "integrity": "sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.18.8", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.20.2", + "semver": "^6.3.0" + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.9.tgz", + "integrity": "sha512-WvypNAYaVh23QcjpMR24CwZY2Nz6hqdOcFdPbNpV56hL5H6KiFheO7Xm1aPdlLQ7d5emYZX7VZwPp9x3z+2opw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.9", + "@babel/helper-split-export-declaration": "^7.18.6" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.18.6.tgz", + "integrity": "sha512-7LcpH1wnQLGrI+4v+nPp+zUvIkF9x0ddv1Hkdue10tg3gmRnLy97DXh4STiOf1qeIInyD69Qv5kKSZzKD8B/7A==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.1.0" + } + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.2.tgz", + "integrity": "sha512-r9QJJ+uDWrd+94BSPcP6/de67ygLtvVy6cK4luE6MOuDsZIdoaPBnfSpbO/+LTifjPckbKXRuI9BB/Z2/y3iTg==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + } + }, + "@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "dev": true + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz", + "integrity": "sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==", + "dev": true, + "requires": { + "@babel/template": "^7.18.6", + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", + "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", + "dev": true, + "requires": { + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-module-transforms": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz", + "integrity": "sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.18.6", + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz", + "integrity": "sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==", + "dev": true + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-replace-supers": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.18.9.tgz", + "integrity": "sha512-dNsWibVI4lNT6HiuOIBr1oyxo40HvIVmbwPUm3XZ7wMh4k2WxrxTqZwSqw/eEmXDS9np0ey5M2bz9tBmO9c+YQ==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-simple-access": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", + "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz", + "integrity": "sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw==", + "dev": true, + "requires": { + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-string-parser": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", + "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", + "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "dev": true + }, + "@babel/helper-wrap-function": { + "version": "7.18.11", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.18.11.tgz", + "integrity": "sha512-oBUlbv+rjZLh2Ks9SKi4aL7eKaAXBWleHzU89mP0G6BMUlRxSckk9tSIkgDGydhgFxHuGSlBQZfnaD47oBEB7w==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.18.9", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.18.11", + "@babel/types": "^7.18.10" + } + }, + "@babel/helpers": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz", + "integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==", + "dev": true, + "requires": { + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9" + } + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.18.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.11.tgz", + "integrity": "sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ==" + }, + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", + "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-proposal-optional-chaining": "^7.18.9" + } + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.10.tgz", + "integrity": "sha512-1mFuY2TOsR1hxbjCo4QL+qlIjV07p4H4EUYw2J/WCqsvFV6V9X9z9YhXbWndc/4fw+hYGlDT7egYxliMp5O6Ew==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-proposal-class-static-block": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", + "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + } + }, + "@babel/plugin-proposal-decorators": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.18.10.tgz", + "integrity": "sha512-wdGTwWF5QtpTY/gbBtQLAiCnoxfD4qMbN87NYZle1dOZ9Os8Y6zXcKrIaOU8W+TIvFUWVGG9tUgNww3CjXRVVw==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-replace-supers": "^7.18.9", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/plugin-syntax-decorators": "^7.18.6" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + } + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", + "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz", + "integrity": "sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.18.8", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.18.8" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", + "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", + "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-decorators": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.18.6.tgz", + "integrity": "sha512-fqyLgjcxf/1yhyZ6A+yo1u9gJ7eleFQod2lkaUsF9DQ7sbbY3Ligym3L0+I2c0WmqNKDpoD9UTb1AKP3qRMOAQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-import-assertions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz", + "integrity": "sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", + "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", + "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-remap-async-to-generator": "^7.18.6" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz", + "integrity": "sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.9.tgz", + "integrity": "sha512-EkRQxsxoytpTlKJmSPYrsOMjCILacAjtSVkd4gChEe2kXjFCun3yohhW5I7plXJhCemM0gKsaGMcO8tinvCA5g==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-replace-supers": "^7.18.9", + "@babel/helper-split-export-declaration": "^7.18.6", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", + "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.9.tgz", + "integrity": "sha512-p5VCYNddPLkZTq4XymQIaIfZNJwT9YsjkPOhkVEqt6QIpQFZVM9IltqqYpOEkJoN1DPznmxUDyZ5CTZs/ZCuHA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", + "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz", + "integrity": "sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz", + "integrity": "sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-simple-access": "^7.18.6", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.9.tgz", + "integrity": "sha512-zY/VSIbbqtoRoJKo2cDTewL364jSlZGvn0LKOf9ntbfxOvjfmyrdtEEOAdswOswhZEb8UH3jDkCKHd1sPgsS0A==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-validator-identifier": "^7.18.6", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.18.6.tgz", + "integrity": "sha512-UmEOGF8XgaIqD74bC8g7iV3RYj8lMf0Bw7NJzvnS9qQhM4mg+1WHKotUIdjxgD2RGrgFLZZPCFPFj3P/kVDYhg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz", + "integrity": "sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", + "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "regenerator-transform": "^0.15.0" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-runtime": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz", + "integrity": "sha512-q5mMeYAdfEbpBAgzl7tBre/la3LeCxmDO1+wMXRdPWbcoMjR3GiXlCLk7JBZVVye0bqTGNMbt0yYVXX1B1jEWQ==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.9", + "babel-plugin-polyfill-corejs2": "^0.3.2", + "babel-plugin-polyfill-corejs3": "^0.5.3", + "babel-plugin-polyfill-regenerator": "^0.4.0", + "semver": "^6.3.0" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.18.9.tgz", + "integrity": "sha512-39Q814wyoOPtIB/qGopNIL9xDChOE1pNU0ZY5dO0owhiVt/5kFm4li+/bBtwc7QotG0u5EPzqhZdjMtmqBqyQA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", + "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/preset-env": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.10.tgz", + "integrity": "sha512-wVxs1yjFdW3Z/XkNfXKoblxoHgbtUF7/l3PvvP4m02Qz9TZ6uZGxRVYjSQeR87oQmHco9zWitW5J82DJ7sCjvA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.18.8", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-async-generator-functions": "^7.18.10", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.18.9", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.18.6", + "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.18.9", + "@babel/plugin-transform-classes": "^7.18.9", + "@babel/plugin-transform-computed-properties": "^7.18.9", + "@babel/plugin-transform-destructuring": "^7.18.9", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.18.8", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.18.6", + "@babel/plugin-transform-modules-commonjs": "^7.18.6", + "@babel/plugin-transform-modules-systemjs": "^7.18.9", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.18.6", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.18.8", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.18.9", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.18.10", + "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.18.10", + "babel-plugin-polyfill-corejs2": "^0.3.2", + "babel-plugin-polyfill-corejs3": "^0.5.3", + "babel-plugin-polyfill-regenerator": "^0.4.0", + "core-js-compat": "^3.22.1", + "semver": "^6.3.0" + } + }, + "@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/runtime": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", + "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" + } + }, + "@babel/traverse": { + "version": "7.18.11", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.11.tgz", + "integrity": "sha512-TG9PiM2R/cWCAy6BPJKeHzNbu4lPzOSZpeMfeNErskGpTJx6trEvFaVCbDvpcxwy49BKWmEPwiW8mrysNiDvIQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.10", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.18.11", + "@babel/types": "^7.18.10", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.10.tgz", + "integrity": "sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.18.10", + "@babel/helper-validator-identifier": "^7.18.6", + "to-fast-properties": "^2.0.0" + } + }, + "@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "globals": { + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, + "@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "dev": true + }, + "@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@headlessui/vue": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.7.3.tgz", + "integrity": "sha512-Is4iakKts9u9E0+jEZNzoJpBjwq2SamwEIoEl2RlyYSu6Zco536GsPXaQEfg/o7Eyc1GUUlcL+dJd4Rt7qyf7A==", + "requires": {} + }, + "@heroicons/vue": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@heroicons/vue/-/vue-2.0.12.tgz", + "integrity": "sha512-ypuQl/Wei7BoShO65AdRVikXDaSt6hJBRuKs2pVd/6HvxfEz18oXraH+J5G/QPIwObMEVhMb6nkOs9l3WZv/gA==", + "requires": {} + }, + "@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", + "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "dev": true + }, + "@node-ipc/js-queue": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@node-ipc/js-queue/-/js-queue-2.0.3.tgz", + "integrity": "sha512-fL1wpr8hhD5gT2dA1qifeVaoDFlQR5es8tFuKqjHX+kdOtdNHnxkVZbtIrR2rxnMFvehkjaZRNV2H/gPXlb0hw==", + "dev": true, + "requires": { + "easy-stack": "1.0.1" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@polka/url": { + "version": "1.0.0-next.21", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", + "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", + "dev": true + }, + "@sideway/address": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", + "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@sideway/formula": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", + "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==", + "dev": true + }, + "@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "dev": true + }, + "@soda/friendly-errors-webpack-plugin": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.1.tgz", + "integrity": "sha512-h2ooWqP8XuFqTXT+NyAFbrArzfQA7R6HTezADrvD9Re8fxMLTPPniLdqVTdDaO0eIoLaAwKT+d6w+5GeTk7Vbg==", + "dev": true, + "requires": { + "chalk": "^3.0.0", + "error-stack-parser": "^2.0.6", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@soda/get-current-script": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@soda/get-current-script/-/get-current-script-1.0.2.tgz", + "integrity": "sha512-T7VNNlYVM1SgQ+VsMYhnDkcGmWhQdL0bDyGm5TlQ3GBXnJscEClUUOKduWTmm2zCnvNLC1hc3JpuXjs/nFOc5w==", + "dev": true + }, + "@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true + }, + "@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/bonjour": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", + "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/connect-history-api-fallback": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", + "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", + "dev": true, + "requires": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "@types/eslint": { + "version": "8.4.5", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz", + "integrity": "sha512-dhsC09y1gpJWnK+Ff4SGvCuSnk9DaU0BJZSzOwa6GVSg65XtTugLBITDAAzRU5duGBoXBHpdR/9jHGxJjNflJQ==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "requires": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "@types/estree": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", + "dev": true + }, + "@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.30", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.30.tgz", + "integrity": "sha512-gstzbTWro2/nFed1WXtf+TtrpwxH7Ggs4RLYTLbeVgIkUQOI3WG/JKjgeOU1zXDvezllupjrf8OPIdvTbIaVOQ==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "dev": true + }, + "@types/http-proxy": { + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", + "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "@types/mime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", + "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", + "dev": true + }, + "@types/minimist": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "dev": true + }, + "@types/node": { + "version": "18.7.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.6.tgz", + "integrity": "sha512-EdxgKRXgYsNITy5mjjXjVE/CS8YENSdhiagGrLqjG0pvA2owgJ6i4l7wy/PFZGC0B1/H20lWKN7ONVDNYDZm7A==", + "dev": true + }, + "@types/normalize-package-data": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "dev": true + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, + "@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", + "dev": true, + "requires": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/ws": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", + "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@vue/babel-helper-vue-jsx-merge-props": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.2.1.tgz", + "integrity": "sha512-QOi5OW45e2R20VygMSNhyQHvpdUwQZqGPc748JLGCYEy+yp8fNFNdbNIGAgZmi9e+2JHPd6i6idRuqivyicIkA==", + "dev": true + }, + "@vue/babel-helper-vue-transform-on": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.0.2.tgz", + "integrity": "sha512-hz4R8tS5jMn8lDq6iD+yWL6XNB699pGIVLk7WSJnn1dbpjaazsjZQkieJoRX6gW5zpYSCFqQ7jUquPNY65tQYA==", + "dev": true + }, + "@vue/babel-plugin-jsx": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.1.1.tgz", + "integrity": "sha512-j2uVfZjnB5+zkcbc/zsOc0fSNGCMMjaEXP52wdwdIfn0qjFfEYpYZBFKFg+HHnQeJCVrjOeO0YxgaL7DMrym9w==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/plugin-syntax-jsx": "^7.0.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "@vue/babel-helper-vue-transform-on": "^1.0.2", + "camelcase": "^6.0.0", + "html-tags": "^3.1.0", + "svg-tags": "^1.0.0" + } + }, + "@vue/babel-plugin-transform-vue-jsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-1.2.1.tgz", + "integrity": "sha512-HJuqwACYehQwh1fNT8f4kyzqlNMpBuUK4rSiSES5D4QsYncv5fxFsLyrxFPG2ksO7t5WP+Vgix6tt6yKClwPzA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/plugin-syntax-jsx": "^7.2.0", + "@vue/babel-helper-vue-jsx-merge-props": "^1.2.1", + "html-tags": "^2.0.0", + "lodash.kebabcase": "^4.1.1", + "svg-tags": "^1.0.0" + }, + "dependencies": { + "html-tags": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-2.0.0.tgz", + "integrity": "sha512-+Il6N8cCo2wB/Vd3gqy/8TZhTD3QvcVeQLCnZiGkGCH3JP28IgGAY41giccp2W4R3jfyJPAP318FQTa1yU7K7g==", + "dev": true + } + } + }, + "@vue/babel-preset-app": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@vue/babel-preset-app/-/babel-preset-app-5.0.8.tgz", + "integrity": "sha512-yl+5qhpjd8e1G4cMXfORkkBlvtPCIgmRf3IYCWYDKIQ7m+PPa5iTm4feiNmCMD6yGqQWMhhK/7M3oWGL9boKwg==", + "dev": true, + "requires": { + "@babel/core": "^7.12.16", + "@babel/helper-compilation-targets": "^7.12.16", + "@babel/helper-module-imports": "^7.12.13", + "@babel/plugin-proposal-class-properties": "^7.12.13", + "@babel/plugin-proposal-decorators": "^7.12.13", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-jsx": "^7.12.13", + "@babel/plugin-transform-runtime": "^7.12.15", + "@babel/preset-env": "^7.12.16", + "@babel/runtime": "^7.12.13", + "@vue/babel-plugin-jsx": "^1.0.3", + "@vue/babel-preset-jsx": "^1.1.2", + "babel-plugin-dynamic-import-node": "^2.3.3", + "core-js": "^3.8.3", + "core-js-compat": "^3.8.3", + "semver": "^7.3.4" + }, + "dependencies": { + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@vue/babel-preset-jsx": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@vue/babel-preset-jsx/-/babel-preset-jsx-1.3.1.tgz", + "integrity": "sha512-ml+nqcSKp8uAqFZLNc7OWLMzR7xDBsUfkomF98DtiIBlLqlq4jCQoLINARhgqRIyKdB+mk/94NWpIb4pL6D3xw==", + "dev": true, + "requires": { + "@vue/babel-helper-vue-jsx-merge-props": "^1.2.1", + "@vue/babel-plugin-transform-vue-jsx": "^1.2.1", + "@vue/babel-sugar-composition-api-inject-h": "^1.3.0", + "@vue/babel-sugar-composition-api-render-instance": "^1.3.0", + "@vue/babel-sugar-functional-vue": "^1.2.2", + "@vue/babel-sugar-inject-h": "^1.2.2", + "@vue/babel-sugar-v-model": "^1.3.0", + "@vue/babel-sugar-v-on": "^1.3.0" + } + }, + "@vue/babel-sugar-composition-api-inject-h": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vue/babel-sugar-composition-api-inject-h/-/babel-sugar-composition-api-inject-h-1.3.0.tgz", + "integrity": "sha512-pIDOutEpqbURdVw7xhgxmuDW8Tl+lTgzJZC5jdlUu0lY2+izT9kz3Umd/Tbu0U5cpCJ2Yhu87BZFBzWpS0Xemg==", + "dev": true, + "requires": { + "@babel/plugin-syntax-jsx": "^7.2.0" + } + }, + "@vue/babel-sugar-composition-api-render-instance": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vue/babel-sugar-composition-api-render-instance/-/babel-sugar-composition-api-render-instance-1.3.0.tgz", + "integrity": "sha512-NYNnU2r7wkJLMV5p9Zj4pswmCs037O/N2+/Fs6SyX7aRFzXJRP1/2CZh5cIwQxWQajHXuCUd5mTb7DxoBVWyTg==", + "dev": true, + "requires": { + "@babel/plugin-syntax-jsx": "^7.2.0" + } + }, + "@vue/babel-sugar-functional-vue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vue/babel-sugar-functional-vue/-/babel-sugar-functional-vue-1.2.2.tgz", + "integrity": "sha512-JvbgGn1bjCLByIAU1VOoepHQ1vFsroSA/QkzdiSs657V79q6OwEWLCQtQnEXD/rLTA8rRit4rMOhFpbjRFm82w==", + "dev": true, + "requires": { + "@babel/plugin-syntax-jsx": "^7.2.0" + } + }, + "@vue/babel-sugar-inject-h": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vue/babel-sugar-inject-h/-/babel-sugar-inject-h-1.2.2.tgz", + "integrity": "sha512-y8vTo00oRkzQTgufeotjCLPAvlhnpSkcHFEp60+LJUwygGcd5Chrpn5480AQp/thrxVm8m2ifAk0LyFel9oCnw==", + "dev": true, + "requires": { + "@babel/plugin-syntax-jsx": "^7.2.0" + } + }, + "@vue/babel-sugar-v-model": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vue/babel-sugar-v-model/-/babel-sugar-v-model-1.3.0.tgz", + "integrity": "sha512-zcsabmdX48JmxTObn3xmrvvdbEy8oo63DphVyA3WRYGp4SEvJRpu/IvZCVPl/dXLuob2xO/QRuncqPgHvZPzpA==", + "dev": true, + "requires": { + "@babel/plugin-syntax-jsx": "^7.2.0", + "@vue/babel-helper-vue-jsx-merge-props": "^1.2.1", + "@vue/babel-plugin-transform-vue-jsx": "^1.2.1", + "camelcase": "^5.0.0", + "html-tags": "^2.0.0", + "svg-tags": "^1.0.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "html-tags": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-2.0.0.tgz", + "integrity": "sha512-+Il6N8cCo2wB/Vd3gqy/8TZhTD3QvcVeQLCnZiGkGCH3JP28IgGAY41giccp2W4R3jfyJPAP318FQTa1yU7K7g==", + "dev": true + } + } + }, + "@vue/babel-sugar-v-on": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vue/babel-sugar-v-on/-/babel-sugar-v-on-1.3.0.tgz", + "integrity": "sha512-8VZgrS0G5bh7+Prj7oJkzg9GvhSPnuW5YT6MNaVAEy4uwxRLJ8GqHenaStfllChTao4XZ3EZkNtHB4Xbr/ePdA==", + "dev": true, + "requires": { + "@babel/plugin-syntax-jsx": "^7.2.0", + "@vue/babel-plugin-transform-vue-jsx": "^1.2.1", + "camelcase": "^5.0.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + } + } + }, + "@vue/cli-overlay": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@vue/cli-overlay/-/cli-overlay-5.0.8.tgz", + "integrity": "sha512-KmtievE/B4kcXp6SuM2gzsnSd8WebkQpg3XaB6GmFh1BJGRqa1UiW9up7L/Q67uOdTigHxr5Ar2lZms4RcDjwQ==", + "dev": true + }, + "@vue/cli-plugin-babel": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@vue/cli-plugin-babel/-/cli-plugin-babel-5.0.4.tgz", + "integrity": "sha512-413ZwOWLtgw5vWJoMbrv36crW3qTas4Iru8sU7cRb0IqEZbS28R9X4PVtO8Pek2NYFbrs2XKRYOB7GblB6hVqg==", + "dev": true, + "requires": { + "@babel/core": "^7.12.16", + "@vue/babel-preset-app": "^5.0.4", + "@vue/cli-shared-utils": "^5.0.4", + "babel-loader": "^8.2.2", + "thread-loader": "^3.0.0", + "webpack": "^5.54.0" + } + }, + "@vue/cli-plugin-eslint": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@vue/cli-plugin-eslint/-/cli-plugin-eslint-5.0.4.tgz", + "integrity": "sha512-NLBEKFTFJhYHMzQ3z+sb6xlkcNaN/RcbZ3hiCY72aJe4YOO8jEAp0XkPzlHd4xYkMW7jrmOwwLOPw+3BJ4b77Q==", + "dev": true, + "requires": { + "@vue/cli-shared-utils": "^5.0.4", + "eslint-webpack-plugin": "^3.1.0", + "globby": "^11.0.2", + "webpack": "^5.54.0", + "yorkie": "^2.0.0" + } + }, + "@vue/cli-plugin-router": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@vue/cli-plugin-router/-/cli-plugin-router-5.0.4.tgz", + "integrity": "sha512-lylzCuH3Br0BcTz5IxxSffpyoF9dQ2k4jTdK8QlWrnRanWGw7P9C0kYMr9rohHaXpvAlu6bio392gbNIWpEepg==", + "dev": true, + "requires": { + "@vue/cli-shared-utils": "^5.0.4" + } + }, + "@vue/cli-plugin-vuex": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@vue/cli-plugin-vuex/-/cli-plugin-vuex-5.0.8.tgz", + "integrity": "sha512-HSYWPqrunRE5ZZs8kVwiY6oWcn95qf/OQabwLfprhdpFWAGtLStShjsGED2aDpSSeGAskQETrtR/5h7VqgIlBA==", + "dev": true, + "requires": {} + }, + "@vue/cli-service": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@vue/cli-service/-/cli-service-5.0.4.tgz", + "integrity": "sha512-xRiLNTFYmMCT9edZpyYXHijW5xot3gbZpcWDOXUOhKPHN4qs4XqWALnZlU97JYjZOr3XIr/ZvyciyEfrlUVqSA==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.12.16", + "@soda/friendly-errors-webpack-plugin": "^1.8.0", + "@soda/get-current-script": "^1.0.2", + "@types/minimist": "^1.2.0", + "@vue/cli-overlay": "^5.0.4", + "@vue/cli-plugin-router": "^5.0.4", + "@vue/cli-plugin-vuex": "^5.0.4", + "@vue/cli-shared-utils": "^5.0.4", + "@vue/component-compiler-utils": "^3.3.0", + "@vue/vue-loader-v15": "npm:vue-loader@^15.9.7", + "@vue/web-component-wrapper": "^1.3.0", + "acorn": "^8.0.5", + "acorn-walk": "^8.0.2", + "address": "^1.1.2", + "autoprefixer": "^10.2.4", + "browserslist": "^4.16.3", + "case-sensitive-paths-webpack-plugin": "^2.3.0", + "cli-highlight": "^2.1.10", + "clipboardy": "^2.3.0", + "cliui": "^7.0.4", + "copy-webpack-plugin": "^9.0.1", + "css-loader": "^6.5.0", + "css-minimizer-webpack-plugin": "^3.0.2", + "cssnano": "^5.0.0", + "debug": "^4.1.1", + "default-gateway": "^6.0.3", + "dotenv": "^10.0.0", + "dotenv-expand": "^5.1.0", + "fs-extra": "^9.1.0", + "globby": "^11.0.2", + "hash-sum": "^2.0.0", + "html-webpack-plugin": "^5.1.0", + "is-file-esm": "^1.0.0", + "launch-editor-middleware": "^2.2.1", + "lodash.defaultsdeep": "^4.6.1", + "lodash.mapvalues": "^4.6.0", + "mini-css-extract-plugin": "^2.5.3", + "minimist": "^1.2.5", + "module-alias": "^2.2.2", + "portfinder": "^1.0.26", + "postcss": "^8.2.6", + "postcss-loader": "^6.1.1", + "progress-webpack-plugin": "^1.0.12", + "ssri": "^8.0.1", + "terser-webpack-plugin": "^5.1.1", + "thread-loader": "^3.0.0", + "vue-loader": "^17.0.0", + "vue-style-loader": "^4.1.3", + "webpack": "^5.54.0", + "webpack-bundle-analyzer": "^4.4.0", + "webpack-chain": "^6.5.1", + "webpack-dev-server": "^4.7.3", + "webpack-merge": "^5.7.3", + "webpack-virtual-modules": "^0.4.2", + "whatwg-fetch": "^3.6.2" + } + }, + "@vue/cli-shared-utils": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@vue/cli-shared-utils/-/cli-shared-utils-5.0.8.tgz", + "integrity": "sha512-uK2YB7bBVuQhjOJF+O52P9yFMXeJVj7ozqJkwYE9PlMHL1LMHjtCYm4cSdOebuPzyP+/9p0BimM/OqxsevIopQ==", + "dev": true, + "requires": { + "@achrinza/node-ipc": "^9.2.5", + "chalk": "^4.1.2", + "execa": "^1.0.0", + "joi": "^17.4.0", + "launch-editor": "^2.2.1", + "lru-cache": "^6.0.0", + "node-fetch": "^2.6.7", + "open": "^8.0.2", + "ora": "^5.3.0", + "read-pkg": "^5.1.1", + "semver": "^7.3.4", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@vue/compiler-core": { + "version": "3.2.37", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.37.tgz", + "integrity": "sha512-81KhEjo7YAOh0vQJoSmAD68wLfYqJvoiD4ulyedzF+OEk/bk6/hx3fTNVfuzugIIaTrOx4PGx6pAiBRe5e9Zmg==", + "requires": { + "@babel/parser": "^7.16.4", + "@vue/shared": "3.2.37", + "estree-walker": "^2.0.2", + "source-map": "^0.6.1" + } + }, + "@vue/compiler-dom": { + "version": "3.2.37", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.37.tgz", + "integrity": "sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ==", + "requires": { + "@vue/compiler-core": "3.2.37", + "@vue/shared": "3.2.37" + } + }, + "@vue/compiler-sfc": { + "version": "3.2.37", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.37.tgz", + "integrity": "sha512-+7i/2+9LYlpqDv+KTtWhOZH+pa8/HnX/905MdVmAcI/mPQOBwkHHIzrsEsucyOIZQYMkXUiTkmZq5am/NyXKkg==", + "requires": { + "@babel/parser": "^7.16.4", + "@vue/compiler-core": "3.2.37", + "@vue/compiler-dom": "3.2.37", + "@vue/compiler-ssr": "3.2.37", + "@vue/reactivity-transform": "3.2.37", + "@vue/shared": "3.2.37", + "estree-walker": "^2.0.2", + "magic-string": "^0.25.7", + "postcss": "^8.1.10", + "source-map": "^0.6.1" + } + }, + "@vue/compiler-ssr": { + "version": "3.2.37", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.37.tgz", + "integrity": "sha512-7mQJD7HdXxQjktmsWp/J67lThEIcxLemz1Vb5I6rYJHR5vI+lON3nPGOH3ubmbvYGt8xEUaAr1j7/tIFWiEOqw==", + "requires": { + "@vue/compiler-dom": "3.2.37", + "@vue/shared": "3.2.37" + } + }, + "@vue/component-compiler-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.3.0.tgz", + "integrity": "sha512-97sfH2mYNU+2PzGrmK2haqffDpVASuib9/w2/noxiFi31Z54hW+q3izKQXXQZSNhtiUpAI36uSuYepeBe4wpHQ==", + "dev": true, + "requires": { + "consolidate": "^0.15.1", + "hash-sum": "^1.0.2", + "lru-cache": "^4.1.2", + "merge-source-map": "^1.1.0", + "postcss": "^7.0.36", + "postcss-selector-parser": "^6.0.2", + "prettier": "^1.18.2 || ^2.0.0", + "source-map": "~0.6.1", + "vue-template-es2015-compiler": "^1.9.0" + }, + "dependencies": { + "hash-sum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", + "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", + "dev": true + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "requires": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + } + } + }, + "@vue/devtools-api": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.2.1.tgz", + "integrity": "sha512-OEgAMeQXvCoJ+1x8WyQuVZzFo0wcyCmUR3baRVLmKBo1LmYZWMlRiXlux5jd0fqVJu6PfDbOrZItVqUEzLobeQ==" + }, + "@vue/reactivity": { + "version": "3.2.37", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.37.tgz", + "integrity": "sha512-/7WRafBOshOc6m3F7plwzPeCu/RCVv9uMpOwa/5PiY1Zz+WLVRWiy0MYKwmg19KBdGtFWsmZ4cD+LOdVPcs52A==", + "requires": { + "@vue/shared": "3.2.37" + } + }, + "@vue/reactivity-transform": { + "version": "3.2.37", + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.37.tgz", + "integrity": "sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg==", + "requires": { + "@babel/parser": "^7.16.4", + "@vue/compiler-core": "3.2.37", + "@vue/shared": "3.2.37", + "estree-walker": "^2.0.2", + "magic-string": "^0.25.7" + } + }, + "@vue/runtime-core": { + "version": "3.2.37", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.37.tgz", + "integrity": "sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ==", + "requires": { + "@vue/reactivity": "3.2.37", + "@vue/shared": "3.2.37" + } + }, + "@vue/runtime-dom": { + "version": "3.2.37", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.37.tgz", + "integrity": "sha512-HimKdh9BepShW6YozwRKAYjYQWg9mQn63RGEiSswMbW+ssIht1MILYlVGkAGGQbkhSh31PCdoUcfiu4apXJoPw==", + "requires": { + "@vue/runtime-core": "3.2.37", + "@vue/shared": "3.2.37", + "csstype": "^2.6.8" + } + }, + "@vue/server-renderer": { + "version": "3.2.37", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.37.tgz", + "integrity": "sha512-kLITEJvaYgZQ2h47hIzPh2K3jG8c1zCVbp/o/bzQOyvzaKiCquKS7AaioPI28GNxIsE/zSx+EwWYsNxDCX95MA==", + "requires": { + "@vue/compiler-ssr": "3.2.37", + "@vue/shared": "3.2.37" + } + }, + "@vue/shared": { + "version": "3.2.37", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.37.tgz", + "integrity": "sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw==" + }, + "@vue/vue-loader-v15": { + "version": "npm:vue-loader@15.10.0", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.10.0.tgz", + "integrity": "sha512-VU6tuO8eKajrFeBzMssFUP9SvakEeeSi1BxdTH5o3+1yUyrldp8IERkSdXlMI2t4kxF2sqYUDsQY+WJBxzBmZg==", + "dev": true, + "requires": { + "@vue/component-compiler-utils": "^3.1.0", + "hash-sum": "^1.0.2", + "loader-utils": "^1.1.0", + "vue-hot-reload-api": "^2.3.0", + "vue-style-loader": "^4.1.0" + }, + "dependencies": { + "hash-sum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", + "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", + "dev": true + } + } + }, + "@vue/web-component-wrapper": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vue/web-component-wrapper/-/web-component-wrapper-1.3.0.tgz", + "integrity": "sha512-Iu8Tbg3f+emIIMmI2ycSI8QcEuAUgPTgHwesDU1eKMLE4YC/c/sFbGc70QgMq31ijRftV0R7vCm9co6rldCeOA==", + "dev": true + }, + "@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dev": true, + "requires": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "dev": true + }, + "@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dev": true, + "requires": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true + }, + "acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true, + "requires": {} + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "dev": true, + "requires": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + } + } + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "address": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.0.tgz", + "integrity": "sha512-tNEZYz5G/zYunxFm7sfhAxkXEuLj3K6BKwv6ZURlsF6yiUQ65z0Q2wZW9L5cPUl9ocofGvXOdFYbFHp0+6MOig==", + "dev": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "requires": { + "ajv": "^8.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true + }, + "arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true + }, + "autoprefixer": { + "version": "10.4.7", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.7.tgz", + "integrity": "sha512-ypHju4Y2Oav95SipEcCcI5J7CGPuvz8oat7sUtYj3ClK44bldfvtvcxK6IEK++7rqB7YchDGzweZIBG+SD0ZAA==", + "dev": true, + "requires": { + "browserslist": "^4.20.3", + "caniuse-lite": "^1.0.30001335", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + } + }, + "babel-eslint": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + } + }, + "babel-loader": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz", + "integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==", + "dev": true, + "requires": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "dependencies": { + "loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + } + } + }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dev": true, + "requires": { + "object.assign": "^4.1.0" + } + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.2.tgz", + "integrity": "sha512-LPnodUl3lS0/4wN3Rb+m+UK8s7lj2jcLRrjho4gLw+OJs+I4bvGXshINesY5xx/apM+biTnQ9reDI8yj+0M5+Q==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.2", + "semver": "^6.1.1" + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz", + "integrity": "sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.2", + "core-js-compat": "^3.21.0" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.0.tgz", + "integrity": "sha512-RW1cnryiADFeHmfLS+WW/G431p1PsW5qdRdz0SDRi7TKcUgc7Oh/uXkT7MZ/+tGsT1BkczEAmD5XjUyJ5SWDTw==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.2" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "body-parser": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "bonjour-service": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.0.13.tgz", + "integrity": "sha512-LWKRU/7EqDUC9CTAQtuZl5HzBALoCYwtLhffW3et7vZMwv3bWLpJf8bRYlMD5OCcDpTfnPgNCV4yo9ZIaJGMiA==", + "dev": true, + "requires": { + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", + "integrity": "sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001370", + "electron-to-chromium": "^1.4.202", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.5" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "requires": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true + }, + "caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "caniuse-lite": { + "version": "1.0.30001378", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001378.tgz", + "integrity": "sha512-JVQnfoO7FK7WvU4ZkBRbPjaot4+YqxogSDosHv0Hv5mWpUESmN+UubMU6L/hGz8QlQ2aY5U0vR6MOs6j/CXpNA==", + "dev": true + }, + "case-sensitive-paths-webpack-plugin": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", + "integrity": "sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true + }, + "ci-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", + "dev": true + }, + "clean-css": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz", + "integrity": "sha512-lCr8OHhiWCTw4v8POJovCoh4T7I9U11yVsPjMWWnnMmp9ZowCxyad1Pathle/9HjaDp+fdQKjO9fQydE6RHTZg==", + "dev": true, + "requires": { + "source-map": "~0.6.0" + } + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "cli-spinners": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", + "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", + "dev": true + }, + "clipboardy": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz", + "integrity": "sha512-mKhiIL2DrQIsuXMgBgnfEHOZOryC7kY7YO//TN6c63wlEm3NG5tz+YgY5rVi29KCmq/QQjKYvM7a19+MDOTHOQ==", + "dev": true, + "requires": { + "arch": "^2.1.1", + "execa": "^1.0.0", + "is-wsl": "^2.1.1" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true + }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true + }, + "colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "dev": true + }, + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true + }, + "consolidate": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz", + "integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==", + "dev": true, + "requires": { + "bluebird": "^3.1.1" + } + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "requires": { + "safe-buffer": "5.2.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "copy-webpack-plugin": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-9.1.0.tgz", + "integrity": "sha512-rxnR7PaGigJzhqETHGmAcxKnLZSR5u1Y3/bcIv/1FnqXedcL/E2ewK7ZCNrArJKCiSv8yVXhTqetJh8inDvfsA==", + "dev": true, + "requires": { + "fast-glob": "^3.2.7", + "glob-parent": "^6.0.1", + "globby": "^11.0.3", + "normalize-path": "^3.0.0", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "core-js": { + "version": "3.22.8", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.22.8.tgz", + "integrity": "sha512-UoGQ/cfzGYIuiq6Z7vWL1HfkE9U9IZ4Ub+0XSiJTCzvbZzgPA69oDF2f+lgJ6dFFLEdjW5O6svvoKzXX23xFkA==" + }, + "core-js-compat": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.24.1.tgz", + "integrity": "sha512-XhdNAGeRnTpp8xbD+sR/HFDK9CbeeeqXT6TuofXh3urqEevzkWmLRgrVoykodsw8okqo2pu1BOmuCKrHx63zdw==", + "dev": true, + "requires": { + "browserslist": "^4.21.3", + "semver": "7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } + } + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "cosmiconfig": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", + "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "css-declaration-sorter": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.0.tgz", + "integrity": "sha512-OGT677UGHJTAVMRhPO+HJ4oKln3wkBTwtDFH0ojbqm+MJm6xuDMHp2nkhh/ThaBqq20IbraBQSWKfSLNHQO9Og==", + "dev": true, + "requires": {} + }, + "css-loader": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", + "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", + "dev": true, + "requires": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.7", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.5" + }, + "dependencies": { + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "css-minimizer-webpack-plugin": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz", + "integrity": "sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==", + "dev": true, + "requires": { + "cssnano": "^5.0.6", + "jest-worker": "^27.0.2", + "postcss": "^8.3.5", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0", + "source-map": "^0.6.1" + }, + "dependencies": { + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + } + } + } + }, + "css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + } + }, + "css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, + "requires": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + } + }, + "css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, + "cssnano": { + "version": "5.1.13", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.13.tgz", + "integrity": "sha512-S2SL2ekdEz6w6a2epXn4CmMKU4K3KpcyXLKfAYc9UQQqJRkD/2eLUG0vJ3Db/9OvO5GuAdgXw3pFbR6abqghDQ==", + "dev": true, + "requires": { + "cssnano-preset-default": "^5.2.12", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + } + }, + "cssnano-preset-default": { + "version": "5.2.12", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.12.tgz", + "integrity": "sha512-OyCBTZi+PXgylz9HAA5kHyoYhfGcYdwFmyaJzWnzxuGRtnMw/kR6ilW9XzlzlRAtB6PLT/r+prYgkef7hngFew==", + "dev": true, + "requires": { + "css-declaration-sorter": "^6.3.0", + "cssnano-utils": "^3.1.0", + "postcss-calc": "^8.2.3", + "postcss-colormin": "^5.3.0", + "postcss-convert-values": "^5.1.2", + "postcss-discard-comments": "^5.1.2", + "postcss-discard-duplicates": "^5.1.0", + "postcss-discard-empty": "^5.1.1", + "postcss-discard-overridden": "^5.1.0", + "postcss-merge-longhand": "^5.1.6", + "postcss-merge-rules": "^5.1.2", + "postcss-minify-font-values": "^5.1.0", + "postcss-minify-gradients": "^5.1.1", + "postcss-minify-params": "^5.1.3", + "postcss-minify-selectors": "^5.2.1", + "postcss-normalize-charset": "^5.1.0", + "postcss-normalize-display-values": "^5.1.0", + "postcss-normalize-positions": "^5.1.1", + "postcss-normalize-repeat-style": "^5.1.1", + "postcss-normalize-string": "^5.1.0", + "postcss-normalize-timing-functions": "^5.1.0", + "postcss-normalize-unicode": "^5.1.0", + "postcss-normalize-url": "^5.1.0", + "postcss-normalize-whitespace": "^5.1.1", + "postcss-ordered-values": "^5.1.3", + "postcss-reduce-initial": "^5.1.0", + "postcss-reduce-transforms": "^5.1.0", + "postcss-svgo": "^5.1.0", + "postcss-unique-selectors": "^5.1.1" + } + }, + "cssnano-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", + "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", + "dev": true, + "requires": {} + }, + "csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, + "requires": { + "css-tree": "^1.1.2" + } + }, + "csstype": { + "version": "2.6.20", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz", + "integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==" + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "deepmerge": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-1.5.2.tgz", + "integrity": "sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==", + "dev": true + }, + "default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "requires": { + "execa": "^5.0.0" + }, + "dependencies": { + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + } + } + }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==", + "dev": true, + "requires": { + "clone": "^1.0.2" + } + }, + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true + }, + "define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==", + "dev": true + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true + }, + "detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true + }, + "detective": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz", + "integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==", + "dev": true, + "requires": { + "acorn-node": "^1.8.2", + "defined": "^1.0.0", + "minimist": "^1.2.6" + } + }, + "didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", + "dev": true + }, + "dns-packet": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz", + "integrity": "sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==", + "dev": true, + "requires": { + "@leichtgewicht/ip-codec": "^2.0.1" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "requires": { + "utila": "~0.4" + } + }, + "dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true + }, + "domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "requires": { + "domelementtype": "^2.2.0" + } + }, + "domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "requires": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + } + }, + "dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "dev": true + }, + "dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "dev": true + }, + "duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "easy-stack": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/easy-stack/-/easy-stack-1.0.1.tgz", + "integrity": "sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==", + "dev": true + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.4.224", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.224.tgz", + "integrity": "sha512-dOujC5Yzj0nOVE23iD5HKqrRSDj2SD7RazpZS/b/WX85MtO6/LzKDF4TlYZTBteB+7fvSg5JpWh0sN7fImNF8w==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "enhanced-resolve": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", + "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "dev": true, + "requires": { + "stackframe": "^1.3.4" + } + }, + "es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "dev": true, + "requires": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, + "eslint-plugin-vue": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.20.0.tgz", + "integrity": "sha512-oVNDqzBC9h3GO+NTgWeLMhhGigy6/bQaQbHS+0z7C4YEu/qK/yxHvca/2PTZtGNPsCrHwOTgKMrwu02A9iPBmw==", + "dev": true, + "requires": { + "eslint-utils": "^2.1.0", + "natural-compare": "^1.4.0", + "semver": "^6.3.0", + "vue-eslint-parser": "^7.10.0" + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "eslint-webpack-plugin": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-3.2.0.tgz", + "integrity": "sha512-avrKcGncpPbPSUHX6B3stNGzkKFto3eL+DKM4+VyMrVnhPc3vRczVlCq3uhuFOdRvDHTVXuzwk1ZKUrqDQHQ9w==", + "dev": true, + "requires": { + "@types/eslint": "^7.29.0 || ^8.4.1", + "jest-worker": "^28.0.2", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-worker": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", + "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + } + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true + }, + "event-pubsub": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/event-pubsub/-/event-pubsub-4.3.0.tgz", + "integrity": "sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ==", + "dev": true + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "express": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", + "dev": true, + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.0", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.10.3", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", + "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", + "dev": true + }, + "follow-redirects": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", + "dev": true + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true + }, + "fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "dev": true + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fs-monkey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", + "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", + "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "dependencies": { + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + } + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "requires": { + "duplexer": "^0.1.2" + } + }, + "handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "hash-sum": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz", + "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true + }, + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", + "dev": true + }, + "html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dev": true, + "requires": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + } + }, + "html-tags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz", + "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==", + "dev": true + }, + "html-webpack-plugin": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", + "integrity": "sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==", + "dev": true, + "requires": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + } + }, + "htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dev": true, + "requires": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + } + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "requires": {} + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ipaddr.js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", + "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-ci": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", + "dev": true, + "requires": { + "ci-info": "^1.5.0" + } + }, + "is-core-module": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-file-esm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-file-esm/-/is-file-esm-1.0.0.tgz", + "integrity": "sha512-rZlaNKb4Mr8WlRu2A9XdeoKgnO5aA53XdPHgCKVyCrQ/rWi89RET1+bq37Ru46obaQXeiX4vmFIm1vks41hoSA==", + "dev": true, + "requires": { + "read-pkg-up": "^7.0.1" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true + }, + "javascript-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz", + "integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==", + "dev": true + }, + "jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "joi": { + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.6.0.tgz", + "integrity": "sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.0", + "@sideway/pinpoint": "^2.0.0" + } + }, + "js-message": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.7.tgz", + "integrity": "sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "klona": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz", + "integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==", + "dev": true + }, + "launch-editor": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.5.0.tgz", + "integrity": "sha512-coRiIMBJ3JF7yX8nZE4Fr+xxUy+3WTRsDSwIzHghU28gjXwkAWsvac3BpZrL/jHtbiqQ4TiRAyTJmsgErNk1jQ==", + "dev": true, + "requires": { + "picocolors": "^1.0.0", + "shell-quote": "^1.7.3" + } + }, + "launch-editor-middleware": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/launch-editor-middleware/-/launch-editor-middleware-2.5.0.tgz", + "integrity": "sha512-kv9MMO81pbYjznk9j/DBu0uBGxIpT6uYhGajq6fxdGEPb+DCRBoS96jGkhe3MJumdY3zZFkuS8CFPTZI9DaBNw==", + "dev": true, + "requires": { + "launch-editor": "^2.5.0" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lilconfig": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", + "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", + "dev": true + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true + }, + "loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + } + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "lodash.defaultsdeep": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz", + "integrity": "sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==", + "dev": true + }, + "lodash.kebabcase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", + "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", + "dev": true + }, + "lodash.mapvalues": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz", + "integrity": "sha512-JPFqXFeZQ7BfS00H58kClY7SPVeHertPE0lNuCyZ26/XlN8TvakYD7b9bGyNmXbT/D3BbtPAAmq90gPWqLkxlQ==", + "dev": true + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "log-update": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz", + "integrity": "sha512-vlP11XfFGyeNQlmEn9tJ66rEW1coA/79m5z6BCkudjbAGE83uhAcGYrBFwfs3AdLiLzGRusRPAbSPK9xZteCmg==", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "cli-cursor": "^2.0.0", + "wrap-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "wrap-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", + "integrity": "sha512-iXR3tDXpbnTpzjKSylUJRkLuOrEC7hwEB221cgn6wtF8wpmz28puFXAEfPT5zrjM3wahygB//VuWEr1vTkDcNQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0" + } + } + } + }, + "lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "requires": { + "tslib": "^2.0.3" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "requires": { + "sourcemap-codec": "^1.4.8" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true + }, + "memfs": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.7.tgz", + "integrity": "sha512-ygaiUSNalBX85388uskeCyhSAoOSgzBbtVCr9jA2RROssFL9Q19/ZXFqS+2Th2sr1ewNIWgFdLzLC3Yl1Zv+lw==", + "dev": true, + "requires": { + "fs-monkey": "^1.0.3" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "requires": { + "mime-db": "1.52.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "mini-css-extract-plugin": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.1.tgz", + "integrity": "sha512-wd+SD57/K6DiV7jIR34P+s3uckTRuQvx0tKPcvjFlrEylk6P4mQ2KSWk1hblj1Kxaqok7LogKOieygXqBczNlg==", + "dev": true, + "requires": { + "schema-utils": "^4.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + } + } + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "minipass": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", + "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "module-alias": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", + "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==", + "dev": true + }, + "mrmime": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", + "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "requires": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + } + }, + "mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "requires": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "requires": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true + }, + "node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true + }, + "normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "dev": true, + "requires": { + "path-key": "^2.0.0" + }, + "dependencies": { + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true + } + } + }, + "nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "requires": { + "boolbase": "^1.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true + }, + "object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true + }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "open": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "dev": true, + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, + "opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "requires": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "requires": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "requires": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true + }, + "parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "requires": { + "parse5": "^6.0.1" + }, + "dependencies": { + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + } + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "portfinder": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", + "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==", + "dev": true, + "requires": { + "async": "^2.6.4", + "debug": "^3.2.7", + "mkdirp": "^0.5.6" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "postcss": { + "version": "8.4.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", + "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "postcss-calc": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", + "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.9", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-colormin": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.0.tgz", + "integrity": "sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==", + "dev": true, + "requires": { + "browserslist": "^4.16.6", + "caniuse-api": "^3.0.0", + "colord": "^2.9.1", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-convert-values": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.2.tgz", + "integrity": "sha512-c6Hzc4GAv95B7suy4udszX9Zy4ETyMCgFPUDtWjdFTKH1SE9eFY/jEpHSwTH1QPuwxHpWslhckUQWbNRM4ho5g==", + "dev": true, + "requires": { + "browserslist": "^4.20.3", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-discard-comments": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", + "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", + "dev": true, + "requires": {} + }, + "postcss-discard-duplicates": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", + "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", + "dev": true, + "requires": {} + }, + "postcss-discard-empty": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", + "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", + "dev": true, + "requires": {} + }, + "postcss-discard-overridden": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", + "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", + "dev": true, + "requires": {} + }, + "postcss-import": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", + "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + } + }, + "postcss-js": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.0.tgz", + "integrity": "sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==", + "dev": true, + "requires": { + "camelcase-css": "^2.0.1" + } + }, + "postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "requires": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + } + }, + "postcss-loader": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", + "integrity": "sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==", + "dev": true, + "requires": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.5" + }, + "dependencies": { + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "postcss-merge-longhand": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.6.tgz", + "integrity": "sha512-6C/UGF/3T5OE2CEbOuX7iNO63dnvqhGZeUnKkDeifebY0XqkkvrctYSZurpNE902LDf2yKwwPFgotnfSoPhQiw==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^5.1.0" + } + }, + "postcss-merge-rules": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.2.tgz", + "integrity": "sha512-zKMUlnw+zYCWoPN6yhPjtcEdlJaMUZ0WyVcxTAmw3lkkN/NDMRkOkiuctQEoWAOvH7twaxUUdvBWl0d4+hifRQ==", + "dev": true, + "requires": { + "browserslist": "^4.16.6", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^3.1.0", + "postcss-selector-parser": "^6.0.5" + } + }, + "postcss-minify-font-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", + "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-minify-gradients": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", + "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", + "dev": true, + "requires": { + "colord": "^2.9.1", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-minify-params": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.3.tgz", + "integrity": "sha512-bkzpWcjykkqIujNL+EVEPOlLYi/eZ050oImVtHU7b4lFS82jPnsCb44gvC6pxaNt38Els3jWYDHTjHKf0koTgg==", + "dev": true, + "requires": { + "browserslist": "^4.16.6", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-minify-selectors": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", + "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.5" + } + }, + "postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "requires": {} + }, + "postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.4" + } + }, + "postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0" + } + }, + "postcss-nested": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz", + "integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.6" + } + }, + "postcss-normalize-charset": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", + "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", + "dev": true, + "requires": {} + }, + "postcss-normalize-display-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", + "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-positions": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", + "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-repeat-style": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", + "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-string": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", + "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-timing-functions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", + "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-unicode": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.0.tgz", + "integrity": "sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ==", + "dev": true, + "requires": { + "browserslist": "^4.16.6", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", + "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", + "dev": true, + "requires": { + "normalize-url": "^6.0.1", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-whitespace": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", + "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-ordered-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", + "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", + "dev": true, + "requires": { + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-reduce-initial": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.0.tgz", + "integrity": "sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw==", + "dev": true, + "requires": { + "browserslist": "^4.16.6", + "caniuse-api": "^3.0.0" + } + }, + "postcss-reduce-transforms": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", + "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "postcss-svgo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", + "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0", + "svgo": "^2.7.0" + } + }, + "postcss-unique-selectors": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", + "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.5" + } + }, + "postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true, + "optional": true + }, + "pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dev": true, + "requires": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "progress-webpack-plugin": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/progress-webpack-plugin/-/progress-webpack-plugin-1.0.16.tgz", + "integrity": "sha512-sdiHuuKOzELcBANHfrupYo+r99iPRyOnw15qX+rNlVUqXGfjXdH4IgxriKwG1kNJwVswKQHMdj1hYZMcb9jFaA==", + "dev": true, + "requires": { + "chalk": "^2.1.0", + "figures": "^2.0.0", + "log-update": "^2.3.0" + } + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "dependencies": { + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true + } + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true + } + } + }, + "read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "requires": { + "pify": "^2.3.0" + } + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "dependencies": { + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", + "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", + "dev": true, + "requires": { + "regenerate": "^1.4.2" + } + }, + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true + }, + "regenerator-transform": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", + "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "regexpu-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.1.0.tgz", + "integrity": "sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA==", + "dev": true, + "requires": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.0.1", + "regjsgen": "^0.6.0", + "regjsparser": "^0.8.2", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.0.0" + } + }, + "regjsgen": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", + "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==", + "dev": true + }, + "regjsparser": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", + "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true + } + } + }, + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true + }, + "renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dev": true, + "requires": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + } + }, + "select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true + }, + "selfsigned": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.0.1.tgz", + "integrity": "sha512-LmME957M1zOsUhG+67rAjKfiWFox3SBxE/yymatMZsAx+oMrJ0YQ8AToOnyCm7xbeg2ep37IHLxdu0o2MavQOQ==", + "dev": true, + "requires": { + "node-forge": "^1" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true + } + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "shell-quote": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", + "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", + "dev": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "sirv": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz", + "integrity": "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==", + "dev": true, + "requires": { + "@polka/url": "^1.0.0-next.20", + "mrmime": "^1.0.0", + "totalist": "^1.0.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, + "sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "requires": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", + "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", + "dev": true + }, + "spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + } + }, + "spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "dev": true, + "requires": { + "minipass": "^3.1.1" + } + }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "dev": true + }, + "stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "dev": true + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha512-RsSNPLpq6YUL7QYy44RnPVTn/lcVZtb48Uof3X5JLbF4zD/Gs7ZFDv2HWol+leoQN2mT86LAzSshGfkTlSOpsA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "stylehacks": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.0.tgz", + "integrity": "sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q==", + "dev": true, + "requires": { + "browserslist": "^4.16.6", + "postcss-selector-parser": "^6.0.4" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "dev": true + }, + "svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dev": true, + "requires": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "dependencies": { + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + } + } + }, + "table": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", + "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", + "dev": true, + "requires": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } + } + }, + "tailwindcss": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.1.8.tgz", + "integrity": "sha512-YSneUCZSFDYMwk+TGq8qYFdCA3yfBRdBlS7txSq0LUmzyeqRe3a8fBQzbz9M3WS/iFT4BNf/nmw9mEzrnSaC0g==", + "dev": true, + "requires": { + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "color-name": "^1.1.4", + "detective": "^5.2.1", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "lilconfig": "^2.0.6", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.14", + "postcss-import": "^14.1.0", + "postcss-js": "^4.0.0", + "postcss-load-config": "^3.1.4", + "postcss-nested": "5.0.6", + "postcss-selector-parser": "^6.0.10", + "postcss-value-parser": "^4.2.0", + "quick-lru": "^5.1.1", + "resolve": "^1.22.1" + }, + "dependencies": { + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true + }, + "terser": { + "version": "5.14.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", + "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", + "dev": true, + "requires": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + } + } + }, + "terser-webpack-plugin": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.5.tgz", + "integrity": "sha512-AOEDLDxD2zylUGf/wxHxklEkOe2/r+seuyOWujejFrIxHf11brA1/dWQNIgXa1c6/Wkxgu7zvv0JhOWfc2ELEA==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.14", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0", + "terser": "^5.14.1" + }, + "dependencies": { + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "requires": { + "any-promise": "^1.0.0" + } + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "requires": { + "thenify": ">= 3.1.0 < 4" + } + }, + "thread-loader": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/thread-loader/-/thread-loader-3.0.4.tgz", + "integrity": "sha512-ByaL2TPb+m6yArpqQUZvP+5S1mZtXsEP7nWKKlAUTm7fCml8kB5s1uI3+eHRP2bk5mVYfRSBI7FFf+tWEyLZwA==", + "dev": true, + "requires": { + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^4.1.0", + "loader-utils": "^2.0.0", + "neo-async": "^2.6.2", + "schema-utils": "^3.0.0" + }, + "dependencies": { + "loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true + }, + "totalist": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", + "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", + "dev": true + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", + "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", + "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", + "dev": true + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true + }, + "update-browserslist-db": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz", + "integrity": "sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true + }, + "vue": { + "version": "3.2.37", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.37.tgz", + "integrity": "sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ==", + "requires": { + "@vue/compiler-dom": "3.2.37", + "@vue/compiler-sfc": "3.2.37", + "@vue/runtime-dom": "3.2.37", + "@vue/server-renderer": "3.2.37", + "@vue/shared": "3.2.37" + } + }, + "vue-eslint-parser": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.11.0.tgz", + "integrity": "sha512-qh3VhDLeh773wjgNTl7ss0VejY9bMMa0GoDG2fQVyDzRFdiU3L7fw74tWZDHNQXdZqxO3EveQroa9ct39D2nqg==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "eslint-scope": "^5.1.1", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.2.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^6.3.0" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" + } + } + } + }, + "vue-hot-reload-api": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz", + "integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==", + "dev": true + }, + "vue-loader": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-17.0.0.tgz", + "integrity": "sha512-OWSXjrzIvbF2LtOUmxT3HYgwwubbfFelN8PAP9R9dwpIkj48TVioHhWWSx7W7fk+iF5cgg3CBJRxwTdtLU4Ecg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "hash-sum": "^2.0.0", + "loader-utils": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "vue-router": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.16.tgz", + "integrity": "sha512-JcO7cb8QJLBWE+DfxGUL3xUDOae/8nhM1KVdnudadTAORbuxIC/xAydC5Zr/VLHUDQi1ppuTF5/rjBGzgzrJNA==", + "requires": { + "@vue/devtools-api": "^6.0.0" + } + }, + "vue-style-loader": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz", + "integrity": "sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg==", + "dev": true, + "requires": { + "hash-sum": "^1.0.2", + "loader-utils": "^1.0.2" + }, + "dependencies": { + "hash-sum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", + "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", + "dev": true + } + } + }, + "vue-template-es2015-compiler": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz", + "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", + "dev": true + }, + "watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "requires": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + } + }, + "wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "requires": { + "minimalistic-assert": "^1.0.0" + } + }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "webpack": { + "version": "5.74.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", + "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", + "dev": true, + "requires": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.10.0", + "es-module-lexer": "^0.9.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.1.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.1.3", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "dependencies": { + "@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "dev": true + }, + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "webpack-bundle-analyzer": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.5.0.tgz", + "integrity": "sha512-GUMZlM3SKwS8Z+CKeIFx7CVoHn3dXFcUAjT/dcZQQmfSZGvitPfMob2ipjai7ovFFqPvTqkEZ/leL4O0YOdAYQ==", + "dev": true, + "requires": { + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "chalk": "^4.1.0", + "commander": "^7.2.0", + "gzip-size": "^6.0.0", + "lodash": "^4.17.20", + "opener": "^1.5.2", + "sirv": "^1.0.7", + "ws": "^7.3.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "webpack-chain": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/webpack-chain/-/webpack-chain-6.5.1.tgz", + "integrity": "sha512-7doO/SRtLu8q5WM0s7vPKPWX580qhi0/yBHkOxNkv50f6qB76Zy9o2wRTrrPULqYTvQlVHuvbA8v+G5ayuUDsA==", + "dev": true, + "requires": { + "deepmerge": "^1.5.2", + "javascript-stringify": "^2.0.1" + } + }, + "webpack-dev-middleware": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "dev": true, + "requires": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + } + } + } + }, + "webpack-dev-server": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.10.0.tgz", + "integrity": "sha512-7dezwAs+k6yXVFZ+MaL8VnE+APobiO3zvpp3rBHe/HmWQ+avwh0Q3d0xxacOiBybZZ3syTZw9HXzpa3YNbAZDQ==", + "dev": true, + "requires": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.1", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.0.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.4.2" + }, + "dependencies": { + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + } + }, + "ws": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", + "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", + "dev": true, + "requires": {} + } + } + }, + "webpack-merge": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + } + }, + "webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true + }, + "webpack-virtual-modules": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.4.4.tgz", + "integrity": "sha512-h9atBP/bsZohWpHnr+2sic8Iecb60GxftXsWNLLLSqewgIsGzByd2gcIID4nXcG+3tNe4GQG3dLcff3kXupdRA==", + "dev": true + }, + "websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true + }, + "whatwg-fetch": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", + "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==", + "dev": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, + "requires": {} + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true + }, + "yorkie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yorkie/-/yorkie-2.0.0.tgz", + "integrity": "sha512-jcKpkthap6x63MB4TxwCyuIGkV0oYP/YRyuQU5UO0Yz/E/ZAu+653/uov+phdmO54n6BcvFRyyt0RRrWdN2mpw==", + "dev": true, + "requires": { + "execa": "^0.8.0", + "is-ci": "^1.0.10", + "normalize-path": "^1.0.0", + "strip-indent": "^2.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz", + "integrity": "sha512-zDWS+Rb1E8BlqqhALSt9kUhss8Qq4nN3iof3gsOdyINksElaPyNBtKUMTR62qhvgVWR0CqCX7sdnKe4MnUbFEA==", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "dev": true + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "normalize-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-1.0.0.tgz", + "integrity": "sha512-7WyT0w8jhpDStXRq5836AMmihQwq2nrUVQrgjvUo/p/NZf9uy/MeJ246lBJVmWuYXMlJuG9BNZHF0hWjfTbQUA==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + } + } + } + } +} diff --git a/web/app/package.json b/web/app/package.json new file mode 100644 index 00000000..0d9a10f0 --- /dev/null +++ b/web/app/package.json @@ -0,0 +1,50 @@ +{ + "name": "gatus", + "version": "4.0.0", + "private": true, + "scripts": { + "serve": "vue-cli-service serve --mode development", + "build": "vue-cli-service build --modern --mode production", + "lint": "vue-cli-service lint" + }, + "dependencies": { + "@headlessui/vue": "^1.7.3", + "@heroicons/vue": "^2.0.12", + "core-js": "3.22.8", + "vue": "3.2.37", + "vue-router": "4.0.16" + }, + "devDependencies": { + "@vue/cli-plugin-babel": "5.0.4", + "@vue/cli-plugin-eslint": "5.0.4", + "@vue/cli-plugin-router": "5.0.4", + "@vue/cli-service": "5.0.4", + "@vue/compiler-sfc": "3.2.37", + "autoprefixer": "10.4.7", + "babel-eslint": "10.1.0", + "eslint": "7.32.0", + "eslint-plugin-vue": "7.20.0", + "postcss": "8.4.14", + "tailwindcss": "^3.1.8" + }, + "eslintConfig": { + "root": true, + "env": { + "node": true + }, + "extends": [ + "plugin:vue/vue3-essential", + "eslint:recommended" + ], + "parserOptions": { + "parser": "babel-eslint" + }, + "rules": {} + }, + "browserslist": [ + "defaults", + "> 1%", + "last 2 versions", + "not dead" + ] +} diff --git a/web/app/postcss.config.js b/web/app/postcss.config.js new file mode 100644 index 00000000..6788c42d --- /dev/null +++ b/web/app/postcss.config.js @@ -0,0 +1,8 @@ +const tailwindcss = require('tailwindcss'); + +module.exports = { + plugins: [ + tailwindcss('./tailwind.config.js'), + require('autoprefixer'), + ], +}; \ No newline at end of file diff --git a/web/app/public/apple-touch-icon.png b/web/app/public/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..63263f49b27e024abb9c03a00d49136770f8a0d2 GIT binary patch literal 7677 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 literal 0 HcmV?d00001 diff --git a/web/app/public/favicon-32x32.png b/web/app/public/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..79febd05780020b9a3cd4fb73942edb907322601 GIT binary patch literal 1703 zcmZ`(X;4#F6uwCefh1snU`T-QP(U<_A*e_KG-xO!ge~mOIFpo3tU!Q-h}M|^QnXl> zTEzv|QfVW^;6evcKt+oQ;{w*DBTA7?#DKvSwf4TGw$mS-Iq%(j&pF@sopau@BQ`qR zmdGFi0BnU30ts9Z^s>gmyP)Blt8kf<6&e)^Kub00y$p{A(;_5M0BrUKK)nrsS?E%a z1E6FBFd+qiyB7eOy#8EV5CGVKSaCuaObBR+<~2tKeX@WCAx{$lkBOSZKutJLi-tfB z0Qn$DE4HKsYQ)@Tk!24y0gM7QpaR^JqF@V86GWj2fto}#qy!)Wbi__x#S2 zxrfQv$AHCS6@Y&dZ7N6;#jNpy2CIc|XqGym9J3nwy&)W>RRY)-P@Acmg`nV{{h77u|I30|`pN(48- zkQfSC2vLIV7ye~qBVDY0N^C;j3HuCY~N34hdUnS6{kVMA=+M_?; z4cjf8#W_f>*f}g4x#QwKu))4}l*OJjHJ^N8Gr?Z^^fi~FH*y@O1dr!Z@4Y$9Am1Xn zy?nOW$z$@li?(ClypEp?)&zSZL4(XN>-5ZLGi(1S-`89Idpu(O-qr2Ho&M(b@t185 zzs;USTHk$W{pHQu+fQEHJ@~8peDVDqePN9K-BV6hHa6FPZ0Vw3KJHmUzS|qs)o(j! zZ#CpSc75FUN$m6wV~ZD!-Wc?;!oRh3HoWe^melKS!6q7kqVy# zehqqIChZPSLMK5Xj0z)+klh`em*sb+gu{=ABou@utTLoY`PiM}UY_cd?v&pkc9IvW zE_1A;(rDX0R2-cR<;S=;b#+mEbJ90rsqAw76vw~9Sthfo zZ9UbBnaO#Em|oej!&jA#9ITgpMN8IU1FWccBa^?Uv92zl#)RSZ(Y&X|=5js*-Q))s zc-8_!1#sF{+C06BNFvZFm?W3BEh_3>dt1(Y@0GrXN}Ptewh0pO!fTX@<7^$D+JMFP z_x1Z1O+PE}2#{VZ_jHlBz31bpEq+*?Swui?3ozpD(<8tle&?W6}Ot z_uH$78>7p->z>4yoTvLAH4;iw&r9*A$M_>-9Cq*1f$}+{C)Vst(NR1*BV2mww=L;( zJ~UmoqwYj zKh9ErzXZQkT+VXt@&Fc_&1JKZ8KdAofSkPa?9Af-2gGTU-ar5yRfx~aRH$SH89=2{ z`DW*SS(ql1XZYq7WF0UDF|2ZiEN#p zJ#s|89)>=w-119moSZSGo!tN$%i)c0pYTNW-t<~CMg`&dl@{$?i zA3Bv$lF%7Dbf!bK*0D==qf)TeSEkr%g+i4Uts`YC?;yA+lmN?af8X7EH|IXiedN)b z`Eu^L=bZ2Redm1VobNoA$VU}4WeOpzre|u2rV&wfwHhBo^ly9{gOJmIGK%P7WVi}> zP=xLP@pt8^DFq|;e-V9+;=aF9!uKKQsDVC0d@C)YFOQh~p)yYTp903BJ^4F^DrUsW zC;Y#*$FAtMe#GPtm2twq$_4%phAPHu`Q}Xc>&=?vCCD>}Q2K*KmuWyd4tM zY*Cj((S>#Rp$VwPht^EA0l3dNVAu4Jw?mS?4ISL{1-E+T(PwV3fUNt}k`BM(61y86 zra*^nAh#Hu^8HB%)*{l<6P?!F9XaMzht^d6L<@FA?c_%v{T6iC1j3jyXkCuNhS()? zd|J|xTYz8IceQ%ghv+kojF-_Ruz|VIVTVUO*mkaWxQYA2V?(oM;q#nyc0{$42k7Dy)UFmz_!y_2h0&3AJqRc2mCvle;*m^ zk*zs@V2hKYG)%^;I&KHETj&tm_Q^Oh;XzTV(lROA~=VC)FYD ze@0r?B0-%4Slnkyh0EQEAHu(xmu_ z^c~>c<%n6so{pTFm6{bW)`2lTX#7iQEU+1UPz|?LxEjD23Bi1Xrf>U+<6#cfQ zGq^=(`{)bEbD*{(_=uRdrX%=GBR}Bx;&1c;`)T+1!|ROl>N=_x8L_&J*L4KV^HRe9 zqzfC;+9C(+uKfh8A0HcW{7L_65BS-3T;oUHM@Jlg!vB~D{Qba`2kCt=Y5f0UKTmkT z&$fTcE&f|mXXs6H9|-l~+bUiE?F_aV`2qXiJm5bWOGiF*i=TCEdTRQ` zCYgp0zB%LpF2tei{Spr##?)uO$;+1zazIUI+1bWN%CE=C8rwxA!5mF^Bh7 zH&NWb4)gI%VkI=b);NlNdFXIls)sxOVKDfyCh+mxs^K(-In2*IU)cICtWPWxz8?6c z;}Y8Jd!pXP_d(fDf^M_HUV#4=HOjGzWBsyG+6L*!k(Shq&&$f!t(kZo=GwI))pP~F zFUya;!U@2B#0I}}-!U-9e&-S!{6+Npw{Pxg9AhZvJ<5GH>3hcpJM0Lj1|A>mK|ElG zUr8J4`f)Lh2X5!_GEVn-gipX{xBe2xsKYIp$n8#R0+albz4zJId$})XOyzApG-c|) z3jEUkD`}SU$7|2Czg;X&+QiS6+*`hG_&)p)s7 zkEK)%{QErWaF`a-wNjpH0&L+uN&Si#-Mq7JJ69@i5!DSshh8Z^H34*&-(?}D^l33&X=6(~_j=yHV#Zi+&9~6uY)f}!1J7@f zQXQ=OLjiNwz0xu+5`b-KNW~VL^=M59nyYk=;e5x8t}^D z$^wT4Na8F7U$NMZFkg{x=;idvd?<^spTiK2{Q~(=j#r8tvI2p291tiWWY~skKCN7d zkH%M%qsi59Xt*?-c?tnK>rsJM*2`BY=>_zNv)1iMht>_fD^V{M6v`I4o_YgHJ?+j0 zlzJ;>wffaUwVq4$&iAycL5-mDJ*}ays`%XWJj|(EKzl%ZPRGRO*>5Ums*pFLPm(T& zEY1JchnzM{Pg|;SbX#ijxh8gV=^V(H>;+M)DgR=+h1K(1ZjT(!K%U0Em*3$1 zV5*0h8s>-eaVInRYJf?T_K zR$mY8bS`xKt)QRY7G)Ut4x@!Vu)RV4yGGp&@vWRshd%+EOZa+0XI*01K?}%ouXIjo zMh)bhFv!&GfWsr8NjmvjB#zN5>3N={l$OzF#JsH=8ZU*sN$7hm2>)e} zjW)OT?Q6TLex=E;?W}r3_$9Iw+_djHz`}l%=CggM0WnV zt1UG{?}M1@p%1dB7{pmdVF%YiKDWhA@jQ|J5PXx}Yno?bHh^Kj%%ldMaS?s>#Xhoo(>O=WlLxU!dLtd2f;ir0DGCQsqLRqc<@+~@a-3P9LBjb z?lfWyPWoOJ=UDYOGosC;6r16*7S3xmZpVR0eYlT9ws@E11zo@AHGabPqQGarD1Y{o zvoAz>^$uusV>{o?#8cN1nh*b}wEtSVuc`TreM)^G5BlAMD7#hFy&(SH2lM|n&>j^g z-2A$+VF!;<+n$|{BO3OS5+Q7&#AT$De}F3MW1#WrT%7#=jvY3ezZD>bw`t| z3-6f-by{p)KXbNP2jGE|5<&N}j)oW9J$o29O16;+Ck)BuXaRen1q?fX!HMf;rp-ie b>>;`sy<;?HH70)NZ=BZ4bQkthQI6?<6fZl` literal 0 HcmV?d00001 diff --git a/web/app/public/index.html b/web/app/public/index.html new file mode 100644 index 00000000..31982192 --- /dev/null +++ b/web/app/public/index.html @@ -0,0 +1,27 @@ + + + + + + {{ .UI.Title }} + + + + + + + + + + + + + + + + +
+ + diff --git a/web/app/public/logo-192x192.png b/web/app/public/logo-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..272a45b8587b1c491f9a8a78b1bba30519d07df2 GIT binary patch literal 8659 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
!OYAYxbt4qH>?O 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 IPvNisAHWa`tN;K2 literal 0 HcmV?d00001 diff --git a/web/app/public/manifest.json b/web/app/public/manifest.json new file mode 100644 index 00000000..d0353120 --- /dev/null +++ b/web/app/public/manifest.json @@ -0,0 +1,24 @@ +{ + "id": "gatus", + "name": "Gatus", + "short_name": "Gatus", + "description": "Gatus is an advanced automated status page that lets you monitor your applications and configure alerts to notify you if there's an issue", + "lang": "en", + "scope": "/", + "start_url": "/", + "theme_color": "#f7f9fb", + "background_color": "#f7f9fb", + "display": "standalone", + "icons": [ + { + "src": "/logo-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/logo-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/web/app/src/App.vue b/web/app/src/App.vue new file mode 100644 index 00000000..a4714cd6 --- /dev/null +++ b/web/app/src/App.vue @@ -0,0 +1,106 @@ + + + + \ No newline at end of file diff --git a/web/app/src/assets/logo.png b/web/app/src/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..fd49e1ae26afb7c034ef69387afd1510e108a659 GIT binary patch literal 52184 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
PryPc|77v|?tfSBUy$`apvC0oU=L~` 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 GGywofiDVuC literal 0 HcmV?d00001 diff --git a/web/app/src/assets/logo.svg b/web/app/src/assets/logo.svg new file mode 100644 index 00000000..52cc2b84 --- /dev/null +++ b/web/app/src/assets/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/src/components/Endpoint.vue b/web/app/src/components/Endpoint.vue new file mode 100644 index 00000000..f1f41e05 --- /dev/null +++ b/web/app/src/components/Endpoint.vue @@ -0,0 +1,186 @@ + + + + + + + \ No newline at end of file diff --git a/web/app/src/components/EndpointGroup.vue b/web/app/src/components/EndpointGroup.vue new file mode 100644 index 00000000..dbc1fa63 --- /dev/null +++ b/web/app/src/components/EndpointGroup.vue @@ -0,0 +1,99 @@ + + + + + + + \ No newline at end of file diff --git a/web/app/src/components/Endpoints.vue b/web/app/src/components/Endpoints.vue new file mode 100644 index 00000000..0f9b71ac --- /dev/null +++ b/web/app/src/components/Endpoints.vue @@ -0,0 +1,74 @@ + + + + + + + diff --git a/web/app/src/components/Loading.vue b/web/app/src/components/Loading.vue new file mode 100644 index 00000000..3cc40f16 --- /dev/null +++ b/web/app/src/components/Loading.vue @@ -0,0 +1,11 @@ + + + diff --git a/web/app/src/components/Pagination.vue b/web/app/src/components/Pagination.vue new file mode 100644 index 00000000..3f909e1f --- /dev/null +++ b/web/app/src/components/Pagination.vue @@ -0,0 +1,34 @@ + + + + \ No newline at end of file diff --git a/web/app/src/components/Settings.vue b/web/app/src/components/Settings.vue new file mode 100644 index 00000000..a8804e22 --- /dev/null +++ b/web/app/src/components/Settings.vue @@ -0,0 +1,104 @@ + + + + + + + diff --git a/web/app/src/components/Social.vue b/web/app/src/components/Social.vue new file mode 100644 index 00000000..c6d729ec --- /dev/null +++ b/web/app/src/components/Social.vue @@ -0,0 +1,36 @@ + + + + + + + diff --git a/web/app/src/components/Tooltip.vue b/web/app/src/components/Tooltip.vue new file mode 100644 index 00000000..c5964df1 --- /dev/null +++ b/web/app/src/components/Tooltip.vue @@ -0,0 +1,130 @@ + + + + + + + diff --git a/web/app/src/index.css b/web/app/src/index.css new file mode 100644 index 00000000..c73cbbb6 --- /dev/null +++ b/web/app/src/index.css @@ -0,0 +1,39 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +.bg-success { + background-color: #28a745; +} + +html:not(.dark) body { + background-color: #f7f9fb; +} + +html { + height: 100%; +} + +body { + padding-top: 20px; + padding-bottom: 20px; + min-height: 100vh; +} + +#global { + margin-top: 0 !important; +} + +#global, #results { + max-width: 1280px; +} + +@media screen and (max-width: 1279px) { + body { + padding-top: 0; + padding-bottom: 0; + } + #global { + min-height: 100vh; + } +} diff --git a/web/app/src/main.js b/web/app/src/main.js new file mode 100644 index 00000000..b53334fe --- /dev/null +++ b/web/app/src/main.js @@ -0,0 +1,8 @@ +import { createApp } from 'vue' +import App from './App.vue' +import './index.css' +import router from './router' + +export const SERVER_URL = process.env.NODE_ENV === 'production' ? '' : 'http://localhost:8080' + +createApp(App).use(router).mount('#app') diff --git a/web/app/src/mixins/helper.js b/web/app/src/mixins/helper.js new file mode 100644 index 00000000..48f6f6a1 --- /dev/null +++ b/web/app/src/mixins/helper.js @@ -0,0 +1,38 @@ +export const helper = { + methods: { + generatePrettyTimeAgo(t) { + let differenceInMs = new Date().getTime() - new Date(t).getTime(); + if (differenceInMs < 500) { + return "now"; + } + if (differenceInMs > 3 * 86400000) { // If it was more than 3 days ago, we'll display the number of days ago + let days = (differenceInMs / 86400000).toFixed(0); + return days + " day" + (days !== "1" ? "s" : "") + " ago"; + } + if (differenceInMs > 3600000) { // If it was more than 1h ago, display the number of hours ago + let hours = (differenceInMs / 3600000).toFixed(0); + return hours + " hour" + (hours !== "1" ? "s" : "") + " ago"; + } + if (differenceInMs > 60000) { + let minutes = (differenceInMs / 60000).toFixed(0); + return minutes + " minute" + (minutes !== "1" ? "s" : "") + " ago"; + } + let seconds = (differenceInMs / 1000).toFixed(0); + return seconds + " second" + (seconds !== "1" ? "s" : "") + " ago"; + }, + generatePrettyTimeDifference(start, end) { + let minutes = Math.ceil((new Date(start) - new Date(end)) / 1000 / 60); + return minutes + (minutes === 1 ? ' minute' : ' minutes'); + }, + prettifyTimestamp(timestamp) { + let date = new Date(timestamp); + let YYYY = date.getFullYear(); + let MM = ((date.getMonth() + 1) < 10 ? "0" : "") + "" + (date.getMonth() + 1); + let DD = ((date.getDate()) < 10 ? "0" : "") + "" + (date.getDate()); + let hh = ((date.getHours()) < 10 ? "0" : "") + "" + (date.getHours()); + let mm = ((date.getMinutes()) < 10 ? "0" : "") + "" + (date.getMinutes()); + let ss = ((date.getSeconds()) < 10 ? "0" : "") + "" + (date.getSeconds()); + return YYYY + "-" + MM + "-" + DD + " " + hh + ":" + mm + ":" + ss; + }, + } +} diff --git a/web/app/src/router/index.js b/web/app/src/router/index.js new file mode 100644 index 00000000..b04bbd87 --- /dev/null +++ b/web/app/src/router/index.js @@ -0,0 +1,23 @@ +import {createRouter, createWebHistory} from 'vue-router' +import Home from '@/views/Home' +import Details from "@/views/Details"; + +const routes = [ + { + path: '/', + name: 'Home', + component: Home + }, + { + path: '/endpoints/:key', + name: 'Details', + component: Details, + }, +]; + +const router = createRouter({ + history: createWebHistory(process.env.BASE_URL), + routes +}); + +export default router; diff --git a/web/app/src/views/Details.vue b/web/app/src/views/Details.vue new file mode 100644 index 00000000..fad1d351 --- /dev/null +++ b/web/app/src/views/Details.vue @@ -0,0 +1,231 @@ + + + + + + \ No newline at end of file diff --git a/web/app/src/views/Home.vue b/web/app/src/views/Home.vue new file mode 100644 index 00000000..21b95d55 --- /dev/null +++ b/web/app/src/views/Home.vue @@ -0,0 +1,76 @@ + + + \ No newline at end of file diff --git a/web/app/tailwind.config.js b/web/app/tailwind.config.js new file mode 100644 index 00000000..ab741dc8 --- /dev/null +++ b/web/app/tailwind.config.js @@ -0,0 +1,20 @@ +module.exports = { + content: [ + './public/index.html', + './src/**/*.{vue,js,ts,jsx,tsx}' + ], + darkMode: 'class', // or 'media' or 'class' + theme: { + fontFamily: { + 'mono': ['Consolas', 'Monaco', '"Courier New"', 'monospace'] + }, + extend: {}, + }, + variants: { + extend: {}, + }, + plugins: [], + future: { + hoverOnlyWhenSupported: true, + }, +} diff --git a/web/app/vue.config.js b/web/app/vue.config.js new file mode 100644 index 00000000..d271e7d7 --- /dev/null +++ b/web/app/vue.config.js @@ -0,0 +1,6 @@ +module.exports = { + filenameHashing: false, + productionSourceMap: false, + outputDir: '../static', + publicPath: '/' +} \ No newline at end of file diff --git a/web/static.go b/web/static.go new file mode 100644 index 00000000..97a34f8f --- /dev/null +++ b/web/static.go @@ -0,0 +1,13 @@ +package static + +import "embed" + +var ( + //go:embed static + FileSystem embed.FS +) + +const ( + RootPath = "static" + IndexPath = RootPath + "/index.html" +) diff --git a/web/static/apple-touch-icon.png b/web/static/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..63263f49b27e024abb9c03a00d49136770f8a0d2 GIT binary patch literal 7677 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.tooltip-title:first-child{margin-top:0} + +/* +! tailwindcss v3.1.8 | MIT License | https://tailwindcss.com +*/*,:after,:before{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:after,:before{--tw-content:""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Consolas,Monaco,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::-webkit-backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.invisible{visibility:hidden}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.top-2{top:.5rem}.left-5{left:1.25rem}.bottom-2{bottom:.5rem}.float-right{float:right}.mx-auto{margin-left:auto;margin-right:auto}.my-auto{margin-top:auto;margin-bottom:auto}.my-4{margin-top:1rem;margin-bottom:1rem}.my-24{margin-top:6rem;margin-bottom:6rem}.mb-2{margin-bottom:.5rem}.mt-4{margin-top:1rem}.mb-5{margin-bottom:1.25rem}.mt-3{margin-top:.75rem}.mr-2{margin-right:.5rem}.mb-4{margin-bottom:1rem}.mt-12{margin-top:3rem}.mt-6{margin-top:1.5rem}.mb-10{margin-bottom:2.5rem}.mb-1{margin-bottom:.25rem}.mt-2{margin-top:.5rem}.mt-1{margin-top:.25rem}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.h-64{height:16rem}.h-6{height:1.5rem}.w-64{width:16rem}.w-3\/4{width:75%}.w-1\/4{width:25%}.w-full{width:100%}.w-1\/2{width:50%}.w-7{width:1.75rem}.w-3{width:.75rem}.w-4{width:1rem}.w-8{width:2rem}.max-w-md{max-width:28rem}.max-w-lg{max-width:32rem}.flex-1{flex:1 1 0%}@-webkit-keyframes spin{to{transform:rotate(1turn)}}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{-webkit-animation:spin 1s linear infinite;animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.flex-row{flex-direction:row}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px*var(--tw-divide-y-reverse))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity))}.overflow-x-hidden{overflow-x:hidden}.rounded-sm{border-radius:.125rem}.rounded-md{border-radius:.375rem}.rounded-none{border-radius:0}.rounded{border-radius:.25rem}.rounded-xl{border-radius:.75rem}.rounded-full{border-radius:9999px}.border{border-width:1px}.border-l{border-left-width:1px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-green-800{--tw-border-opacity:1;border-color:rgb(22 101 52/var(--tw-border-opacity))}.border-gray-400{--tw-border-opacity:1;border-color:rgb(156 163 175/var(--tw-border-opacity))}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity))}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-gray-600{--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}.bg-green-700{--tw-bg-opacity:1;background-color:rgb(21 128 61/var(--tw-bg-opacity))}.bg-red-600{--tw-bg-opacity:1;background-color:rgb(220 38 38/var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-gray-400{--tw-bg-opacity:1;background-color:rgb(156 163 175/var(--tw-bg-opacity))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-green-600{--tw-gradient-from:#16a34a;--tw-gradient-to:rgba(22,163,74,0);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.to-green-700{--tw-gradient-to:#15803d}.object-scale-down{-o-object-fit:scale-down;object-fit:scale-down}.p-5{padding:1.25rem}.p-1{padding:.25rem}.p-3{padding:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-0{padding-top:0;padding-bottom:0}.py-7{padding-top:1.75rem;padding-bottom:1.75rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-0{padding-left:0;padding-right:0}.pb-12{padding-bottom:3rem}.pt-12{padding-top:3rem}.pt-2{padding-top:.5rem}.pb-2{padding-bottom:.5rem}.pl-1{padding-left:.25rem}.pb-0\.5{padding-bottom:.125rem}.pb-0{padding-bottom:0}.pl-12{padding-left:3rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:Consolas,Monaco,Courier New,monospace}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.text-2xl{font-size:1.5rem;line-height:2rem}.font-light{font-weight:300}.font-medium{font-weight:500}.font-extrabold{font-weight:800}.font-bold{font-weight:700}.leading-6{line-height:1.5rem}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity))}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.opacity-60{opacity:.6}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow,.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.transition{transition-property:color,background-color,border-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.bg-success{background-color:#28a745}html:not(.dark) body{background-color:#f7f9fb}html{height:100%}body{padding-top:20px;padding-bottom:20px;min-height:100vh}#global{margin-top:0!important}#global,#results{max-width:1280px}@media screen and (max-width:1279px){body{padding-top:0;padding-bottom:0}#global{min-height:100vh}}@media (hover:hover) and (pointer:fine){.hover\:scale-110:hover{--tw-scale-x:1.1;--tw-scale-y:1.1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.hover\:bg-gray-200:hover{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.hover\:from-green-700:hover{--tw-gradient-from:#15803d;--tw-gradient-to:rgba(21,128,61,0);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.hover\:to-green-800:hover{--tw-gradient-to:#166534}.hover\:text-gray-500:hover{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.hover\:text-blue-800:hover{--tw-text-opacity:1;color:rgb(30 64 175/var(--tw-text-opacity))}.hover\:underline:hover{-webkit-text-decoration-line:underline;text-decoration-line:underline}}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-blue-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(59 130 246/var(--tw-ring-opacity))}.dark .dark\:divide-gray-600>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(75 85 99/var(--tw-divide-opacity))}.dark .dark\:border-gray-500{--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}.dark .dark\:bg-gray-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}.dark .dark\:bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.dark .dark\:bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.dark .dark\:text-gray-200{--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity))}.dark .dark\:text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.dark .dark\:text-gray-100{--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}@media (hover:hover) and (pointer:fine){.dark .dark\:hover\:bg-gray-700:hover{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.dark .dark\:hover\:bg-gray-600:hover{--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity))}.dark .dark\:hover\:text-gray-400:hover{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.dark .dark\:hover\:text-blue-400:hover{--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity))}.dark .dark\:hover\:text-gray-500:hover{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}}@media (min-width:640px){.sm\:px-10{padding-left:2.5rem;padding-right:2.5rem}.sm\:text-lg{font-size:1.125rem;line-height:1.75rem}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}@media (min-width:1024px){.lg\:text-4xl{font-size:2.25rem;line-height:2.5rem}}@media (min-width:1280px){.xl\:my-5{margin-top:1.25rem;margin-bottom:1.25rem}.xl\:rounded{border-radius:.25rem}.xl\:border{border-width:1px}.xl\:px-24{padding-left:6rem;padding-right:6rem}.xl\:pb-5{padding-bottom:1.25rem}.xl\:text-5xl{font-size:3rem;line-height:1}.xl\:text-3xl{font-size:1.875rem;line-height:2.25rem}.xl\:shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}}#settings{position:fixed;left:10px;bottom:10px}#settings select:focus{box-shadow:none}.endpoint:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.endpoint:last-child{border-bottom-left-radius:3px;border-bottom-right-radius:3px;border-bottom-width:3px}.status-over-time{overflow:auto}.status-over-time>span:not(:first-child){margin-left:2px}.status{cursor:pointer;transition:all .5s ease-in-out;overflow-x:hidden;color:#fff;width:5%;font-size:75%;font-weight:700;text-align:center}.status:hover{opacity:.7;transition:opacity .1s ease-in-out;color:#000}.status-time-ago{color:#6a737d;opacity:.5;margin-top:5px}.status.status-success:after{content:"✓"}.status.status-failure:after{content:"X"}@media screen and (max-width:600px){.status.status-failure:after,.status.status-success:after{content:" ";white-space:pre}}.endpoint-group{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.endpoint-group h5:hover{color:#1b1e21}.endpoint-group-content>div:first-child{border-top-left-radius:0;border-top-right-radius:0}.endpoint[data-v-62613fe4]{border-radius:3px;border-bottom-width:3px} \ No newline at end of file diff --git a/web/static/favicon-16x16.png b/web/static/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..a3aa9aacb8411ed772dcb46bbfe211a6267fab33 GIT binary patch literal 1053 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl<6e(pbstU$g(vPY0F z14ES>14Ba#1H&(%P{RubhEf9thF1v;3|2E37{m+a>z)%80ma9U6Y9I(G#qghjsf2+MDAmAVvkoHe z<*+K0ArvIeP|N^SX}i|jYP}~=4G@7fddW^uk(;Oj)WTQ{7xcDU<0CggO>TlJLm5aA zWF}O-H&76cWGAS>je-aQxj;7qO}1X;0TBV(3(@Yj$c>?)LB3y2ZlWqg7egV$4c-tn zKtbDeAYX?9-CWARSPJrm!+r;bauC~ivoA;x>^q=(hFpZJfP(-3|Nl~OlQHNx%E+U)A$3&wl^?+IU9&++(d1mE26(W!qnIzS^#_^I734X4O6WS@>UYWuE8C zXa86p^+BZ&Xdh#ex4TQ4&Xt?0Kn`btMg}S#M9T6{V9*Ukc58W z{s?!V&<#%)#}J9jw*8gnhZ03v-0OlgRk;>!5>@oxl%g4Z?SfNk|IGLQMfa>XsK5Jr zPWAapwjZAT-Fts@Uw;1X`-He3Y=6r5d5k2K+7InZ>~-PkxRg?oV`HY6d5Ph>h@?Nm z#3d%V^>ZZ8wHccCheUC+=~T^#WSSP<@MhJuYa1P(ZJ2rg*17iy;aUe=-$`z$zrfnA zF>Qt=Q=flB@XIwkbJ`{LA9yMnbbZ&#h>g*o^ce1hFP$a2T6+Dl!^y$FM2n+xw#?5u z_A1i1x^a4y^!iKDPya6de1AvGk3X;c?k`-T>)^XFKn&Cy&nOiTJTUc4xd$I_#u!2j2$>9`c<;@`qr*B+2 mapcSqnIr6{8$1?x=`p+#7cBYYWI7dS1%s!npUXO@geCywF>)^e literal 0 HcmV?d00001 diff --git a/web/static/favicon-32x32.png b/web/static/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..79febd05780020b9a3cd4fb73942edb907322601 GIT binary patch literal 1703 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 b>>;`sy<;?HH70)NZ=BZ4bQkthQI6?<6fZl` literal 0 HcmV?d00001 diff --git a/web/static/img/logo.svg b/web/static/img/logo.svg new file mode 100644 index 00000000..52cc2b84 --- /dev/null +++ b/web/static/img/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/static/index.html b/web/static/index.html new file mode 100644 index 00000000..be309704 --- /dev/null +++ b/web/static/index.html @@ -0,0 +1 @@ +{{ .UI.Title }}
\ No newline at end of file diff --git a/web/static/js/app.js b/web/static/js/app.js new file mode 100644 index 00000000..29ffcec2 --- /dev/null +++ b/web/static/js/app.js @@ -0,0 +1 @@ +(function(){"use strict";var e={2434:function(e,t,s){s.d(t,{L:function(){return bs}});s(7727);var n=s(9963),o=s(6252),a=s(3577),r=s.p+"img/logo.svg";const i={class:"mb-2"},l={class:"flex flex-wrap"},d={class:"w-3/4 text-left my-auto"},g={class:"text-3xl xl:text-5xl lg:text-4xl font-light"},u={class:"w-1/4 flex justify-end"},h=["src"],c={key:1,src:r,alt:"Gatus",class:"object-scale-down",style:{"max-width":"100px","min-width":"50px","min-height":"50px"}},p={key:0,class:"flex flex-wrap"},m=["href"],v={key:2,class:"mx-auto max-w-md pt-12"},f=(0,o._)("img",{src:r,alt:"Gatus",class:"mx-auto",style:{"max-width":"160px","min-width":"50px","min-height":"50px"}},null,-1),w=(0,o._)("h2",{class:"mt-4 text-center text-4xl font-extrabold text-gray-800 dark:text-gray-200"}," Gatus ",-1),x={class:"py-7 px-4 rounded-sm sm:px-10"},y={key:0,class:"text-red-500 text-center mb-5"},k={class:"text-sm"},T={key:0,class:"text-red-500"},b={key:1,class:"text-red-500"},R=["href"];function _(e,t,s,n,r,_){const S=(0,o.up)("Loading"),D=(0,o.up)("router-view"),I=(0,o.up)("Tooltip"),A=(0,o.up)("Social");return(0,o.wg)(),(0,o.iD)(o.HY,null,[r.retrievedConfig?((0,o.wg)(),(0,o.iD)("div",{key:1,class:(0,a.C_)([r.config&&r.config.oidc&&!r.config.authenticated?"hidden":"","container container-xs relative mx-auto xl:rounded xl:border xl:shadow-xl xl:my-5 p-5 pb-12 xl:pb-5 text-left dark:bg-gray-800 dark:text-gray-200 dark:border-gray-500"]),id:"global"},[(0,o._)("div",i,[(0,o._)("div",l,[(0,o._)("div",d,[(0,o._)("div",g,(0,a.zw)(_.header),1)]),(0,o._)("div",u,[((0,o.wg)(),(0,o.j4)((0,o.LL)(_.link?"a":"div"),{href:_.link,target:"_blank",class:"flex items-center justify-center",style:{width:"100px","min-height":"100px"}},{default:(0,o.w5)((()=>[_.logo?((0,o.wg)(),(0,o.iD)("img",{key:0,src:_.logo,alt:"Gatus",class:"object-scale-down",style:{"max-width":"100px","min-width":"50px","min-height":"50px"}},null,8,h)):((0,o.wg)(),(0,o.iD)("img",c))])),_:1},8,["href"]))])]),_.buttons?((0,o.wg)(),(0,o.iD)("div",p,[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(_.buttons,(e=>((0,o.wg)(),(0,o.iD)("a",{key:e.name,href:e.link,target:"_blank",class:"px-2 py-0.5 font-medium select-none text-gray-600 hover:text-gray-500 dark:text-gray-300 dark:hover:text-gray-400 hover:underline"},(0,a.zw)(e.name),9,m)))),128))])):(0,o.kq)("",!0)]),(0,o.Wm)(D,{onShowTooltip:_.showTooltip},null,8,["onShowTooltip"])],2)):((0,o.wg)(),(0,o.j4)(S,{key:0,class:"h-64 w-64 px-4"})),r.config&&r.config.oidc&&!r.config.authenticated?((0,o.wg)(),(0,o.iD)("div",v,[f,w,(0,o._)("div",x,[e.$route&&e.$route.query.error?((0,o.wg)(),(0,o.iD)("div",y,[(0,o._)("div",k,["access_denied"===e.$route.query.error?((0,o.wg)(),(0,o.iD)("span",T,"You do not have access to this status page")):((0,o.wg)(),(0,o.iD)("span",b,(0,a.zw)(e.$route.query.error),1))])])):(0,o.kq)("",!0),(0,o._)("div",null,[(0,o._)("a",{href:`${r.SERVER_URL}/oidc/login`,class:"max-w-lg mx-auto w-full flex justify-center py-3 px-4 border border-green-800 rounded-md shadow-lg text-sm text-white bg-green-700 bg-gradient-to-r from-green-600 to-green-700 hover:from-green-700 hover:to-green-800"}," Login with OIDC ",8,R)])])])):(0,o.kq)("",!0),(0,o.Wm)(I,{result:r.tooltip.result,event:r.tooltip.event},null,8,["result","event"]),(0,o.Wm)(A)],64)}const S=e=>((0,o.dD)("data-v-a4b3d200"),e=e(),(0,o.Cn)(),e),D={id:"social"},I=S((()=>(0,o._)("a",{href:"https://github.com/TwiN/gatus",target:"_blank",title:"Gatus on GitHub"},[(0,o._)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"32",height:"32",viewBox:"0 0 16 16",class:"hover:scale-110"},[(0,o._)("path",{fill:"gray",d:"M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"})])],-1))),A=[I];function C(e,t,s,n,a,r){return(0,o.wg)(),(0,o.iD)("div",D,A)}var $={name:"Social"},P=s(3744);const E=(0,P.Z)($,[["render",C],["__scopeId","data-v-a4b3d200"]]);var U=E;const L=(0,o._)("div",{class:"tooltip-title"},"Timestamp:",-1),H={id:"tooltip-timestamp"},W=(0,o._)("div",{class:"tooltip-title"},"Response time:",-1),M={id:"tooltip-response-time"},O=(0,o._)("div",{class:"tooltip-title"},"Conditions:",-1),B={id:"tooltip-conditions"},j=(0,o._)("br",null,null,-1),q={key:1,id:"tooltip-errors-container"},z=(0,o._)("div",{class:"tooltip-title"},"Errors:",-1),Y={id:"tooltip-errors"},N=(0,o._)("br",null,null,-1);function Z(e,t,s,n,r,i){return(0,o.wg)(),(0,o.iD)("div",{id:"tooltip",ref:"tooltip",class:(0,a.C_)(r.hidden?"invisible":""),style:(0,a.j5)("top:"+r.top+"px; left:"+r.left+"px")},[s.result?(0,o.WI)(e.$slots,"default",{key:0},(()=>[L,(0,o._)("code",H,(0,a.zw)(e.prettifyTimestamp(s.result.timestamp)),1),W,(0,o._)("code",M,(0,a.zw)((s.result.duration/1e6).toFixed(0))+"ms",1),s.result.conditionResults&&s.result.conditionResults.length?(0,o.WI)(e.$slots,"default",{key:0},(()=>[O,(0,o._)("code",B,[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(s.result.conditionResults,(t=>(0,o.WI)(e.$slots,"default",{key:t},(()=>[(0,o.Uk)((0,a.zw)(t.success?"✓":"X")+" ~ "+(0,a.zw)(t.condition),1),j])))),128))])])):(0,o.kq)("",!0),s.result.errors&&s.result.errors.length?((0,o.wg)(),(0,o.iD)("div",q,[z,(0,o._)("code",Y,[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(s.result.errors,(t=>(0,o.WI)(e.$slots,"default",{key:t},(()=>[(0,o.Uk)(" - "+(0,a.zw)(t),1),N])))),128))])])):(0,o.kq)("",!0)])):(0,o.kq)("",!0)],6)}s(5306);const G={methods:{generatePrettyTimeAgo(e){let t=(new Date).getTime()-new Date(e).getTime();if(t<500)return"now";if(t>2592e5){let e=(t/864e5).toFixed(0);return e+" day"+("1"!==e?"s":"")+" ago"}if(t>36e5){let e=(t/36e5).toFixed(0);return e+" hour"+("1"!==e?"s":"")+" ago"}if(t>6e4){let e=(t/6e4).toFixed(0);return e+" minute"+("1"!==e?"s":"")+" ago"}let s=(t/1e3).toFixed(0);return s+" second"+("1"!==s?"s":"")+" ago"},generatePrettyTimeDifference(e,t){let s=Math.ceil((new Date(e)-new Date(t))/1e3/60);return s+(1===s?" minute":" minutes")},prettifyTimestamp(e){let t=new Date(e),s=t.getFullYear(),n=(t.getMonth()+1<10?"0":"")+(t.getMonth()+1),o=(t.getDate()<10?"0":"")+t.getDate(),a=(t.getHours()<10?"0":"")+t.getHours(),r=(t.getMinutes()<10?"0":"")+t.getMinutes(),i=(t.getSeconds()<10?"0":"")+t.getSeconds();return s+"-"+n+"-"+o+" "+a+":"+r+":"+i}}};var F={name:"Endpoints",props:{event:Event,result:Object},mixins:[G],methods:{htmlEntities(e){return String(e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")},reposition(){if(this.event&&this.event.type)if("mouseenter"===this.event.type){let e=this.event.target.getBoundingClientRect().y+30,t=this.event.target.getBoundingClientRect().x,s=this.$refs.tooltip.getBoundingClientRect();t+window.scrollX+s.width+50>document.body.getBoundingClientRect().width&&(t=this.event.target.getBoundingClientRect().x-s.width+this.event.target.getBoundingClientRect().width,t<0&&(t+=-t)),e+window.scrollY+s.height+50>document.body.getBoundingClientRect().height&&e>=0&&(e=this.event.target.getBoundingClientRect().y-(s.height+10),e<0&&(e=this.event.target.getBoundingClientRect().y+30)),this.top=e,this.left=t}else"mouseleave"===this.event.type&&(this.hidden=!0)}},watch:{event:function(e){e&&e.type&&("mouseenter"===e.type?this.hidden=!1:"mouseleave"===e.type&&(this.hidden=!0))}},updated(){this.reposition()},created(){this.reposition()},data(){return{hidden:!0,top:0,left:0}}};const K=(0,P.Z)(F,[["render",Z]]);var V=K;const J={class:"flex justify-center items-center mx-auto"},X=(0,o._)("img",{class:(0,a.C_)("animate-spin opacity-60 rounded-full"),src:r,alt:"Gatus logo"},null,-1),Q=[X];function ee(e,t,s,n,a,r){return(0,o.wg)(),(0,o.iD)("div",J,Q)}var te={};const se=(0,P.Z)(te,[["render",ee]]);var ne=se,oe={name:"App",components:{Loading:ne,Social:U,Tooltip:V},methods:{fetchConfig(){fetch(`${bs}/api/v1/config`,{credentials:"include"}).then((e=>{this.retrievedConfig=!0,200===e.status&&e.json().then((e=>{this.config=e}))}))},showTooltip(e,t){this.tooltip={result:e,event:t}}},computed:{logo(){return window.config&&window.config.logo&&"{{ .UI.Logo }}"!==window.config.logo?window.config.logo:""},header(){return window.config&&window.config.header&&"{{ .UI.Header }}"!==window.config.header?window.config.header:"Health Status"},link(){return window.config&&window.config.link&&"{{ .UI.Link }}"!==window.config.link?window.config.link:null},buttons(){return window.config&&window.config.buttons?window.config.buttons:[]}},data(){return{error:"",retrievedConfig:!1,config:{oidc:!1,authenticated:!0},tooltip:{},SERVER_URL:bs}},created(){this.fetchConfig()}};const ae=(0,P.Z)(oe,[["render",_]]);var re=ae,ie=s(2119);function le(e,t,s,a,r,i){const l=(0,o.up)("Loading"),d=(0,o.up)("Endpoints"),g=(0,o.up)("Pagination"),u=(0,o.up)("Settings");return(0,o.wg)(),(0,o.iD)(o.HY,null,[r.retrievedData?(0,o.kq)("",!0):((0,o.wg)(),(0,o.j4)(l,{key:0,class:"h-64 w-64 px-4 my-24"})),(0,o.WI)(e.$slots,"default",{},(()=>[(0,o.wy)((0,o.Wm)(d,{endpointStatuses:r.endpointStatuses,showStatusOnHover:!0,onShowTooltip:i.showTooltip,onToggleShowAverageResponseTime:i.toggleShowAverageResponseTime,showAverageResponseTime:r.showAverageResponseTime},null,8,["endpointStatuses","onShowTooltip","onToggleShowAverageResponseTime","showAverageResponseTime"]),[[n.F8,r.retrievedData]]),(0,o.wy)((0,o.Wm)(g,{onPage:i.changePage},null,8,["onPage"]),[[n.F8,r.retrievedData]])])),(0,o.Wm)(u,{onRefreshData:i.fetchData},null,8,["onRefreshData"])],64)}s(3948);const de={id:"settings",class:"flex bg-gray-200 border-gray-300 rounded border shadow dark:text-gray-200 dark:bg-gray-800 dark:border-gray-500"},ge={class:"text-xs text-gray-600 rounded-xl py-1.5 px-1.5 dark:text-gray-200"},ue=["selected"],he=["selected"],ce=["selected"],pe=["selected"],me=["selected"],ve=["selected"];function fe(e,t,s,n,a,r){const i=(0,o.up)("ArrowPathIcon"),l=(0,o.up)("SunIcon"),d=(0,o.up)("MoonIcon");return(0,o.wg)(),(0,o.iD)("div",de,[(0,o._)("div",ge,[(0,o.Wm)(i,{class:"w-3"})]),(0,o._)("select",{class:"text-center text-gray-500 text-xs dark:text-gray-200 dark:bg-gray-800 border-r border-l border-gray-300 dark:border-gray-500 pl-1",id:"refresh-rate",ref:"refreshInterval",onChange:t[0]||(t[0]=(...e)=>r.handleChangeRefreshInterval&&r.handleChangeRefreshInterval(...e))},[(0,o._)("option",{value:"10",selected:10===a.refreshInterval},"10s",8,ue),(0,o._)("option",{value:"30",selected:30===a.refreshInterval},"30s",8,he),(0,o._)("option",{value:"60",selected:60===a.refreshInterval},"1m",8,ce),(0,o._)("option",{value:"120",selected:120===a.refreshInterval},"2m",8,pe),(0,o._)("option",{value:"300",selected:300===a.refreshInterval},"5m",8,me),(0,o._)("option",{value:"600",selected:600===a.refreshInterval},"10m",8,ve)],544),(0,o._)("button",{onClick:t[1]||(t[1]=(...e)=>r.toggleDarkMode&&r.toggleDarkMode(...e)),class:"text-xs p-1"},[a.darkMode?(0,o.WI)(e.$slots,"default",{key:0},(()=>[(0,o.Wm)(l,{class:"w-4"})])):(0,o.WI)(e.$slots,"default",{key:1},(()=>[(0,o.Wm)(d,{class:"w-4 text-gray-500"})]))])])}var we=s(6758),xe=s(4913),ye=s(7886);function ke(){var e;const t=null===(e=document.cookie.match(/theme=(dark|light);?/))||void 0===e?void 0:e[1];return"dark"===t||!t&&(window.matchMedia("(prefers-color-scheme: dark)").matches||document.documentElement.classList.contains("dark"))}var Te={name:"Settings",components:{ArrowPathIcon:ye.Z,MoonIcon:we.Z,SunIcon:xe.Z},props:{},methods:{setRefreshInterval(e){localStorage.setItem("gatus:refresh-interval",e);let t=this;this.refreshIntervalHandler=setInterval((function(){t.refreshData()}),1e3*e)},refreshData(){this.$emit("refreshData")},handleChangeRefreshInterval(){this.refreshData(),clearInterval(this.refreshIntervalHandler),this.setRefreshInterval(this.$refs.refreshInterval.value)},toggleDarkMode(){ke()?document.cookie="theme=light; path=/; max-age=31536000; samesite=strict":document.cookie="theme=dark; path=/; max-age=31536000; samesite=strict",this.applyTheme()},applyTheme(){ke()?(this.darkMode=!0,document.documentElement.classList.add("dark")):(this.darkMode=!1,document.documentElement.classList.remove("dark"))}},created(){10!==this.refreshInterval&&30!==this.refreshInterval&&60!==this.refreshInterval&&120!==this.refreshInterval&&300!==this.refreshInterval&&600!==this.refreshInterval&&(this.refreshInterval=300),this.setRefreshInterval(this.refreshInterval),this.applyTheme()},unmounted(){clearInterval(this.refreshIntervalHandler)},data(){return{refreshInterval:localStorage.getItem("gatus:refresh-interval")<10?300:parseInt(localStorage.getItem("gatus:refresh-interval")),refreshIntervalHandler:0,darkMode:ke()}}};const be=(0,P.Z)(Te,[["render",fe]]);var Re=be;const _e={id:"results"};function Se(e,t,s,n,a,r){const i=(0,o.up)("EndpointGroup");return(0,o.wg)(),(0,o.iD)("div",_e,[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(a.endpointGroups,(t=>(0,o.WI)(e.$slots,"default",{key:t},(()=>[(0,o.Wm)(i,{endpoints:t.endpoints,name:t.name,onShowTooltip:r.showTooltip,onToggleShowAverageResponseTime:r.toggleShowAverageResponseTime,showAverageResponseTime:s.showAverageResponseTime},null,8,["endpoints","name","onShowTooltip","onToggleShowAverageResponseTime","showAverageResponseTime"])])))),128))])}const De={class:"font-mono text-gray-400 text-xl font-medium pb-2 px-3 dark:text-gray-200 dark:hover:text-gray-500 dark:border-gray-500"},Ie={class:"endpoint-group-arrow mr-2"},Ae={key:0,class:"rounded-xl bg-red-600 text-white px-2 font-bold leading-6 float-right h-6 text-center hover:scale-110 text-sm",title:"Partial Outage"},Ce={key:1,class:"float-right text-green-600 w-7 hover:scale-110",title:"Operational"};function $e(e,t,s,n,r,i){const l=(0,o.up)("CheckCircleIcon"),d=(0,o.up)("Endpoint");return(0,o.wg)(),(0,o.iD)("div",{class:(0,a.C_)(0===s.endpoints.length?"mt-3":"mt-4")},["undefined"!==s.name?(0,o.WI)(e.$slots,"default",{key:0},(()=>[(0,o._)("div",{class:"endpoint-group pt-2 border dark:bg-gray-800 dark:border-gray-500",onClick:t[0]||(t[0]=(...e)=>i.toggleGroup&&i.toggleGroup(...e))},[(0,o._)("h5",De,[(0,o._)("span",Ie,(0,a.zw)(r.collapsed?"▼":"▲"),1),(0,o.Uk)(" "+(0,a.zw)(s.name)+" ",1),r.unhealthyCount?((0,o.wg)(),(0,o.iD)("span",Ae,(0,a.zw)(r.unhealthyCount),1)):((0,o.wg)(),(0,o.iD)("span",Ce,[(0,o.Wm)(l)]))])])])):(0,o.kq)("",!0),r.collapsed?(0,o.kq)("",!0):((0,o.wg)(),(0,o.iD)("div",{key:1,class:(0,a.C_)("undefined"===s.name?"":"endpoint-group-content")},[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(s.endpoints,((t,n)=>(0,o.WI)(e.$slots,"default",{key:n},(()=>[(0,o.Wm)(d,{data:t,maximumNumberOfResults:20,onShowTooltip:i.showTooltip,onToggleShowAverageResponseTime:i.toggleShowAverageResponseTime,showAverageResponseTime:s.showAverageResponseTime},null,8,["data","onShowTooltip","onToggleShowAverageResponseTime","showAverageResponseTime"])])))),128))],2))],2)}const Pe={key:0,class:"endpoint px-3 py-3 border-l border-r border-t rounded-none hover:bg-gray-100 dark:hover:bg-gray-700 dark:border-gray-500"},Ee={class:"flex flex-wrap mb-2"},Ue={class:"w-3/4"},Le={key:0,class:"text-gray-500 font-light"},He={class:"w-1/4 text-right"},We=["title"],Me={class:"status-over-time flex flex-row"},Oe=["onMouseenter"],Be=["onMouseenter"],je={class:"flex flex-wrap status-time-ago"},qe={class:"w-1/2"},ze={class:"w-1/2 text-right"},Ye=(0,o._)("div",{class:"w-1/2"},"   ",-1);function Ne(e,t,s,n,r,i){const l=(0,o.up)("router-link");return s.data?((0,o.wg)(),(0,o.iD)("div",Pe,[(0,o._)("div",Ee,[(0,o._)("div",Ue,[(0,o.Wm)(l,{to:i.generatePath(),class:"font-bold hover:text-blue-800 hover:underline dark:hover:text-blue-400",title:"View detailed endpoint health"},{default:(0,o.w5)((()=>[(0,o.Uk)((0,a.zw)(s.data.name),1)])),_:1},8,["to"]),s.data.results&&s.data.results.length&&s.data.results[s.data.results.length-1].hostname?((0,o.wg)(),(0,o.iD)("span",Le," | "+(0,a.zw)(s.data.results[s.data.results.length-1].hostname),1)):(0,o.kq)("",!0)]),(0,o._)("div",He,[s.data.results&&s.data.results.length?((0,o.wg)(),(0,o.iD)("span",{key:0,class:"font-light overflow-x-hidden cursor-pointer select-none hover:text-gray-500",onClick:t[0]||(t[0]=(...e)=>i.toggleShowAverageResponseTime&&i.toggleShowAverageResponseTime(...e)),title:s.showAverageResponseTime?"Average response time":"Minimum and maximum response time"},[s.showAverageResponseTime?(0,o.WI)(e.$slots,"default",{key:0},(()=>[(0,o.Uk)(" ~"+(0,a.zw)(r.averageResponseTime)+"ms ",1)])):(0,o.WI)(e.$slots,"default",{key:1},(()=>[(0,o.Uk)((0,a.zw)(r.minResponseTime===r.maxResponseTime?r.minResponseTime:r.minResponseTime+"-"+r.maxResponseTime)+"ms ",1)]))],8,We)):(0,o.kq)("",!0)])]),(0,o._)("div",null,[(0,o._)("div",Me,[s.data.results&&s.data.results.length?(0,o.WI)(e.$slots,"default",{key:0},(()=>[s.data.results.length[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(s.maximumNumberOfResults-s.data.results.length,(e=>((0,o.wg)(),(0,o.iD)("span",{key:e,class:"status rounded border border-dashed border-gray-400"}," ")))),128))])):(0,o.kq)("",!0),((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(s.data.results,(s=>(0,o.WI)(e.$slots,"default",{key:s},(()=>[s.success?((0,o.wg)(),(0,o.iD)("span",{key:0,class:"status status-success rounded bg-success",onMouseenter:e=>i.showTooltip(s,e),onMouseleave:t[1]||(t[1]=e=>i.showTooltip(null,e))},null,40,Oe)):((0,o.wg)(),(0,o.iD)("span",{key:1,class:"status status-failure rounded bg-red-600",onMouseenter:e=>i.showTooltip(s,e),onMouseleave:t[2]||(t[2]=e=>i.showTooltip(null,e))},null,40,Be))])))),128))])):(0,o.WI)(e.$slots,"default",{key:1},(()=>[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(s.maximumNumberOfResults,(e=>((0,o.wg)(),(0,o.iD)("span",{key:e,class:"status rounded border border-dashed border-gray-400"}," ")))),128))]))])]),(0,o._)("div",je,[s.data.results&&s.data.results.length?(0,o.WI)(e.$slots,"default",{key:0},(()=>[(0,o._)("div",qe,(0,a.zw)(e.generatePrettyTimeAgo(s.data.results[0].timestamp)),1),(0,o._)("div",ze,(0,a.zw)(e.generatePrettyTimeAgo(s.data.results[s.data.results.length-1].timestamp)),1)])):(0,o.WI)(e.$slots,"default",{key:1},(()=>[Ye]))])])):(0,o.kq)("",!0)}var Ze={name:"Endpoint",props:{maximumNumberOfResults:Number,data:Object,showAverageResponseTime:Boolean},emits:["showTooltip","toggleShowAverageResponseTime"],mixins:[G],methods:{updateMinAndMaxResponseTimes(){let e=null,t=null,s=0;for(let n in this.data.results){const o=parseInt((this.data.results[n].duration/1e6).toFixed(0));s+=o,(null==e||e>o)&&(e=o),(null==t||t0&&(this.endpoints[t].results[this.endpoints[t].results.length-1].success||e++);this.unhealthyCount=e},toggleGroup(){this.collapsed=!this.collapsed,localStorage.setItem(`gatus:endpoint-group:${this.name}:collapsed`,this.collapsed)},showTooltip(e,t){this.$emit("showTooltip",e,t)},toggleShowAverageResponseTime(){this.$emit("toggleShowAverageResponseTime")}},watch:{endpoints:function(){this.healthCheck()}},created(){this.healthCheck()},data(){return{unhealthyCount:0,collapsed:"true"===localStorage.getItem(`gatus:endpoint-group:${this.name}:collapsed`)}}};const Je=(0,P.Z)(Ve,[["render",$e]]);var Xe=Je,Qe={name:"Endpoints",components:{EndpointGroup:Xe},props:{showStatusOnHover:Boolean,endpointStatuses:Object,showAverageResponseTime:Boolean},emits:["showTooltip","toggleShowAverageResponseTime"],methods:{process(){let e={};for(let s in this.endpointStatuses){let t=this.endpointStatuses[s];e[t.group]&&0!==e[t.group].length||(e[t.group]=[]),e[t.group].push(t)}let t=[];for(let s in e)"undefined"!==s&&t.push({name:s,endpoints:e[s]});e["undefined"]&&t.push({name:"undefined",endpoints:e["undefined"]}),this.endpointGroups=t},showTooltip(e,t){this.$emit("showTooltip",e,t)},toggleShowAverageResponseTime(){this.$emit("toggleShowAverageResponseTime")}},watch:{endpointStatuses:function(){this.process()}},data(){return{userClickedStatus:!1,endpointGroups:[]}}};const et=(0,P.Z)(Qe,[["render",Se]]);var tt=et;const st={class:"mt-3 flex"},nt={class:"flex-1"},ot={class:"flex-1 text-right"};function at(e,t,s,n,a,r){return(0,o.wg)(),(0,o.iD)("div",st,[(0,o._)("div",nt,[a.currentPage<5?((0,o.wg)(),(0,o.iD)("button",{key:0,onClick:t[0]||(t[0]=(...e)=>r.nextPage&&r.nextPage(...e)),class:"bg-gray-100 hover:bg-gray-200 text-gray-500 border border-gray-200 px-2 rounded font-mono dark:bg-gray-700 dark:text-gray-200 dark:border-gray-500 dark:hover:bg-gray-600"},"<")):(0,o.kq)("",!0)]),(0,o._)("div",ot,[a.currentPage>1?((0,o.wg)(),(0,o.iD)("button",{key:0,onClick:t[1]||(t[1]=(...e)=>r.previousPage&&r.previousPage(...e)),class:"bg-gray-100 hover:bg-gray-200 text-gray-500 border border-gray-200 px-2 rounded font-mono dark:bg-gray-700 dark:text-gray-200 dark:border-gray-500 dark:hover:bg-gray-600"},">")):(0,o.kq)("",!0)])])}var rt={name:"Pagination",components:{},emits:["page"],methods:{nextPage(){this.currentPage++,this.$emit("page",this.currentPage)},previousPage(){this.currentPage--,this.$emit("page",this.currentPage)}},data(){return{currentPage:1}}};const it=(0,P.Z)(rt,[["render",at]]);var lt=it,dt={name:"Home",components:{Loading:ne,Pagination:lt,Endpoints:tt,Settings:Re},emits:["showTooltip","toggleShowAverageResponseTime"],methods:{fetchData(){fetch(`${bs}/api/v1/endpoints/statuses?page=${this.currentPage}`,{credentials:"include"}).then((e=>{this.retrievedData=!0,200===e.status?e.json().then((e=>{JSON.stringify(this.endpointStatuses)!==JSON.stringify(e)&&(this.endpointStatuses=e)})):e.text().then((e=>{console.log(`[Home][fetchData] Error: ${e}`)}))}))},changePage(e){this.retrievedData=!1,this.currentPage=e,this.fetchData()},showTooltip(e,t){this.$emit("showTooltip",e,t)},toggleShowAverageResponseTime(){this.showAverageResponseTime=!this.showAverageResponseTime}},data(){return{endpointStatuses:[],currentPage:1,showAverageResponseTime:!0,retrievedData:!1}},created(){this.retrievedData=!1,this.fetchData()}};const gt=(0,P.Z)(dt,[["render",le]]);var ut=gt;const ht=e=>((0,o.dD)("data-v-62613fe4"),e=e(),(0,o.Cn)(),e),ct=(0,o.Uk)(" ← "),pt=ht((()=>(0,o._)("h1",{class:"text-xl xl:text-3xl font-mono text-gray-400"},"RECENT CHECKS",-1))),mt=ht((()=>(0,o._)("hr",{class:"mb-4"},null,-1))),vt={key:1,class:"mt-12"},ft=ht((()=>(0,o._)("h1",{class:"text-xl xl:text-3xl font-mono text-gray-400"},"UPTIME",-1))),wt=ht((()=>(0,o._)("hr",null,null,-1))),xt={class:"flex space-x-4 text-center text-2xl mt-6 relative bottom-2 mb-10"},yt={class:"flex-1"},kt=ht((()=>(0,o._)("h2",{class:"text-sm text-gray-400 mb-1"},"Last 30 days",-1))),Tt=["src"],bt={class:"flex-1"},Rt=ht((()=>(0,o._)("h2",{class:"text-sm text-gray-400 mb-1"},"Last 7 days",-1))),_t=["src"],St={class:"flex-1"},Dt=ht((()=>(0,o._)("h2",{class:"text-sm text-gray-400 mb-1"},"Last 24 hours",-1))),It=["src"],At={class:"flex-1"},Ct=ht((()=>(0,o._)("h2",{class:"text-sm text-gray-400 mb-1"},"Last hour",-1))),$t=["src"],Pt={key:2,class:"mt-12"},Et={class:"flex items-center justify-between"},Ut=ht((()=>(0,o._)("h1",{class:"text-xl xl:text-3xl font-mono text-gray-400"},"RESPONSE TIME",-1))),Lt=ht((()=>(0,o._)("option",{value:"24h"},"24 hours",-1))),Ht=ht((()=>(0,o._)("option",{value:"7d"},"7 days",-1))),Wt=ht((()=>(0,o._)("option",{value:"30d"},"30 days",-1))),Mt=[Lt,Ht,Wt],Ot=["src"],Bt={class:"flex space-x-4 text-center text-2xl mt-6 relative bottom-2 mb-10"},jt={class:"flex-1"},qt=ht((()=>(0,o._)("h2",{class:"text-sm text-gray-400 mb-1"},"Last 30 days",-1))),zt=["src"],Yt={class:"flex-1"},Nt=ht((()=>(0,o._)("h2",{class:"text-sm text-gray-400 mb-1"},"Last 7 days",-1))),Zt=["src"],Gt={class:"flex-1"},Ft=ht((()=>(0,o._)("h2",{class:"text-sm text-gray-400 mb-1"},"Last 24 hours",-1))),Kt=["src"],Vt={class:"flex-1"},Jt=ht((()=>(0,o._)("h2",{class:"text-sm text-gray-400 mb-1"},"Last hour",-1))),Xt=["src"],Qt={key:3},es=ht((()=>(0,o._)("h1",{class:"text-xl xl:text-3xl font-mono text-gray-400 mt-4"},"CURRENT HEALTH",-1))),ts=ht((()=>(0,o._)("hr",null,null,-1))),ss={class:"flex space-x-4 text-center text-2xl mt-6 relative bottom-2 mb-10"},ns={class:"flex-1"},os=["src"],as={key:4},rs=ht((()=>(0,o._)("h1",{class:"text-xl xl:text-3xl font-mono text-gray-400 mt-4"},"EVENTS",-1))),is=ht((()=>(0,o._)("hr",null,null,-1))),ls={role:"list",class:"px-0 xl:px-24 divide-y divide-gray-200 dark:divide-gray-600"},ds={class:"text-sm sm:text-lg"},gs={class:"flex mt-1 text-xs sm:text-sm text-gray-400"},us={class:"flex-2 text-left pl-12"},hs={class:"flex-1 text-right"};function cs(e,t,s,r,i,l){const d=(0,o.up)("router-link"),g=(0,o.up)("Endpoint"),u=(0,o.up)("Pagination"),h=(0,o.up)("ArrowUpCircleIcon"),c=(0,o.up)("ArrowDownCircleIcon"),p=(0,o.up)("PlayCircleIcon"),m=(0,o.up)("Settings");return(0,o.wg)(),(0,o.iD)(o.HY,null,[(0,o.Wm)(d,{to:"../",class:"absolute top-2 left-5 inline-block px-2 pb-0.5 text-sm text-black bg-gray-100 rounded hover:bg-gray-200 focus:outline-none border border-gray-200 dark:bg-gray-700 dark:text-gray-200 dark:border-gray-500 dark:hover:bg-gray-600"},{default:(0,o.w5)((()=>[ct])),_:1}),(0,o._)("div",null,[i.endpointStatus?(0,o.WI)(e.$slots,"default",{key:0},(()=>[pt,mt,(0,o.Wm)(g,{data:i.endpointStatus,maximumNumberOfResults:20,onShowTooltip:l.showTooltip,onToggleShowAverageResponseTime:l.toggleShowAverageResponseTime,showAverageResponseTime:i.showAverageResponseTime},null,8,["data","onShowTooltip","onToggleShowAverageResponseTime","showAverageResponseTime"]),(0,o.Wm)(u,{onPage:l.changePage},null,8,["onPage"])]),!0):(0,o.kq)("",!0),i.endpointStatus&&i.endpointStatus.key?((0,o.wg)(),(0,o.iD)("div",vt,[ft,wt,(0,o._)("div",xt,[(0,o._)("div",yt,[kt,(0,o._)("img",{src:l.generateUptimeBadgeImageURL("30d"),alt:"30d uptime badge",class:"mx-auto"},null,8,Tt)]),(0,o._)("div",bt,[Rt,(0,o._)("img",{src:l.generateUptimeBadgeImageURL("7d"),alt:"7d uptime badge",class:"mx-auto"},null,8,_t)]),(0,o._)("div",St,[Dt,(0,o._)("img",{src:l.generateUptimeBadgeImageURL("24h"),alt:"24h uptime badge",class:"mx-auto"},null,8,It)]),(0,o._)("div",At,[Ct,(0,o._)("img",{src:l.generateUptimeBadgeImageURL("1h"),alt:"1h uptime badge",class:"mx-auto"},null,8,$t)])])])):(0,o.kq)("",!0),i.endpointStatus&&i.endpointStatus.key&&i.showResponseTimeChartAndBadges?((0,o.wg)(),(0,o.iD)("div",Pt,[(0,o._)("div",Et,[Ut,(0,o.wy)((0,o._)("select",{"onUpdate:modelValue":t[0]||(t[0]=e=>i.selectedChartDuration=e),class:"text-sm bg-gray-400 text-white border border-gray-600 rounded-md px-3 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500"},Mt,512),[[n.bM,i.selectedChartDuration]])]),(0,o._)("img",{src:l.generateResponseTimeChartImageURL(i.selectedChartDuration),alt:"response time chart",class:"mt-6"},null,8,Ot),(0,o._)("div",Bt,[(0,o._)("div",jt,[qt,(0,o._)("img",{src:l.generateResponseTimeBadgeImageURL("30d"),alt:"7d response time badge",class:"mx-auto mt-2"},null,8,zt)]),(0,o._)("div",Yt,[Nt,(0,o._)("img",{src:l.generateResponseTimeBadgeImageURL("7d"),alt:"7d response time badge",class:"mx-auto mt-2"},null,8,Zt)]),(0,o._)("div",Gt,[Ft,(0,o._)("img",{src:l.generateResponseTimeBadgeImageURL("24h"),alt:"24h response time badge",class:"mx-auto mt-2"},null,8,Kt)]),(0,o._)("div",Vt,[Jt,(0,o._)("img",{src:l.generateResponseTimeBadgeImageURL("1h"),alt:"1h response time badge",class:"mx-auto mt-2"},null,8,Xt)])])])):(0,o.kq)("",!0),i.endpointStatus&&i.endpointStatus.key?((0,o.wg)(),(0,o.iD)("div",Qt,[es,ts,(0,o._)("div",ss,[(0,o._)("div",ns,[(0,o._)("img",{src:l.generateHealthBadgeImageURL(),alt:"health badge",class:"mx-auto"},null,8,os)])])])):(0,o.kq)("",!0),i.endpointStatus&&i.endpointStatus.key?((0,o.wg)(),(0,o.iD)("div",as,[rs,is,(0,o._)("ul",ls,[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(i.events,(t=>((0,o.wg)(),(0,o.iD)("li",{key:t,class:"p-3 my-4"},[(0,o._)("h2",ds,["HEALTHY"===t.type?((0,o.wg)(),(0,o.j4)(h,{key:0,class:"w-8 inline mr-2 text-green-600"})):"UNHEALTHY"===t.type?((0,o.wg)(),(0,o.j4)(c,{key:1,class:"w-8 inline mr-2 text-red-500"})):"START"===t.type?((0,o.wg)(),(0,o.j4)(p,{key:2,class:"w-8 inline mr-2 text-gray-400 dark:text-gray-100"})):(0,o.kq)("",!0),(0,o.Uk)(" "+(0,a.zw)(t.fancyText),1)]),(0,o._)("div",gs,[(0,o._)("div",us,(0,a.zw)(e.prettifyTimestamp(t.timestamp)),1),(0,o._)("div",hs,(0,a.zw)(t.fancyTimeAgo),1)])])))),128))])])):(0,o.kq)("",!0)]),(0,o.Wm)(m,{onRefreshData:l.fetchData},null,8,["onRefreshData"])],64)}var ps=s(9505),ms=s(7163),vs=s(8585),fs={name:"Details",components:{Pagination:lt,Endpoint:Fe,Settings:Re,ArrowDownCircleIcon:ps.Z,ArrowUpCircleIcon:ms.Z,PlayCircleIcon:vs.Z},emits:["showTooltip"],mixins:[G],methods:{fetchData(){fetch(`${this.serverUrl}/api/v1/endpoints/${this.$route.params.key}/statuses?page=${this.currentPage}`,{credentials:"include"}).then((e=>{200===e.status?e.json().then((e=>{if(JSON.stringify(this.endpointStatus)!==JSON.stringify(e)){this.endpointStatus=e;let t=[];for(let s=e.events.length-1;s>=0;s--){let n=e.events[s];if(s===e.events.length-1)"UNHEALTHY"===n.type?n.fancyText="Endpoint is unhealthy":"HEALTHY"===n.type?n.fancyText="Endpoint is healthy":"START"===n.type&&(n.fancyText="Monitoring started");else{let t=e.events[s+1];"HEALTHY"===n.type?n.fancyText="Endpoint became healthy":"UNHEALTHY"===n.type?n.fancyText=t?"Endpoint was unhealthy for "+this.generatePrettyTimeDifference(t.timestamp,n.timestamp):"Endpoint became unhealthy":"START"===n.type&&(n.fancyText="Monitoring started")}n.fancyTimeAgo=this.generatePrettyTimeAgo(n.timestamp),t.push(n)}this.events=t;for(let s=0;s0){this.showResponseTimeChartAndBadges=!0;break}}})):e.text().then((e=>{console.log(`[Details][fetchData] Error: ${e}`)}))}))},generateHealthBadgeImageURL(){return`${this.serverUrl}/api/v1/endpoints/${this.endpointStatus.key}/health/badge.svg`},generateUptimeBadgeImageURL(e){return`${this.serverUrl}/api/v1/endpoints/${this.endpointStatus.key}/uptimes/${e}/badge.svg`},generateResponseTimeBadgeImageURL(e){return`${this.serverUrl}/api/v1/endpoints/${this.endpointStatus.key}/response-times/${e}/badge.svg`},generateResponseTimeChartImageURL(e){return`${this.serverUrl}/api/v1/endpoints/${this.endpointStatus.key}/response-times/${e}/chart.svg`},changePage(e){this.currentPage=e,this.fetchData()},showTooltip(e,t){this.$emit("showTooltip",e,t)},toggleShowAverageResponseTime(){this.showAverageResponseTime=!this.showAverageResponseTime}},data(){return{endpointStatus:{},events:[],hourlyAverageResponseTime:{},selectedChartDuration:"24h",serverUrl:"."===bs?"..":bs,currentPage:1,showAverageResponseTime:!0,showResponseTimeChartAndBadges:!1,chartLabels:[],chartValues:[]}},created(){this.fetchData()}};const ws=(0,P.Z)(fs,[["render",cs],["__scopeId","data-v-62613fe4"]]);var xs=ws;const ys=[{path:"/",name:"Home",component:ut},{path:"/endpoints/:key",name:"Details",component:xs}],ks=(0,ie.p7)({history:(0,ie.PO)("/"),routes:ys});var Ts=ks;const bs="";(0,n.ri)(re).use(Ts).mount("#app")}},t={};function s(n){var o=t[n];if(void 0!==o)return o.exports;var a=t[n]={exports:{}};return e[n](a,a.exports,s),a.exports}s.m=e,function(){var e=[];s.O=function(t,n,o,a){if(!n){var r=1/0;for(g=0;g=a)&&Object.keys(s.O).every((function(e){return s.O[e](n[l])}))?n.splice(l--,1):(i=!1,a0&&e[g-1][2]>a;g--)e[g]=e[g-1];e[g]=[n,o,a]}}(),function(){s.d=function(e,t){for(var n in t)s.o(t,n)&&!s.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})}}(),function(){s.g=function(){if("object"===typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"===typeof window)return window}}()}(),function(){s.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)}}(),function(){s.p="/"}(),function(){var e={143:0};s.O.j=function(t){return 0===e[t]};var t=function(t,n){var o,a,r=n[0],i=n[1],l=n[2],d=0;if(r.some((function(t){return 0!==e[t]}))){for(o in i)s.o(i,o)&&(s.m[o]=i[o]);if(l)var g=l(s)}for(t&&t(n);d{const t=new Set(e);return t.w=0,t.n=0,t},u=e=>(e.w&h)>0,l=e=>(e.n&h)>0,a=({deps:e})=>{if(e.length)for(let t=0;t{const{deps:t}=e;if(t.length){let n=0;for(let r=0;r{("length"===t||t>=o)&&l.push(e)}));else switch(void 0!==n&&l.push(u.get(n)),t){case"add":(0,r.kJ)(e)?(0,r.S0)(n)&&l.push(u.get("length")):(l.push(u.get(m)),(0,r._N)(e)&&l.push(u.get(y)));break;case"delete":(0,r.kJ)(e)||(l.push(u.get(m)),(0,r._N)(e)&&l.push(u.get(y)));break;case"set":(0,r._N)(e)&&l.push(u.get(m));break}if(1===l.length)l[0]&&A(l[0]);else{const e=[];for(const t of l)t&&e.push(...t);A(c(e))}}function A(e,t){const n=(0,r.kJ)(e)?e:[...e];for(const r of n)r.computed&&R(r,t);for(const r of n)r.computed||R(r,t)}function R(e,t){(e!==g||e.allowRecurse)&&(e.scheduler?e.scheduler():e.run())}const I=(0,r.fY)("__proto__,__v_isRef,__isVue"),P=new Set(Object.getOwnPropertyNames(Symbol).filter((e=>"arguments"!==e&&"caller"!==e)).map((e=>Symbol[e])).filter(r.yk)),T=$(),j=$(!1,!0),F=$(!0),M=L();function L(){const e={};return["includes","indexOf","lastIndexOf"].forEach((t=>{e[t]=function(...e){const n=Ae(this);for(let t=0,o=this.length;t{e[t]=function(...e){k();const n=Ae(this)[t].apply(this,e);return S(),n}})),e}function $(e=!1,t=!1){return function(n,o,i){if("__v_isReactive"===o)return!e;if("__v_isReadonly"===o)return e;if("__v_isShallow"===o)return t;if("__v_raw"===o&&i===(e?t?me:ge:t?ve:he).get(n))return n;const s=(0,r.kJ)(n);if(!e&&s&&(0,r.RI)(M,o))return Reflect.get(M,o,i);const c=Reflect.get(n,o,i);return((0,r.yk)(o)?P.has(o):I(o))?c:(e||C(n,"get",o),t?c:Fe(c)?s&&(0,r.S0)(o)?c:c.value:(0,r.Kn)(c)?e?we(c):_e(c):c)}}const U=D(),N=D(!0);function D(e=!1){return function(t,n,o,i){let s=t[n];if(Ce(s)&&Fe(s)&&!Fe(o))return!1;if(!e&&!Ce(o)&&(Oe(o)||(o=Ae(o),s=Ae(s)),!(0,r.kJ)(t)&&Fe(s)&&!Fe(o)))return s.value=o,!0;const c=(0,r.kJ)(t)&&(0,r.S0)(n)?Number(n)e,W=e=>Reflect.getPrototypeOf(e);function K(e,t,n=!1,r=!1){e=e["__v_raw"];const o=Ae(e),i=Ae(t);n||(t!==i&&C(o,"get",t),C(o,"get",i));const{has:s}=W(o),c=r?H:n?Pe:Ie;return s.call(o,t)?c(e.get(t)):s.call(o,i)?c(e.get(i)):void(e!==o&&e.get(t))}function Z(e,t=!1){const n=this["__v_raw"],r=Ae(n),o=Ae(e);return t||(e!==o&&C(r,"has",e),C(r,"has",o)),e===o?n.has(e):n.has(e)||n.has(o)}function X(e,t=!1){return e=e["__v_raw"],!t&&C(Ae(e),"iterate",m),Reflect.get(e,"size",e)}function Y(e){e=Ae(e);const t=Ae(this),n=W(t),r=n.has.call(t,e);return r||(t.add(e),E(t,"add",e,e)),this}function Q(e,t){t=Ae(t);const n=Ae(this),{has:o,get:i}=W(n);let s=o.call(n,e);s||(e=Ae(e),s=o.call(n,e));const c=i.call(n,e);return n.set(e,t),s?(0,r.aU)(t,c)&&E(n,"set",e,t,c):E(n,"add",e,t),this}function ee(e){const t=Ae(this),{has:n,get:r}=W(t);let o=n.call(t,e);o||(e=Ae(e),o=n.call(t,e));const i=r?r.call(t,e):void 0,s=t.delete(e);return o&&E(t,"delete",e,void 0,i),s}function te(){const e=Ae(this),t=0!==e.size,n=void 0,r=e.clear();return t&&E(e,"clear",void 0,void 0,n),r}function ne(e,t){return function(n,r){const o=this,i=o["__v_raw"],s=Ae(i),c=t?H:e?Pe:Ie;return!e&&C(s,"iterate",m),i.forEach(((e,t)=>n.call(r,c(e),c(t),o)))}}function re(e,t,n){return function(...o){const i=this["__v_raw"],s=Ae(i),c=(0,r._N)(s),u="entries"===e||e===Symbol.iterator&&c,l="keys"===e&&c,a=i[e](...o),f=n?H:t?Pe:Ie;return!t&&C(s,"iterate",l?y:m),{next(){const{value:e,done:t}=a.next();return t?{value:e,done:t}:{value:u?[f(e[0]),f(e[1])]:f(e),done:t}},[Symbol.iterator](){return this}}}}function oe(e){return function(...t){return"delete"!==e&&this}}function ie(){const e={get(e){return K(this,e)},get size(){return X(this)},has:Z,add:Y,set:Q,delete:ee,clear:te,forEach:ne(!1,!1)},t={get(e){return K(this,e,!1,!0)},get size(){return X(this)},has:Z,add:Y,set:Q,delete:ee,clear:te,forEach:ne(!1,!0)},n={get(e){return K(this,e,!0)},get size(){return X(this,!0)},has(e){return Z.call(this,e,!0)},add:oe("add"),set:oe("set"),delete:oe("delete"),clear:oe("clear"),forEach:ne(!0,!1)},r={get(e){return K(this,e,!0,!0)},get size(){return X(this,!0)},has(e){return Z.call(this,e,!0)},add:oe("add"),set:oe("set"),delete:oe("delete"),clear:oe("clear"),forEach:ne(!0,!0)},o=["keys","values","entries",Symbol.iterator];return o.forEach((o=>{e[o]=re(o,!1,!1),n[o]=re(o,!0,!1),t[o]=re(o,!1,!0),r[o]=re(o,!0,!0)})),[e,n,t,r]}const[se,ce,ue,le]=ie();function ae(e,t){const n=t?e?le:ue:e?ce:se;return(t,o,i)=>"__v_isReactive"===o?!e:"__v_isReadonly"===o?e:"__v_raw"===o?t:Reflect.get((0,r.RI)(n,o)&&o in t?n:t,o,i)}const fe={get:ae(!1,!1)},pe={get:ae(!1,!0)},de={get:ae(!0,!1)};const he=new WeakMap,ve=new WeakMap,ge=new WeakMap,me=new WeakMap;function ye(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function be(e){return e["__v_skip"]||!Object.isExtensible(e)?0:ye((0,r.W7)(e))}function _e(e){return Ce(e)?e:ke(e,!1,G,fe,he)}function xe(e){return ke(e,!1,z,pe,ve)}function we(e){return ke(e,!0,q,de,ge)}function ke(e,t,n,o,i){if(!(0,r.Kn)(e))return e;if(e["__v_raw"]&&(!t||!e["__v_isReactive"]))return e;const s=i.get(e);if(s)return s;const c=be(e);if(0===c)return e;const u=new Proxy(e,2===c?o:n);return i.set(e,u),u}function Se(e){return Ce(e)?Se(e["__v_raw"]):!(!e||!e["__v_isReactive"])}function Ce(e){return!(!e||!e["__v_isReadonly"])}function Oe(e){return!(!e||!e["__v_isShallow"])}function Ee(e){return Se(e)||Ce(e)}function Ae(e){const t=e&&e["__v_raw"];return t?Ae(t):e}function Re(e){return(0,r.Nj)(e,"__v_skip",!0),e}const Ie=e=>(0,r.Kn)(e)?_e(e):e,Pe=e=>(0,r.Kn)(e)?we(e):e;function Te(e){x&&g&&(e=Ae(e),O(e.dep||(e.dep=c())))}function je(e,t){e=Ae(e),e.dep&&A(e.dep)}function Fe(e){return!(!e||!0!==e.__v_isRef)}function Me(e){return $e(e,!1)}function Le(e){return $e(e,!0)}function $e(e,t){return Fe(e)?e:new Ue(e,t)}class Ue{constructor(e,t){this.__v_isShallow=t,this.dep=void 0,this.__v_isRef=!0,this._rawValue=t?e:Ae(e),this._value=t?e:Ie(e)}get value(){return Te(this),this._value}set value(e){e=this.__v_isShallow?e:Ae(e),(0,r.aU)(e,this._rawValue)&&(this._rawValue=e,this._value=this.__v_isShallow?e:Ie(e),je(this,e))}}function Ne(e){return Fe(e)?e.value:e}const De={get:(e,t,n)=>Ne(Reflect.get(e,t,n)),set:(e,t,n,r)=>{const o=e[t];return Fe(o)&&!Fe(n)?(o.value=n,!0):Reflect.set(e,t,n,r)}};function Je(e){return Se(e)?e:new Proxy(e,De)}class Be{constructor(e,t,n,r){this._setter=t,this.dep=void 0,this.__v_isRef=!0,this._dirty=!0,this.effect=new b(e,(()=>{this._dirty||(this._dirty=!0,je(this))})),this.effect.computed=this,this.effect.active=this._cacheable=!r,this["__v_isReadonly"]=n}get value(){const e=Ae(this);return Te(e),!e._dirty&&e._cacheable||(e._dirty=!1,e._value=e.effect.run()),e._value}set value(e){this._setter(e)}}function Ve(e,t,n=!1){let o,i;const s=(0,r.mf)(e);s?(o=e,i=r.dG):(o=e.get,i=e.set);const c=new Be(o,i,s||!i,n);return c}},6252:function(e,t,n){"use strict";n.d(t,{$d:function(){return s},Cn:function(){return J},FN:function(){return _n},Fl:function(){return Ln},HY:function(){return Nt},JJ:function(){return X},Ko:function(){return qe},LL:function(){return Be},P$:function(){return ue},Q6:function(){return he},U2:function(){return ae},Uk:function(){return ln},Us:function(){return jt},WI:function(){return ze},Wm:function(){return on},Y3:function(){return w},Y8:function(){return ie},YP:function(){return ee},_:function(){return rn},aZ:function(){return ve},dD:function(){return D},f3:function(){return Y},h:function(){return $n},iD:function(){return Zt},ic:function(){return Ie},j4:function(){return Xt},kq:function(){return an},nK:function(){return de},up:function(){return De},w5:function(){return B},wg:function(){return qt},wy:function(){return $e}});var r=n(2262),o=n(3577);function i(e,t,n,r){let o;try{o=r?e(...r):e()}catch(i){c(i,t,n)}return o}function s(e,t,n,r){if((0,o.mf)(e)){const s=i(e,t,n,r);return s&&(0,o.tI)(s)&&s.catch((e=>{c(e,t,n)})),s}const u=[];for(let o=0;o>>1,o=T(f[r]);op&&f.splice(t,1)}function E(e,t,n,r){(0,o.kJ)(e)?n.push(...e):t&&t.includes(e,e.allowRecurse?r+1:r)||n.push(e),C()}function A(e){E(e,h,d,v)}function R(e){E(e,m,g,y)}function I(e,t=null){if(d.length){for(x=t,h=[...new Set(d)],d.length=0,v=0;vT(e)-T(t))),y=0;ynull==e.id?1/0:e.id;function j(e){a=!1,l=!0,I(e),f.sort(((e,t)=>T(e)-T(t)));o.dG;try{for(p=0;pe.trim()))),t&&(i=n.map(o.He))}let l;let a=r[l=(0,o.hR)(t)]||r[l=(0,o.hR)((0,o._A)(t))];!a&&c&&(a=r[l=(0,o.hR)((0,o.rs)(t))]),a&&s(a,e,6,i);const f=r[l+"Once"];if(f){if(e.emitted){if(e.emitted[l])return}else e.emitted={};e.emitted[l]=!0,s(f,e,6,i)}}function M(e,t,n=!1){const r=t.emitsCache,i=r.get(e);if(void 0!==i)return i;const s=e.emits;let c={},u=!1;if(!(0,o.mf)(e)){const r=e=>{const n=M(e,t,!0);n&&(u=!0,(0,o.l7)(c,n))};!n&&t.mixins.length&&t.mixins.forEach(r),e.extends&&r(e.extends),e.mixins&&e.mixins.forEach(r)}return s||u?((0,o.kJ)(s)?s.forEach((e=>c[e]=null)):(0,o.l7)(c,s),r.set(e,c),c):(r.set(e,null),null)}function L(e,t){return!(!e||!(0,o.F7)(t))&&(t=t.slice(2).replace(/Once$/,""),(0,o.RI)(e,t[0].toLowerCase()+t.slice(1))||(0,o.RI)(e,(0,o.rs)(t))||(0,o.RI)(e,t))}let $=null,U=null;function N(e){const t=$;return $=e,U=e&&e.type.__scopeId||null,t}function D(e){U=e}function J(){U=null}function B(e,t=$,n){if(!t)return e;if(e._n)return e;const r=(...n)=>{r._d&&Wt(-1);const o=N(t),i=e(...n);return N(o),r._d&&Wt(1),i};return r._n=!0,r._c=!0,r._d=!0,r}function V(e){const{type:t,vnode:n,proxy:r,withProxy:i,props:s,propsOptions:[u],slots:l,attrs:a,emit:f,render:p,renderCache:d,data:h,setupState:v,ctx:g,inheritAttrs:m}=e;let y,b;const _=N(e);try{if(4&n.shapeFlag){const e=i||r;y=fn(p.call(e,e,d,s,v,h,g)),b=a}else{const e=t;0,y=fn(e.length>1?e(s,{attrs:a,slots:l,emit:f}):e(s,null)),b=t.props?a:G(a)}}catch(w){Vt.length=0,c(w,e,1),y=on(Jt)}let x=y;if(b&&!1!==m){const e=Object.keys(b),{shapeFlag:t}=x;e.length&&7&t&&(u&&e.some(o.tR)&&(b=q(b,u)),x=un(x,b))}return n.dirs&&(x=un(x),x.dirs=x.dirs?x.dirs.concat(n.dirs):n.dirs),n.transition&&(x.transition=n.transition),y=x,N(_),y}const G=e=>{let t;for(const n in e)("class"===n||"style"===n||(0,o.F7)(n))&&((t||(t={}))[n]=e[n]);return t},q=(e,t)=>{const n={};for(const r in e)(0,o.tR)(r)&&r.slice(9)in t||(n[r]=e[r]);return n};function z(e,t,n){const{props:r,children:o,component:i}=e,{props:s,children:c,patchFlag:u}=t,l=i.emitsOptions;if(t.dirs||t.transition)return!0;if(!(n&&u>=0))return!(!o&&!c||c&&c.$stable)||r!==s&&(r?!s||H(r,s,l):!!s);if(1024&u)return!0;if(16&u)return r?H(r,s,l):!!s;if(8&u){const e=t.dynamicProps;for(let t=0;te.__isSuspense;function Z(e,t){t&&t.pendingBranch?(0,o.kJ)(e)?t.effects.push(...e):t.effects.push(e):R(e)}function X(e,t){if(bn){let n=bn.provides;const r=bn.parent&&bn.parent.provides;r===n&&(n=bn.provides=Object.create(r)),n[e]=t}else 0}function Y(e,t,n=!1){const r=bn||$;if(r){const i=null==r.parent?r.vnode.appContext&&r.vnode.appContext.provides:r.parent.provides;if(i&&e in i)return i[e];if(arguments.length>1)return n&&(0,o.mf)(t)?t.call(r.proxy):t}else 0}const Q={};function ee(e,t,n){return te(e,t,n)}function te(e,t,{immediate:n,deep:c,flush:u,onTrack:l,onTrigger:a}=o.kT){const f=bn;let p,d,h=!1,v=!1;if((0,r.dq)(e)?(p=()=>e.value,h=(0,r.yT)(e)):(0,r.PG)(e)?(p=()=>e,c=!0):(0,o.kJ)(e)?(v=!0,h=e.some((e=>(0,r.PG)(e)||(0,r.yT)(e))),p=()=>e.map((e=>(0,r.dq)(e)?e.value:(0,r.PG)(e)?oe(e):(0,o.mf)(e)?i(e,f,2):void 0))):p=(0,o.mf)(e)?t?()=>i(e,f,2):()=>{if(!f||!f.isUnmounted)return d&&d(),s(e,f,3,[g])}:o.dG,t&&c){const e=p;p=()=>oe(e())}let g=e=>{d=_.onStop=()=>{i(e,f,4)}};if(On)return g=o.dG,t?n&&s(t,f,3,[p(),v?[]:void 0,g]):p(),o.dG;let m=v?[]:Q;const y=()=>{if(_.active)if(t){const e=_.run();(c||h||(v?e.some(((e,t)=>(0,o.aU)(e,m[t]))):(0,o.aU)(e,m)))&&(d&&d(),s(t,f,3,[e,m===Q?void 0:m,g]),m=e)}else _.run()};let b;y.allowRecurse=!!t,b="sync"===u?y:"post"===u?()=>Tt(y,f&&f.suspense):()=>A(y);const _=new r.qq(p,b);return t?n?y():m=_.run():"post"===u?Tt(_.run.bind(_),f&&f.suspense):_.run(),()=>{_.stop(),f&&f.scope&&(0,o.Od)(f.scope.effects,_)}}function ne(e,t,n){const r=this.proxy,i=(0,o.HD)(e)?e.includes(".")?re(r,e):()=>r[e]:e.bind(r,r);let s;(0,o.mf)(t)?s=t:(s=t.handler,n=t);const c=bn;xn(this);const u=te(i,s.bind(r),n);return c?xn(c):wn(),u}function re(e,t){const n=t.split(".");return()=>{let t=e;for(let e=0;e{oe(e,t)}));else if((0,o.PO)(e))for(const n in e)oe(e[n],t);return e}function ie(){const e={isMounted:!1,isLeaving:!1,isUnmounting:!1,leavingVNodes:new Map};return Ae((()=>{e.isMounted=!0})),Pe((()=>{e.isUnmounting=!0})),e}const se=[Function,Array],ce={name:"BaseTransition",props:{mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:se,onEnter:se,onAfterEnter:se,onEnterCancelled:se,onBeforeLeave:se,onLeave:se,onAfterLeave:se,onLeaveCancelled:se,onBeforeAppear:se,onAppear:se,onAfterAppear:se,onAppearCancelled:se},setup(e,{slots:t}){const n=_n(),o=ie();let i;return()=>{const s=t.default&&he(t.default(),!0);if(!s||!s.length)return;let c=s[0];if(s.length>1){let e=!1;for(const t of s)if(t.type!==Jt){0,c=t,e=!0;break}}const u=(0,r.IU)(e),{mode:l}=u;if(o.isLeaving)return fe(c);const a=pe(c);if(!a)return fe(c);const f=ae(a,u,o,n);de(a,f);const p=n.subTree,d=p&&pe(p);let h=!1;const{getTransitionKey:v}=a.type;if(v){const e=v();void 0===i?i=e:e!==i&&(i=e,h=!0)}if(d&&d.type!==Jt&&(!Qt(a,d)||h)){const e=ae(d,u,o,n);if(de(d,e),"out-in"===l)return o.isLeaving=!0,e.afterLeave=()=>{o.isLeaving=!1,n.update()},fe(c);"in-out"===l&&a.type!==Jt&&(e.delayLeave=(e,t,n)=>{const r=le(o,d);r[String(d.key)]=d,e._leaveCb=()=>{t(),e._leaveCb=void 0,delete f.delayedLeave},f.delayedLeave=n})}return c}}},ue=ce;function le(e,t){const{leavingVNodes:n}=e;let r=n.get(t.type);return r||(r=Object.create(null),n.set(t.type,r)),r}function ae(e,t,n,r){const{appear:i,mode:c,persisted:u=!1,onBeforeEnter:l,onEnter:a,onAfterEnter:f,onEnterCancelled:p,onBeforeLeave:d,onLeave:h,onAfterLeave:v,onLeaveCancelled:g,onBeforeAppear:m,onAppear:y,onAfterAppear:b,onAppearCancelled:_}=t,x=String(e.key),w=le(n,e),k=(e,t)=>{e&&s(e,r,9,t)},S=(e,t)=>{const n=t[1];k(e,t),(0,o.kJ)(e)?e.every((e=>e.length<=1))&&n():e.length<=1&&n()},C={mode:c,persisted:u,beforeEnter(t){let r=l;if(!n.isMounted){if(!i)return;r=m||l}t._leaveCb&&t._leaveCb(!0);const o=w[x];o&&Qt(e,o)&&o.el._leaveCb&&o.el._leaveCb(),k(r,[t])},enter(e){let t=a,r=f,o=p;if(!n.isMounted){if(!i)return;t=y||a,r=b||f,o=_||p}let s=!1;const c=e._enterCb=t=>{s||(s=!0,k(t?o:r,[e]),C.delayedLeave&&C.delayedLeave(),e._enterCb=void 0)};t?S(t,[e,c]):c()},leave(t,r){const o=String(e.key);if(t._enterCb&&t._enterCb(!0),n.isUnmounting)return r();k(d,[t]);let i=!1;const s=t._leaveCb=n=>{i||(i=!0,r(),k(n?g:v,[t]),t._leaveCb=void 0,w[o]===e&&delete w[o])};w[o]=e,h?S(h,[t,s]):s()},clone(e){return ae(e,t,n,r)}};return C}function fe(e){if(me(e))return e=un(e),e.children=null,e}function pe(e){return me(e)?e.children?e.children[0]:void 0:e}function de(e,t){6&e.shapeFlag&&e.component?de(e.component.subTree,t):128&e.shapeFlag?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function he(e,t=!1,n){let r=[],o=0;for(let i=0;i1)for(let i=0;i!!e.type.__asyncLoader;const me=e=>e.type.__isKeepAlive;RegExp,RegExp;function ye(e,t){return(0,o.kJ)(e)?e.some((e=>ye(e,t))):(0,o.HD)(e)?e.split(",").includes(t):!!e.test&&e.test(t)}function be(e,t){xe(e,"a",t)}function _e(e,t){xe(e,"da",t)}function xe(e,t,n=bn){const r=e.__wdc||(e.__wdc=()=>{let t=n;while(t){if(t.isDeactivated)return;t=t.parent}return e()});if(Ce(t,r,n),n){let e=n.parent;while(e&&e.parent)me(e.parent.vnode)&&we(r,t,n,e),e=e.parent}}function we(e,t,n,r){const i=Ce(t,e,r,!0);Te((()=>{(0,o.Od)(r[t],i)}),n)}function ke(e){let t=e.shapeFlag;256&t&&(t-=256),512&t&&(t-=512),e.shapeFlag=t}function Se(e){return 128&e.shapeFlag?e.ssContent:e}function Ce(e,t,n=bn,o=!1){if(n){const i=n[e]||(n[e]=[]),c=t.__weh||(t.__weh=(...o)=>{if(n.isUnmounted)return;(0,r.Jd)(),xn(n);const i=s(t,n,e,o);return wn(),(0,r.lk)(),i});return o?i.unshift(c):i.push(c),c}}const Oe=e=>(t,n=bn)=>(!On||"sp"===e)&&Ce(e,t,n),Ee=Oe("bm"),Ae=Oe("m"),Re=Oe("bu"),Ie=Oe("u"),Pe=Oe("bum"),Te=Oe("um"),je=Oe("sp"),Fe=Oe("rtg"),Me=Oe("rtc");function Le(e,t=bn){Ce("ec",e,t)}function $e(e,t){const n=$;if(null===n)return e;const r=jn(n)||n.proxy,i=e.dirs||(e.dirs=[]);for(let s=0;st(e,n,void 0,s&&s[n])));else{const n=Object.keys(e);i=new Array(n.length);for(let r=0,o=n.length;r!Yt(e)||e.type!==Jt&&!(e.type===Nt&&!He(e.children))))?e:null}const We=e=>e?kn(e)?jn(e)||e.proxy:We(e.parent):null,Ke=(0,o.l7)(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>We(e.parent),$root:e=>We(e.root),$emit:e=>e.emit,$options:e=>nt(e),$forceUpdate:e=>e.f||(e.f=()=>S(e.update)),$nextTick:e=>e.n||(e.n=w.bind(e.proxy)),$watch:e=>ne.bind(e)}),Ze={get({_:e},t){const{ctx:n,setupState:i,data:s,props:c,accessCache:u,type:l,appContext:a}=e;let f;if("$"!==t[0]){const r=u[t];if(void 0!==r)switch(r){case 1:return i[t];case 2:return s[t];case 4:return n[t];case 3:return c[t]}else{if(i!==o.kT&&(0,o.RI)(i,t))return u[t]=1,i[t];if(s!==o.kT&&(0,o.RI)(s,t))return u[t]=2,s[t];if((f=e.propsOptions[0])&&(0,o.RI)(f,t))return u[t]=3,c[t];if(n!==o.kT&&(0,o.RI)(n,t))return u[t]=4,n[t];Xe&&(u[t]=0)}}const p=Ke[t];let d,h;return p?("$attrs"===t&&(0,r.j)(e,"get",t),p(e)):(d=l.__cssModules)&&(d=d[t])?d:n!==o.kT&&(0,o.RI)(n,t)?(u[t]=4,n[t]):(h=a.config.globalProperties,(0,o.RI)(h,t)?h[t]:void 0)},set({_:e},t,n){const{data:r,setupState:i,ctx:s}=e;return i!==o.kT&&(0,o.RI)(i,t)?(i[t]=n,!0):r!==o.kT&&(0,o.RI)(r,t)?(r[t]=n,!0):!(0,o.RI)(e.props,t)&&(("$"!==t[0]||!(t.slice(1)in e))&&(s[t]=n,!0))},has({_:{data:e,setupState:t,accessCache:n,ctx:r,appContext:i,propsOptions:s}},c){let u;return!!n[c]||e!==o.kT&&(0,o.RI)(e,c)||t!==o.kT&&(0,o.RI)(t,c)||(u=s[0])&&(0,o.RI)(u,c)||(0,o.RI)(r,c)||(0,o.RI)(Ke,c)||(0,o.RI)(i.config.globalProperties,c)},defineProperty(e,t,n){return null!=n.get?e._.accessCache[t]=0:(0,o.RI)(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}};let Xe=!0;function Ye(e){const t=nt(e),n=e.proxy,i=e.ctx;Xe=!1,t.beforeCreate&&et(t.beforeCreate,e,"bc");const{data:s,computed:c,methods:u,watch:l,provide:a,inject:f,created:p,beforeMount:d,mounted:h,beforeUpdate:v,updated:g,activated:m,deactivated:y,beforeDestroy:b,beforeUnmount:_,destroyed:x,unmounted:w,render:k,renderTracked:S,renderTriggered:C,errorCaptured:O,serverPrefetch:E,expose:A,inheritAttrs:R,components:I,directives:P,filters:T}=t,j=null;if(f&&Qe(f,i,j,e.appContext.config.unwrapInjectedRef),u)for(const r in u){const e=u[r];(0,o.mf)(e)&&(i[r]=e.bind(n))}if(s){0;const t=s.call(n,n);0,(0,o.Kn)(t)&&(e.data=(0,r.qj)(t))}if(Xe=!0,c)for(const r in c){const e=c[r],t=(0,o.mf)(e)?e.bind(n,n):(0,o.mf)(e.get)?e.get.bind(n,n):o.dG;0;const s=!(0,o.mf)(e)&&(0,o.mf)(e.set)?e.set.bind(n):o.dG,u=Ln({get:t,set:s});Object.defineProperty(i,r,{enumerable:!0,configurable:!0,get:()=>u.value,set:e=>u.value=e})}if(l)for(const r in l)tt(l[r],i,n,r);if(a){const e=(0,o.mf)(a)?a.call(n):a;Reflect.ownKeys(e).forEach((t=>{X(t,e[t])}))}function F(e,t){(0,o.kJ)(t)?t.forEach((t=>e(t.bind(n)))):t&&e(t.bind(n))}if(p&&et(p,e,"c"),F(Ee,d),F(Ae,h),F(Re,v),F(Ie,g),F(be,m),F(_e,y),F(Le,O),F(Me,S),F(Fe,C),F(Pe,_),F(Te,w),F(je,E),(0,o.kJ)(A))if(A.length){const t=e.exposed||(e.exposed={});A.forEach((e=>{Object.defineProperty(t,e,{get:()=>n[e],set:t=>n[e]=t})}))}else e.exposed||(e.exposed={});k&&e.render===o.dG&&(e.render=k),null!=R&&(e.inheritAttrs=R),I&&(e.components=I),P&&(e.directives=P)}function Qe(e,t,n=o.dG,i=!1){(0,o.kJ)(e)&&(e=ct(e));for(const s in e){const n=e[s];let c;c=(0,o.Kn)(n)?"default"in n?Y(n.from||s,n.default,!0):Y(n.from||s):Y(n),(0,r.dq)(c)&&i?Object.defineProperty(t,s,{enumerable:!0,configurable:!0,get:()=>c.value,set:e=>c.value=e}):t[s]=c}}function et(e,t,n){s((0,o.kJ)(e)?e.map((e=>e.bind(t.proxy))):e.bind(t.proxy),t,n)}function tt(e,t,n,r){const i=r.includes(".")?re(n,r):()=>n[r];if((0,o.HD)(e)){const n=t[e];(0,o.mf)(n)&&ee(i,n)}else if((0,o.mf)(e))ee(i,e.bind(n));else if((0,o.Kn)(e))if((0,o.kJ)(e))e.forEach((e=>tt(e,t,n,r)));else{const r=(0,o.mf)(e.handler)?e.handler.bind(n):t[e.handler];(0,o.mf)(r)&&ee(i,r,e)}else 0}function nt(e){const t=e.type,{mixins:n,extends:r}=t,{mixins:o,optionsCache:i,config:{optionMergeStrategies:s}}=e.appContext,c=i.get(t);let u;return c?u=c:o.length||n||r?(u={},o.length&&o.forEach((e=>rt(u,e,s,!0))),rt(u,t,s)):u=t,i.set(t,u),u}function rt(e,t,n,r=!1){const{mixins:o,extends:i}=t;i&&rt(e,i,n,!0),o&&o.forEach((t=>rt(e,t,n,!0)));for(const s in t)if(r&&"expose"===s);else{const r=ot[s]||n&&n[s];e[s]=r?r(e[s],t[s]):t[s]}return e}const ot={data:it,props:lt,emits:lt,methods:lt,computed:lt,beforeCreate:ut,created:ut,beforeMount:ut,mounted:ut,beforeUpdate:ut,updated:ut,beforeDestroy:ut,beforeUnmount:ut,destroyed:ut,unmounted:ut,activated:ut,deactivated:ut,errorCaptured:ut,serverPrefetch:ut,components:lt,directives:lt,watch:at,provide:it,inject:st};function it(e,t){return t?e?function(){return(0,o.l7)((0,o.mf)(e)?e.call(this,this):e,(0,o.mf)(t)?t.call(this,this):t)}:t:e}function st(e,t){return lt(ct(e),ct(t))}function ct(e){if((0,o.kJ)(e)){const t={};for(let n=0;n0)||16&u){let r;dt(e,t,s,c)&&(f=!0);for(const i in l)t&&((0,o.RI)(t,i)||(r=(0,o.rs)(i))!==i&&(0,o.RI)(t,r))||(a?!n||void 0===n[i]&&void 0===n[r]||(s[i]=ht(a,l,i,void 0,e,!0)):delete s[i]);if(c!==l)for(const e in c)t&&(0,o.RI)(t,e)||(delete c[e],f=!0)}else if(8&u){const n=e.vnode.dynamicProps;for(let r=0;r{l=!0;const[n,r]=vt(e,t,!0);(0,o.l7)(c,n),r&&u.push(...r)};!n&&t.mixins.length&&t.mixins.forEach(r),e.extends&&r(e.extends),e.mixins&&e.mixins.forEach(r)}if(!s&&!l)return r.set(e,o.Z6),o.Z6;if((0,o.kJ)(s))for(let f=0;f-1,r[1]=n<0||e-1||(0,o.RI)(r,"default"))&&u.push(t)}}}}const a=[c,u];return r.set(e,a),a}function gt(e){return"$"!==e[0]}function mt(e){const t=e&&e.toString().match(/^\s*function (\w+)/);return t?t[1]:null===e?"null":""}function yt(e,t){return mt(e)===mt(t)}function bt(e,t){return(0,o.kJ)(t)?t.findIndex((t=>yt(t,e))):(0,o.mf)(t)&&yt(t,e)?0:-1}const _t=e=>"_"===e[0]||"$stable"===e,xt=e=>(0,o.kJ)(e)?e.map(fn):[fn(e)],wt=(e,t,n)=>{if(t._n)return t;const r=B(((...e)=>xt(t(...e))),n);return r._c=!1,r},kt=(e,t,n)=>{const r=e._ctx;for(const i in e){if(_t(i))continue;const n=e[i];if((0,o.mf)(n))t[i]=wt(i,n,r);else if(null!=n){0;const e=xt(n);t[i]=()=>e}}},St=(e,t)=>{const n=xt(t);e.slots.default=()=>n},Ct=(e,t)=>{if(32&e.vnode.shapeFlag){const n=t._;n?(e.slots=(0,r.IU)(t),(0,o.Nj)(t,"_",n)):kt(t,e.slots={})}else e.slots={},t&&St(e,t);(0,o.Nj)(e.slots,en,1)},Ot=(e,t,n)=>{const{vnode:r,slots:i}=e;let s=!0,c=o.kT;if(32&r.shapeFlag){const e=t._;e?n&&1===e?s=!1:((0,o.l7)(i,t),n||1!==e||delete i._):(s=!t.$stable,kt(t,i)),c=t}else t&&(St(e,t),c={default:1});if(s)for(const o in i)_t(o)||o in c||delete i[o]};function Et(){return{app:null,config:{isNativeTag:o.NO,performance:!1,globalProperties:{},optionMergeStrategies:{},errorHandler:void 0,warnHandler:void 0,compilerOptions:{}},mixins:[],components:{},directives:{},provides:Object.create(null),optionsCache:new WeakMap,propsCache:new WeakMap,emitsCache:new WeakMap}}let At=0;function Rt(e,t){return function(n,r=null){(0,o.mf)(n)||(n=Object.assign({},n)),null==r||(0,o.Kn)(r)||(r=null);const i=Et(),s=new Set;let c=!1;const u=i.app={_uid:At++,_component:n,_props:r,_container:null,_context:i,_instance:null,version:Un,get config(){return i.config},set config(e){0},use(e,...t){return s.has(e)||(e&&(0,o.mf)(e.install)?(s.add(e),e.install(u,...t)):(0,o.mf)(e)&&(s.add(e),e(u,...t))),u},mixin(e){return i.mixins.includes(e)||i.mixins.push(e),u},component(e,t){return t?(i.components[e]=t,u):i.components[e]},directive(e,t){return t?(i.directives[e]=t,u):i.directives[e]},mount(o,s,l){if(!c){0;const a=on(n,r);return a.appContext=i,s&&t?t(a,o):e(a,o,l),c=!0,u._container=o,o.__vue_app__=u,jn(a.component)||a.component.proxy}},unmount(){c&&(e(null,u._container),delete u._container.__vue_app__)},provide(e,t){return i.provides[e]=t,u}};return u}}function It(e,t,n,s,c=!1){if((0,o.kJ)(e))return void e.forEach(((e,r)=>It(e,t&&((0,o.kJ)(t)?t[r]:t),n,s,c)));if(ge(s)&&!c)return;const u=4&s.shapeFlag?jn(s.component)||s.component.proxy:s.el,l=c?null:u,{i:a,r:f}=e;const p=t&&t.r,d=a.refs===o.kT?a.refs={}:a.refs,h=a.setupState;if(null!=p&&p!==f&&((0,o.HD)(p)?(d[p]=null,(0,o.RI)(h,p)&&(h[p]=null)):(0,r.dq)(p)&&(p.value=null)),(0,o.mf)(f))i(f,a,12,[l,d]);else{const t=(0,o.HD)(f),i=(0,r.dq)(f);if(t||i){const r=()=>{if(e.f){const n=t?d[f]:f.value;c?(0,o.kJ)(n)&&(0,o.Od)(n,u):(0,o.kJ)(n)?n.includes(u)||n.push(u):t?(d[f]=[u],(0,o.RI)(h,f)&&(h[f]=d[f])):(f.value=[u],e.k&&(d[e.k]=f.value))}else t?(d[f]=l,(0,o.RI)(h,f)&&(h[f]=l)):i&&(f.value=l,e.k&&(d[e.k]=l))};l?(r.id=-1,Tt(r,n)):r()}else 0}}function Pt(){}const Tt=Z;function jt(e){return Ft(e)}function Ft(e,t){Pt();const n=(0,o.E9)();n.__VUE__=!0;const{insert:i,remove:s,patchProp:c,createElement:u,createText:l,createComment:a,setText:f,setElementText:p,parentNode:d,nextSibling:h,setScopeId:v=o.dG,cloneNode:g,insertStaticContent:m}=e,y=(e,t,n,r=null,o=null,i=null,s=!1,c=null,u=!!t.dynamicChildren)=>{if(e===t)return;e&&!Qt(e,t)&&(r=Q(e),H(e,o,i,!0),e=null),-2===t.patchFlag&&(u=!1,t.dynamicChildren=null);const{type:l,ref:a,shapeFlag:f}=t;switch(l){case Dt:b(e,t,n,r);break;case Jt:_(e,t,n,r);break;case Bt:null==e&&x(t,n,r,s);break;case Nt:M(e,t,n,r,o,i,s,c,u);break;default:1&f?C(e,t,n,r,o,i,s,c,u):6&f?L(e,t,n,r,o,i,s,c,u):(64&f||128&f)&&l.process(e,t,n,r,o,i,s,c,u,te)}null!=a&&o&&It(a,e&&e.ref,i,t||e,!t)},b=(e,t,n,r)=>{if(null==e)i(t.el=l(t.children),n,r);else{const n=t.el=e.el;t.children!==e.children&&f(n,t.children)}},_=(e,t,n,r)=>{null==e?i(t.el=a(t.children||""),n,r):t.el=e.el},x=(e,t,n,r)=>{[e.el,e.anchor]=m(e.children,t,n,r,e.el,e.anchor)},w=({el:e,anchor:t},n,r)=>{let o;while(e&&e!==t)o=h(e),i(e,n,r),e=o;i(t,n,r)},k=({el:e,anchor:t})=>{let n;while(e&&e!==t)n=h(e),s(e),e=n;s(t)},C=(e,t,n,r,o,i,s,c,u)=>{s=s||"svg"===t.type,null==e?E(t,n,r,o,i,s,c,u):T(e,t,o,i,s,c,u)},E=(e,t,n,r,s,l,a,f)=>{let d,h;const{type:v,props:m,shapeFlag:y,transition:b,patchFlag:_,dirs:x}=e;if(e.el&&void 0!==g&&-1===_)d=e.el=g(e.el);else{if(d=e.el=u(e.type,l,m&&m.is,m),8&y?p(d,e.children):16&y&&R(e.children,d,null,r,s,l&&"foreignObject"!==v,a,f),x&&Ue(e,null,r,"created"),m){for(const t in m)"value"===t||(0,o.Gg)(t)||c(d,t,null,m[t],l,e.children,r,s,Y);"value"in m&&c(d,"value",null,m.value),(h=m.onVnodeBeforeMount)&&vn(h,r,e)}A(d,e,e.scopeId,a,r)}x&&Ue(e,null,r,"beforeMount");const w=(!s||s&&!s.pendingBranch)&&b&&!b.persisted;w&&b.beforeEnter(d),i(d,t,n),((h=m&&m.onVnodeMounted)||w||x)&&Tt((()=>{h&&vn(h,r,e),w&&b.enter(d),x&&Ue(e,null,r,"mounted")}),s)},A=(e,t,n,r,o)=>{if(n&&v(e,n),r)for(let i=0;i{for(let l=u;l{const l=t.el=e.el;let{patchFlag:a,dynamicChildren:f,dirs:d}=t;a|=16&e.patchFlag;const h=e.props||o.kT,v=t.props||o.kT;let g;n&&Mt(n,!1),(g=v.onVnodeBeforeUpdate)&&vn(g,n,t,e),d&&Ue(t,e,n,"beforeUpdate"),n&&Mt(n,!0);const m=i&&"foreignObject"!==t.type;if(f?j(e.dynamicChildren,f,l,n,r,m,s):u||J(e,t,l,null,n,r,m,s,!1),a>0){if(16&a)F(l,t,h,v,n,r,i);else if(2&a&&h.class!==v.class&&c(l,"class",null,v.class,i),4&a&&c(l,"style",h.style,v.style,i),8&a){const o=t.dynamicProps;for(let t=0;t{g&&vn(g,n,t,e),d&&Ue(t,e,n,"updated")}),r)},j=(e,t,n,r,o,i,s)=>{for(let c=0;c{if(n!==r){for(const l in r){if((0,o.Gg)(l))continue;const a=r[l],f=n[l];a!==f&&"value"!==l&&c(e,l,f,a,u,t.children,i,s,Y)}if(n!==o.kT)for(const l in n)(0,o.Gg)(l)||l in r||c(e,l,n[l],null,u,t.children,i,s,Y);"value"in r&&c(e,"value",n.value,r.value)}},M=(e,t,n,r,o,s,c,u,a)=>{const f=t.el=e?e.el:l(""),p=t.anchor=e?e.anchor:l("");let{patchFlag:d,dynamicChildren:h,slotScopeIds:v}=t;v&&(u=u?u.concat(v):v),null==e?(i(f,n,r),i(p,n,r),R(t.children,n,p,o,s,c,u,a)):d>0&&64&d&&h&&e.dynamicChildren?(j(e.dynamicChildren,h,n,o,s,c,u),(null!=t.key||o&&t===o.subTree)&&Lt(e,t,!0)):J(e,t,n,p,o,s,c,u,a)},L=(e,t,n,r,o,i,s,c,u)=>{t.slotScopeIds=c,null==e?512&t.shapeFlag?o.ctx.activate(t,n,r,s,u):$(t,n,r,o,i,s,u):U(e,t,u)},$=(e,t,n,r,o,i,s)=>{const c=e.component=yn(e,r,o);if(me(e)&&(c.ctx.renderer=te),En(c),c.asyncDep){if(o&&o.registerDep(c,N),!e.el){const e=c.subTree=on(Jt);_(null,e,t,n)}}else N(c,e,t,n,o,i,s)},U=(e,t,n)=>{const r=t.component=e.component;if(z(e,t,n)){if(r.asyncDep&&!r.asyncResolved)return void D(r,t,n);r.next=t,O(r.update),r.update()}else t.el=e.el,r.vnode=t},N=(e,t,n,i,s,c,u)=>{const l=()=>{if(e.isMounted){let t,{next:n,bu:r,u:i,parent:l,vnode:a}=e,f=n;0,Mt(e,!1),n?(n.el=a.el,D(e,n,u)):n=a,r&&(0,o.ir)(r),(t=n.props&&n.props.onVnodeBeforeUpdate)&&vn(t,l,n,a),Mt(e,!0);const p=V(e);0;const h=e.subTree;e.subTree=p,y(h,p,d(h.el),Q(h),e,s,c),n.el=p.el,null===f&&W(e,p.el),i&&Tt(i,s),(t=n.props&&n.props.onVnodeUpdated)&&Tt((()=>vn(t,l,n,a)),s)}else{let r;const{el:u,props:l}=t,{bm:a,m:f,parent:p}=e,d=ge(t);if(Mt(e,!1),a&&(0,o.ir)(a),!d&&(r=l&&l.onVnodeBeforeMount)&&vn(r,p,t),Mt(e,!0),u&&re){const n=()=>{e.subTree=V(e),re(u,e.subTree,e,s,null)};d?t.type.__asyncLoader().then((()=>!e.isUnmounted&&n())):n()}else{0;const r=e.subTree=V(e);0,y(null,r,n,i,e,s,c),t.el=r.el}if(f&&Tt(f,s),!d&&(r=l&&l.onVnodeMounted)){const e=t;Tt((()=>vn(r,p,e)),s)}(256&t.shapeFlag||p&&ge(p.vnode)&&256&p.vnode.shapeFlag)&&e.a&&Tt(e.a,s),e.isMounted=!0,t=n=i=null}},a=e.effect=new r.qq(l,(()=>S(f)),e.scope),f=e.update=()=>a.run();f.id=e.uid,Mt(e,!0),f()},D=(e,t,n)=>{t.component=e;const o=e.vnode.props;e.vnode=t,e.next=null,pt(e,t.props,o,n),Ot(e,t.children,n),(0,r.Jd)(),I(void 0,e.update),(0,r.lk)()},J=(e,t,n,r,o,i,s,c,u=!1)=>{const l=e&&e.children,a=e?e.shapeFlag:0,f=t.children,{patchFlag:d,shapeFlag:h}=t;if(d>0){if(128&d)return void G(l,f,n,r,o,i,s,c,u);if(256&d)return void B(l,f,n,r,o,i,s,c,u)}8&h?(16&a&&Y(l,o,i),f!==l&&p(n,f)):16&a?16&h?G(l,f,n,r,o,i,s,c,u):Y(l,o,i,!0):(8&a&&p(n,""),16&h&&R(f,n,r,o,i,s,c,u))},B=(e,t,n,r,i,s,c,u,l)=>{e=e||o.Z6,t=t||o.Z6;const a=e.length,f=t.length,p=Math.min(a,f);let d;for(d=0;df?Y(e,i,s,!0,!1,p):R(t,n,r,i,s,c,u,l,p)},G=(e,t,n,r,i,s,c,u,l)=>{let a=0;const f=t.length;let p=e.length-1,d=f-1;while(a<=p&&a<=d){const r=e[a],o=t[a]=l?pn(t[a]):fn(t[a]);if(!Qt(r,o))break;y(r,o,n,null,i,s,c,u,l),a++}while(a<=p&&a<=d){const r=e[p],o=t[d]=l?pn(t[d]):fn(t[d]);if(!Qt(r,o))break;y(r,o,n,null,i,s,c,u,l),p--,d--}if(a>p){if(a<=d){const e=d+1,o=ed)while(a<=p)H(e[a],i,s,!0),a++;else{const h=a,v=a,g=new Map;for(a=v;a<=d;a++){const e=t[a]=l?pn(t[a]):fn(t[a]);null!=e.key&&g.set(e.key,a)}let m,b=0;const _=d-v+1;let x=!1,w=0;const k=new Array(_);for(a=0;a<_;a++)k[a]=0;for(a=h;a<=p;a++){const r=e[a];if(b>=_){H(r,i,s,!0);continue}let o;if(null!=r.key)o=g.get(r.key);else for(m=v;m<=d;m++)if(0===k[m-v]&&Qt(r,t[m])){o=m;break}void 0===o?H(r,i,s,!0):(k[o-v]=a+1,o>=w?w=o:x=!0,y(r,t[o],n,null,i,s,c,u,l),b++)}const S=x?$t(k):o.Z6;for(m=S.length-1,a=_-1;a>=0;a--){const e=v+a,o=t[e],p=e+1{const{el:s,type:c,transition:u,children:l,shapeFlag:a}=e;if(6&a)return void q(e.component.subTree,t,n,r);if(128&a)return void e.suspense.move(t,n,r);if(64&a)return void c.move(e,t,n,te);if(c===Nt){i(s,t,n);for(let e=0;eu.enter(s)),o);else{const{leave:e,delayLeave:r,afterLeave:o}=u,c=()=>i(s,t,n),l=()=>{e(s,(()=>{c(),o&&o()}))};r?r(s,c,l):l()}else i(s,t,n)},H=(e,t,n,r=!1,o=!1)=>{const{type:i,props:s,ref:c,children:u,dynamicChildren:l,shapeFlag:a,patchFlag:f,dirs:p}=e;if(null!=c&&It(c,null,n,e,!0),256&a)return void t.ctx.deactivate(e);const d=1&a&&p,h=!ge(e);let v;if(h&&(v=s&&s.onVnodeBeforeUnmount)&&vn(v,t,e),6&a)X(e.component,n,r);else{if(128&a)return void e.suspense.unmount(n,r);d&&Ue(e,null,t,"beforeUnmount"),64&a?e.type.remove(e,t,n,o,te,r):l&&(i!==Nt||f>0&&64&f)?Y(l,t,n,!1,!0):(i===Nt&&384&f||!o&&16&a)&&Y(u,t,n),r&&K(e)}(h&&(v=s&&s.onVnodeUnmounted)||d)&&Tt((()=>{v&&vn(v,t,e),d&&Ue(e,null,t,"unmounted")}),n)},K=e=>{const{type:t,el:n,anchor:r,transition:o}=e;if(t===Nt)return void Z(n,r);if(t===Bt)return void k(e);const i=()=>{s(n),o&&!o.persisted&&o.afterLeave&&o.afterLeave()};if(1&e.shapeFlag&&o&&!o.persisted){const{leave:t,delayLeave:r}=o,s=()=>t(n,i);r?r(e.el,i,s):s()}else i()},Z=(e,t)=>{let n;while(e!==t)n=h(e),s(e),e=n;s(t)},X=(e,t,n)=>{const{bum:r,scope:i,update:s,subTree:c,um:u}=e;r&&(0,o.ir)(r),i.stop(),s&&(s.active=!1,H(c,e,t,n)),u&&Tt(u,t),Tt((()=>{e.isUnmounted=!0}),t),t&&t.pendingBranch&&!t.isUnmounted&&e.asyncDep&&!e.asyncResolved&&e.suspenseId===t.pendingId&&(t.deps--,0===t.deps&&t.resolve())},Y=(e,t,n,r=!1,o=!1,i=0)=>{for(let s=i;s6&e.shapeFlag?Q(e.component.subTree):128&e.shapeFlag?e.suspense.next():h(e.anchor||e.el),ee=(e,t,n)=>{null==e?t._vnode&&H(t._vnode,null,null,!0):y(t._vnode||null,e,t,null,null,null,n),P(),t._vnode=e},te={p:y,um:H,m:q,r:K,mt:$,mc:R,pc:J,pbc:j,n:Q,o:e};let ne,re;return t&&([ne,re]=t(te)),{render:ee,hydrate:ne,createApp:Rt(ee,ne)}}function Mt({effect:e,update:t},n){e.allowRecurse=t.allowRecurse=n}function Lt(e,t,n=!1){const r=e.children,i=t.children;if((0,o.kJ)(r)&&(0,o.kJ)(i))for(let o=0;o>1,e[n[c]]0&&(t[r]=n[i-1]),n[i]=r)}}i=n.length,s=n[i-1];while(i-- >0)n[i]=s,s=t[s];return n}const Ut=e=>e.__isTeleport;const Nt=Symbol(void 0),Dt=Symbol(void 0),Jt=Symbol(void 0),Bt=Symbol(void 0),Vt=[];let Gt=null;function qt(e=!1){Vt.push(Gt=e?null:[])}function zt(){Vt.pop(),Gt=Vt[Vt.length-1]||null}let Ht=1;function Wt(e){Ht+=e}function Kt(e){return e.dynamicChildren=Ht>0?Gt||o.Z6:null,zt(),Ht>0&&Gt&&Gt.push(e),e}function Zt(e,t,n,r,o,i){return Kt(rn(e,t,n,r,o,i,!0))}function Xt(e,t,n,r,o){return Kt(on(e,t,n,r,o,!0))}function Yt(e){return!!e&&!0===e.__v_isVNode}function Qt(e,t){return e.type===t.type&&e.key===t.key}const en="__vInternal",tn=({key:e})=>null!=e?e:null,nn=({ref:e,ref_key:t,ref_for:n})=>null!=e?(0,o.HD)(e)||(0,r.dq)(e)||(0,o.mf)(e)?{i:$,r:e,k:t,f:!!n}:e:null;function rn(e,t=null,n=null,r=0,i=null,s=(e===Nt?0:1),c=!1,u=!1){const l={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&tn(t),ref:t&&nn(t),scopeId:U,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:s,patchFlag:r,dynamicProps:i,dynamicChildren:null,appContext:null};return u?(dn(l,n),128&s&&e.normalize(l)):n&&(l.shapeFlag|=(0,o.HD)(n)?8:16),Ht>0&&!c&&Gt&&(l.patchFlag>0||6&s)&&32!==l.patchFlag&&Gt.push(l),l}const on=sn;function sn(e,t=null,n=null,i=0,s=null,c=!1){if(e&&e!==Je||(e=Jt),Yt(e)){const r=un(e,t,!0);return n&&dn(r,n),Ht>0&&!c&&Gt&&(6&r.shapeFlag?Gt[Gt.indexOf(e)]=r:Gt.push(r)),r.patchFlag|=-2,r}if(Mn(e)&&(e=e.__vccOpts),t){t=cn(t);let{class:e,style:n}=t;e&&!(0,o.HD)(e)&&(t.class=(0,o.C_)(e)),(0,o.Kn)(n)&&((0,r.X3)(n)&&!(0,o.kJ)(n)&&(n=(0,o.l7)({},n)),t.style=(0,o.j5)(n))}const u=(0,o.HD)(e)?1:K(e)?128:Ut(e)?64:(0,o.Kn)(e)?4:(0,o.mf)(e)?2:0;return rn(e,t,n,i,s,u,c,!0)}function cn(e){return e?(0,r.X3)(e)||en in e?(0,o.l7)({},e):e:null}function un(e,t,n=!1){const{props:r,ref:i,patchFlag:s,children:c}=e,u=t?hn(r||{},t):r,l={__v_isVNode:!0,__v_skip:!0,type:e.type,props:u,key:u&&tn(u),ref:t&&t.ref?n&&i?(0,o.kJ)(i)?i.concat(nn(t)):[i,nn(t)]:nn(t):i,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:c,target:e.target,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==Nt?-1===s?16:16|s:s,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:e.transition,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&un(e.ssContent),ssFallback:e.ssFallback&&un(e.ssFallback),el:e.el,anchor:e.anchor};return l}function ln(e=" ",t=0){return on(Dt,null,e,t)}function an(e="",t=!1){return t?(qt(),Xt(Jt,null,e)):on(Jt,null,e)}function fn(e){return null==e||"boolean"===typeof e?on(Jt):(0,o.kJ)(e)?on(Nt,null,e.slice()):"object"===typeof e?pn(e):on(Dt,null,String(e))}function pn(e){return null===e.el||e.memo?e:un(e)}function dn(e,t){let n=0;const{shapeFlag:r}=e;if(null==t)t=null;else if((0,o.kJ)(t))n=16;else if("object"===typeof t){if(65&r){const n=t.default;return void(n&&(n._c&&(n._d=!1),dn(e,n()),n._c&&(n._d=!0)))}{n=32;const r=t._;r||en in t?3===r&&$&&(1===$.slots._?t._=1:(t._=2,e.patchFlag|=1024)):t._ctx=$}}else(0,o.mf)(t)?(t={default:t,_ctx:$},n=32):(t=String(t),64&r?(n=16,t=[ln(t)]):n=8);e.children=t,e.shapeFlag|=n}function hn(...e){const t={};for(let n=0;nbn||$,xn=e=>{bn=e,e.scope.on()},wn=()=>{bn&&bn.scope.off(),bn=null};function kn(e){return 4&e.vnode.shapeFlag}let Sn,Cn,On=!1;function En(e,t=!1){On=t;const{props:n,children:r}=e.vnode,o=kn(e);ft(e,n,o,t),Ct(e,r);const i=o?An(e,t):void 0;return On=!1,i}function An(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=(0,r.Xl)(new Proxy(e.ctx,Ze));const{setup:s}=n;if(s){const n=e.setupContext=s.length>1?Tn(e):null;xn(e),(0,r.Jd)();const u=i(s,e,0,[e.props,n]);if((0,r.lk)(),wn(),(0,o.tI)(u)){if(u.then(wn,wn),t)return u.then((n=>{Rn(e,n,t)})).catch((t=>{c(t,e,0)}));e.asyncDep=u}else Rn(e,u,t)}else In(e,t)}function Rn(e,t,n){(0,o.mf)(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:(0,o.Kn)(t)&&(e.setupState=(0,r.WL)(t)),In(e,n)}function In(e,t,n){const i=e.type;if(!e.render){if(!t&&Sn&&!i.render){const t=i.template;if(t){0;const{isCustomElement:n,compilerOptions:r}=e.appContext.config,{delimiters:s,compilerOptions:c}=i,u=(0,o.l7)((0,o.l7)({isCustomElement:n,delimiters:s},r),c);i.render=Sn(t,u)}}e.render=i.render||o.dG,Cn&&Cn(e)}xn(e),(0,r.Jd)(),Ye(e),(0,r.lk)(),wn()}function Pn(e){return new Proxy(e.attrs,{get(t,n){return(0,r.j)(e,"get","$attrs"),t[n]}})}function Tn(e){const t=t=>{e.exposed=t||{}};let n;return{get attrs(){return n||(n=Pn(e))},slots:e.slots,emit:e.emit,expose:t}}function jn(e){if(e.exposed)return e.exposeProxy||(e.exposeProxy=new Proxy((0,r.WL)((0,r.Xl)(e.exposed)),{get(t,n){return n in t?t[n]:n in Ke?Ke[n](e):void 0}}))}function Fn(e,t=!0){return(0,o.mf)(e)?e.displayName||e.name:e.name||t&&e.__name}function Mn(e){return(0,o.mf)(e)&&"__vccOpts"in e}const Ln=(e,t)=>(0,r.Fl)(e,t,On);function $n(e,t,n){const r=arguments.length;return 2===r?(0,o.Kn)(t)&&!(0,o.kJ)(t)?Yt(t)?on(e,null,[t]):on(e,t):on(e,null,t):(r>3?n=Array.prototype.slice.call(arguments,2):3===r&&Yt(n)&&(n=[n]),on(e,t,n))}Symbol("");const Un="3.2.37"},9963:function(e,t,n){"use strict";n.d(t,{F8:function(){return re},bM:function(){return ee},ri:function(){return ue}});var r=n(3577),o=n(6252);n(2262);const i="http://www.w3.org/2000/svg",s="undefined"!==typeof document?document:null,c=s&&s.createElement("template"),u={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,r)=>{const o=t?s.createElementNS(i,e):s.createElement(e,n?{is:n}:void 0);return"select"===e&&r&&null!=r.multiple&&o.setAttribute("multiple",r.multiple),o},createText:e=>s.createTextNode(e),createComment:e=>s.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>s.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},cloneNode(e){const t=e.cloneNode(!0);return"_value"in e&&(t._value=e._value),t},insertStaticContent(e,t,n,r,o,i){const s=n?n.previousSibling:t.lastChild;if(o&&(o===i||o.nextSibling)){while(1)if(t.insertBefore(o.cloneNode(!0),n),o===i||!(o=o.nextSibling))break}else{c.innerHTML=r?`${e}`:e;const o=c.content;if(r){const e=o.firstChild;while(e.firstChild)o.appendChild(e.firstChild);o.removeChild(e)}t.insertBefore(o,n)}return[s?s.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}};function l(e,t,n){const r=e._vtc;r&&(t=(t?[t,...r]:[...r]).join(" ")),null==t?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}function a(e,t,n){const o=e.style,i=(0,r.HD)(n);if(n&&!i){for(const e in n)p(o,e,n[e]);if(t&&!(0,r.HD)(t))for(const e in t)null==n[e]&&p(o,e,"")}else{const r=o.display;i?t!==n&&(o.cssText=n):t&&e.removeAttribute("style"),"_vod"in e&&(o.display=r)}}const f=/\s*!important$/;function p(e,t,n){if((0,r.kJ)(n))n.forEach((n=>p(e,t,n)));else if(null==n&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const o=v(e,t);f.test(n)?e.setProperty((0,r.rs)(o),n.replace(f,""),"important"):e[o]=n}}const d=["Webkit","Moz","ms"],h={};function v(e,t){const n=h[t];if(n)return n;let o=(0,r._A)(t);if("filter"!==o&&o in e)return h[t]=o;o=(0,r.kC)(o);for(let r=0;r{let e=Date.now,t=!1;if("undefined"!==typeof window){Date.now()>document.createEvent("Event").timeStamp&&(e=performance.now.bind(performance));const n=navigator.userAgent.match(/firefox\/(\d+)/i);t=!!(n&&Number(n[1])<=53)}return[e,t]})();let x=0;const w=Promise.resolve(),k=()=>{x=0},S=()=>x||(w.then(k),x=b());function C(e,t,n,r){e.addEventListener(t,n,r)}function O(e,t,n,r){e.removeEventListener(t,n,r)}function E(e,t,n,r,o=null){const i=e._vei||(e._vei={}),s=i[t];if(r&&s)s.value=r;else{const[n,c]=R(t);if(r){const s=i[t]=I(r,o);C(e,n,s,c)}else s&&(O(e,n,s,c),i[t]=void 0)}}const A=/(?:Once|Passive|Capture)$/;function R(e){let t;if(A.test(e)){let n;t={};while(n=e.match(A))e=e.slice(0,e.length-n[0].length),t[n[0].toLowerCase()]=!0}return[(0,r.rs)(e.slice(2)),t]}function I(e,t){const n=e=>{const r=e.timeStamp||b();(_||r>=n.attached-1)&&(0,o.$d)(P(e,n.value),t,5,[e])};return n.value=e,n.attached=S(),n}function P(e,t){if((0,r.kJ)(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map((e=>t=>!t._stopped&&e&&e(t)))}return t}const T=/^on[a-z]/,j=(e,t,n,o,i=!1,s,c,u,f)=>{"class"===t?l(e,o,i):"style"===t?a(e,n,o):(0,r.F7)(t)?(0,r.tR)(t)||E(e,t,n,o,c):("."===t[0]?(t=t.slice(1),1):"^"===t[0]?(t=t.slice(1),0):F(e,t,o,i))?y(e,t,o,s,c,u,f):("true-value"===t?e._trueValue=o:"false-value"===t&&(e._falseValue=o),m(e,t,o,i))};function F(e,t,n,o){return o?"innerHTML"===t||"textContent"===t||!!(t in e&&T.test(t)&&(0,r.mf)(n)):"spellcheck"!==t&&"draggable"!==t&&"translate"!==t&&("form"!==t&&(("list"!==t||"INPUT"!==e.tagName)&&(("type"!==t||"TEXTAREA"!==e.tagName)&&((!T.test(t)||!(0,r.HD)(n))&&t in e))))}"undefined"!==typeof HTMLElement&&HTMLElement;const M="transition",L="animation",$=(e,{slots:t})=>(0,o.h)(o.P$,J(e),t);$.displayName="Transition";const U={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},N=($.props=(0,r.l7)({},o.P$.props,U),(e,t=[])=>{(0,r.kJ)(e)?e.forEach((e=>e(...t))):e&&e(...t)}),D=e=>!!e&&((0,r.kJ)(e)?e.some((e=>e.length>1)):e.length>1);function J(e){const t={};for(const r in e)r in U||(t[r]=e[r]);if(!1===e.css)return t;const{name:n="v",type:o,duration:i,enterFromClass:s=`${n}-enter-from`,enterActiveClass:c=`${n}-enter-active`,enterToClass:u=`${n}-enter-to`,appearFromClass:l=s,appearActiveClass:a=c,appearToClass:f=u,leaveFromClass:p=`${n}-leave-from`,leaveActiveClass:d=`${n}-leave-active`,leaveToClass:h=`${n}-leave-to`}=e,v=B(i),g=v&&v[0],m=v&&v[1],{onBeforeEnter:y,onEnter:b,onEnterCancelled:_,onLeave:x,onLeaveCancelled:w,onBeforeAppear:k=y,onAppear:S=b,onAppearCancelled:C=_}=t,O=(e,t,n)=>{q(e,t?f:u),q(e,t?a:c),n&&n()},E=(e,t)=>{e._isLeaving=!1,q(e,p),q(e,h),q(e,d),t&&t()},A=e=>(t,n)=>{const r=e?S:b,i=()=>O(t,e,n);N(r,[t,i]),z((()=>{q(t,e?l:s),G(t,e?f:u),D(r)||W(t,o,g,i)}))};return(0,r.l7)(t,{onBeforeEnter(e){N(y,[e]),G(e,s),G(e,c)},onBeforeAppear(e){N(k,[e]),G(e,l),G(e,a)},onEnter:A(!1),onAppear:A(!0),onLeave(e,t){e._isLeaving=!0;const n=()=>E(e,t);G(e,p),Y(),G(e,d),z((()=>{e._isLeaving&&(q(e,p),G(e,h),D(x)||W(e,o,m,n))})),N(x,[e,n])},onEnterCancelled(e){O(e,!1),N(_,[e])},onAppearCancelled(e){O(e,!0),N(C,[e])},onLeaveCancelled(e){E(e),N(w,[e])}})}function B(e){if(null==e)return null;if((0,r.Kn)(e))return[V(e.enter),V(e.leave)];{const t=V(e);return[t,t]}}function V(e){const t=(0,r.He)(e);return t}function G(e,t){t.split(/\s+/).forEach((t=>t&&e.classList.add(t))),(e._vtc||(e._vtc=new Set)).add(t)}function q(e,t){t.split(/\s+/).forEach((t=>t&&e.classList.remove(t)));const{_vtc:n}=e;n&&(n.delete(t),n.size||(e._vtc=void 0))}function z(e){requestAnimationFrame((()=>{requestAnimationFrame(e)}))}let H=0;function W(e,t,n,r){const o=e._endId=++H,i=()=>{o===e._endId&&r()};if(n)return setTimeout(i,n);const{type:s,timeout:c,propCount:u}=K(e,t);if(!s)return r();const l=s+"end";let a=0;const f=()=>{e.removeEventListener(l,p),i()},p=t=>{t.target===e&&++a>=u&&f()};setTimeout((()=>{a(n[e]||"").split(", "),o=r(M+"Delay"),i=r(M+"Duration"),s=Z(o,i),c=r(L+"Delay"),u=r(L+"Duration"),l=Z(c,u);let a=null,f=0,p=0;t===M?s>0&&(a=M,f=s,p=i.length):t===L?l>0&&(a=L,f=l,p=u.length):(f=Math.max(s,l),a=f>0?s>l?M:L:null,p=a?a===M?i.length:u.length:0);const d=a===M&&/\b(transform|all)(,|$)/.test(n[M+"Property"]);return{type:a,timeout:f,propCount:p,hasTransform:d}}function Z(e,t){while(e.lengthX(t)+X(e[n]))))}function X(e){return 1e3*Number(e.slice(0,-1).replace(",","."))}function Y(){return document.body.offsetHeight}new WeakMap,new WeakMap;const Q=e=>{const t=e.props["onUpdate:modelValue"]||!1;return(0,r.kJ)(t)?e=>(0,r.ir)(t,e):t};const ee={deep:!0,created(e,{value:t,modifiers:{number:n}},o){const i=(0,r.DM)(t);C(e,"change",(()=>{const t=Array.prototype.filter.call(e.options,(e=>e.selected)).map((e=>n?(0,r.He)(ne(e)):ne(e)));e._assign(e.multiple?i?new Set(t):t:t[0])})),e._assign=Q(o)},mounted(e,{value:t}){te(e,t)},beforeUpdate(e,t,n){e._assign=Q(n)},updated(e,{value:t}){te(e,t)}};function te(e,t){const n=e.multiple;if(!n||(0,r.kJ)(t)||(0,r.DM)(t)){for(let o=0,i=e.options.length;o-1:i.selected=t.has(s);else if((0,r.WV)(ne(i),t))return void(e.selectedIndex!==o&&(e.selectedIndex=o))}n||-1===e.selectedIndex||(e.selectedIndex=-1)}}function ne(e){return"_value"in e?e._value:e.value}const re={beforeMount(e,{value:t},{transition:n}){e._vod="none"===e.style.display?"":e.style.display,n&&t?n.beforeEnter(e):oe(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:r}){!t!==!n&&(r?t?(r.beforeEnter(e),oe(e,!0),r.enter(e)):r.leave(e,(()=>{oe(e,!1)})):oe(e,t))},beforeUnmount(e,{value:t}){oe(e,t)}};function oe(e,t){e.style.display=t?e._vod:"none"}const ie=(0,r.l7)({patchProp:j},u);let se;function ce(){return se||(se=(0,o.Us)(ie))}const ue=(...e)=>{const t=ce().createApp(...e);const{mount:n}=t;return t.mount=e=>{const o=le(e);if(!o)return;const i=t._component;(0,r.mf)(i)||i.render||i.template||(i.template=o.innerHTML),o.innerHTML="";const s=n(o,!1,o instanceof SVGElement);return o instanceof Element&&(o.removeAttribute("v-cloak"),o.setAttribute("data-v-app","")),s},t};function le(e){if((0,r.HD)(e)){const t=document.querySelector(e);return t}return e}},3577:function(e,t,n){"use strict";function r(e,t){const n=Object.create(null),r=e.split(",");for(let o=0;o!!n[e.toLowerCase()]:e=>!!n[e]}n.d(t,{C_:function(){return d},DM:function(){return T},E9:function(){return re},F7:function(){return S},Gg:function(){return G},HD:function(){return M},He:function(){return te},Kn:function(){return $},NO:function(){return w},Nj:function(){return ee},Od:function(){return E},PO:function(){return B},Pq:function(){return c},RI:function(){return R},S0:function(){return V},W7:function(){return J},WV:function(){return v},Z6:function(){return _},_A:function(){return H},_N:function(){return P},aU:function(){return Y},dG:function(){return x},e1:function(){return i},fY:function(){return r},hR:function(){return X},hq:function(){return g},ir:function(){return Q},j5:function(){return l},kC:function(){return Z},kJ:function(){return I},kT:function(){return b},l7:function(){return O},mf:function(){return F},rs:function(){return K},tI:function(){return U},tR:function(){return C},yA:function(){return u},yk:function(){return L},zw:function(){return m}});const o="Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt",i=r(o);const s="itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly",c=r(s);function u(e){return!!e||""===e}function l(e){if(I(e)){const t={};for(let n=0;n{if(e){const n=e.split(f);n.length>1&&(t[n[0].trim()]=n[1].trim())}})),t}function d(e){let t="";if(M(e))t=e;else if(I(e))for(let n=0;nv(e,t)))}const m=e=>M(e)?e:null==e?"":I(e)||$(e)&&(e.toString===N||!F(e.toString))?JSON.stringify(e,y,2):String(e),y=(e,t)=>t&&t.__v_isRef?y(e,t.value):P(t)?{[`Map(${t.size})`]:[...t.entries()].reduce(((e,[t,n])=>(e[`${t} =>`]=n,e)),{})}:T(t)?{[`Set(${t.size})`]:[...t.values()]}:!$(t)||I(t)||B(t)?t:String(t),b={},_=[],x=()=>{},w=()=>!1,k=/^on[^a-z]/,S=e=>k.test(e),C=e=>e.startsWith("onUpdate:"),O=Object.assign,E=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},A=Object.prototype.hasOwnProperty,R=(e,t)=>A.call(e,t),I=Array.isArray,P=e=>"[object Map]"===D(e),T=e=>"[object Set]"===D(e),j=e=>"[object Date]"===D(e),F=e=>"function"===typeof e,M=e=>"string"===typeof e,L=e=>"symbol"===typeof e,$=e=>null!==e&&"object"===typeof e,U=e=>$(e)&&F(e.then)&&F(e.catch),N=Object.prototype.toString,D=e=>N.call(e),J=e=>D(e).slice(8,-1),B=e=>"[object Object]"===D(e),V=e=>M(e)&&"NaN"!==e&&"-"!==e[0]&&""+parseInt(e,10)===e,G=r(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),q=e=>{const t=Object.create(null);return n=>{const r=t[n];return r||(t[n]=e(n))}},z=/-(\w)/g,H=q((e=>e.replace(z,((e,t)=>t?t.toUpperCase():"")))),W=/\B([A-Z])/g,K=q((e=>e.replace(W,"-$1").toLowerCase())),Z=q((e=>e.charAt(0).toUpperCase()+e.slice(1))),X=q((e=>e?`on${Z(e)}`:"")),Y=(e,t)=>!Object.is(e,t),Q=(e,t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value:n})},te=e=>{const t=parseFloat(e);return isNaN(t)?e:t};let ne;const re=()=>ne||(ne="undefined"!==typeof globalThis?globalThis:"undefined"!==typeof self?self:"undefined"!==typeof window?window:"undefined"!==typeof n.g?n.g:{})},9662:function(e,t,n){var r=n(614),o=n(6330),i=TypeError;e.exports=function(e){if(r(e))return e;throw i(o(e)+" is not a function")}},9483:function(e,t,n){var r=n(4411),o=n(6330),i=TypeError;e.exports=function(e){if(r(e))return e;throw i(o(e)+" is not a constructor")}},6077:function(e,t,n){var r=n(614),o=String,i=TypeError;e.exports=function(e){if("object"==typeof e||r(e))return e;throw i("Can't set "+o(e)+" as a prototype")}},1223:function(e,t,n){var r=n(5112),o=n(30),i=n(3070).f,s=r("unscopables"),c=Array.prototype;void 0==c[s]&&i(c,s,{configurable:!0,value:o(null)}),e.exports=function(e){c[s][e]=!0}},1530:function(e,t,n){"use strict";var r=n(8710).charAt;e.exports=function(e,t,n){return t+(n?r(e,t).length:1)}},9670:function(e,t,n){var r=n(111),o=String,i=TypeError;e.exports=function(e){if(r(e))return e;throw i(o(e)+" is not an object")}},1318:function(e,t,n){var r=n(5656),o=n(1400),i=n(6244),s=function(e){return function(t,n,s){var c,u=r(t),l=i(u),a=o(s,l);if(e&&n!=n){while(l>a)if(c=u[a++],c!=c)return!0}else for(;l>a;a++)if((e||a in u)&&u[a]===n)return e||a||0;return!e&&-1}};e.exports={includes:s(!0),indexOf:s(!1)}},4326:function(e,t,n){var r=n(1702),o=r({}.toString),i=r("".slice);e.exports=function(e){return i(o(e),8,-1)}},648:function(e,t,n){var r=n(1694),o=n(614),i=n(4326),s=n(5112),c=s("toStringTag"),u=Object,l="Arguments"==i(function(){return arguments}()),a=function(e,t){try{return e[t]}catch(n){}};e.exports=r?i:function(e){var t,n,r;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=a(t=u(e),c))?n:l?i(t):"Object"==(r=i(t))&&o(t.callee)?"Arguments":r}},9920:function(e,t,n){var r=n(2597),o=n(3887),i=n(1236),s=n(3070);e.exports=function(e,t,n){for(var c=o(t),u=s.f,l=i.f,a=0;a0&&r[0]<4?1:+(r[0]+r[1])),!o&&s&&(r=s.match(/Edge\/(\d+)/),(!r||r[1]>=74)&&(r=s.match(/Chrome\/(\d+)/),r&&(o=+r[1]))),e.exports=o},748:function(e){e.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},2109:function(e,t,n){var r=n(7854),o=n(1236).f,i=n(8880),s=n(8052),c=n(3072),u=n(9920),l=n(4705);e.exports=function(e,t){var n,a,f,p,d,h,v=e.target,g=e.global,m=e.stat;if(a=g?r:m?r[v]||c(v,{}):(r[v]||{}).prototype,a)for(f in t){if(d=t[f],e.dontCallGetSet?(h=o(a,f),p=h&&h.value):p=a[f],n=l(g?f:v+(m?".":"#")+f,e.forced),!n&&void 0!==p){if(typeof d==typeof p)continue;u(d,p)}(e.sham||p&&p.sham)&&i(d,"sham",!0),s(a,f,d,e)}}},7293:function(e){e.exports=function(e){try{return!!e()}catch(t){return!0}}},7007:function(e,t,n){"use strict";n(4916);var r=n(1702),o=n(8052),i=n(2261),s=n(7293),c=n(5112),u=n(8880),l=c("species"),a=RegExp.prototype;e.exports=function(e,t,n,f){var p=c(e),d=!s((function(){var t={};return t[p]=function(){return 7},7!=""[e](t)})),h=d&&!s((function(){var t=!1,n=/a/;return"split"===e&&(n={},n.constructor={},n.constructor[l]=function(){return n},n.flags="",n[p]=/./[p]),n.exec=function(){return t=!0,null},n[p](""),!t}));if(!d||!h||n){var v=r(/./[p]),g=t(p,""[e],(function(e,t,n,o,s){var c=r(e),u=t.exec;return u===i||u===a.exec?d&&!s?{done:!0,value:v(t,n,o)}:{done:!0,value:c(n,t,o)}:{done:!1}}));o(String.prototype,e,g[0]),o(a,p,g[1])}f&&u(a[p],"sham",!0)}},2104:function(e,t,n){var r=n(4374),o=Function.prototype,i=o.apply,s=o.call;e.exports="object"==typeof Reflect&&Reflect.apply||(r?s.bind(i):function(){return s.apply(i,arguments)})},4374:function(e,t,n){var r=n(7293);e.exports=!r((function(){var e=function(){}.bind();return"function"!=typeof e||e.hasOwnProperty("prototype")}))},6916:function(e,t,n){var r=n(4374),o=Function.prototype.call;e.exports=r?o.bind(o):function(){return o.apply(o,arguments)}},6530:function(e,t,n){var r=n(9781),o=n(2597),i=Function.prototype,s=r&&Object.getOwnPropertyDescriptor,c=o(i,"name"),u=c&&"something"===function(){}.name,l=c&&(!r||r&&s(i,"name").configurable);e.exports={EXISTS:c,PROPER:u,CONFIGURABLE:l}},1702:function(e,t,n){var r=n(4374),o=Function.prototype,i=o.bind,s=o.call,c=r&&i.bind(s,s);e.exports=r?function(e){return e&&c(e)}:function(e){return e&&function(){return s.apply(e,arguments)}}},5005:function(e,t,n){var r=n(7854),o=n(614),i=function(e){return o(e)?e:void 0};e.exports=function(e,t){return arguments.length<2?i(r[e]):r[e]&&r[e][t]}},8173:function(e,t,n){var r=n(9662);e.exports=function(e,t){var n=e[t];return null==n?void 0:r(n)}},647:function(e,t,n){var r=n(1702),o=n(7908),i=Math.floor,s=r("".charAt),c=r("".replace),u=r("".slice),l=/\$([$&'`]|\d{1,2}|<[^>]*>)/g,a=/\$([$&'`]|\d{1,2})/g;e.exports=function(e,t,n,r,f,p){var d=n+e.length,h=r.length,v=a;return void 0!==f&&(f=o(f),v=l),c(p,v,(function(o,c){var l;switch(s(c,0)){case"$":return"$";case"&":return e;case"`":return u(t,0,n);case"'":return u(t,d);case"<":l=f[u(c,1,-1)];break;default:var a=+c;if(0===a)return o;if(a>h){var p=i(a/10);return 0===p?o:p<=h?void 0===r[p-1]?s(c,1):r[p-1]+s(c,1):o}l=r[a-1]}return void 0===l?"":l}))}},7854:function(e,t,n){var r=function(e){return e&&e.Math==Math&&e};e.exports=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof n.g&&n.g)||function(){return this}()||Function("return this")()},2597:function(e,t,n){var r=n(1702),o=n(7908),i=r({}.hasOwnProperty);e.exports=Object.hasOwn||function(e,t){return i(o(e),t)}},3501:function(e){e.exports={}},490:function(e,t,n){var r=n(5005);e.exports=r("document","documentElement")},4664:function(e,t,n){var r=n(9781),o=n(7293),i=n(317);e.exports=!r&&!o((function(){return 7!=Object.defineProperty(i("div"),"a",{get:function(){return 7}}).a}))},8361:function(e,t,n){var r=n(1702),o=n(7293),i=n(4326),s=Object,c=r("".split);e.exports=o((function(){return!s("z").propertyIsEnumerable(0)}))?function(e){return"String"==i(e)?c(e,""):s(e)}:s},2788:function(e,t,n){var r=n(1702),o=n(614),i=n(5465),s=r(Function.toString);o(i.inspectSource)||(i.inspectSource=function(e){return s(e)}),e.exports=i.inspectSource},9909:function(e,t,n){var r,o,i,s=n(8536),c=n(7854),u=n(1702),l=n(111),a=n(8880),f=n(2597),p=n(5465),d=n(6200),h=n(3501),v="Object already initialized",g=c.TypeError,m=c.WeakMap,y=function(e){return i(e)?o(e):r(e,{})},b=function(e){return function(t){var n;if(!l(t)||(n=o(t)).type!==e)throw g("Incompatible receiver, "+e+" required");return n}};if(s||p.state){var _=p.state||(p.state=new m),x=u(_.get),w=u(_.has),k=u(_.set);r=function(e,t){if(w(_,e))throw new g(v);return t.facade=e,k(_,e,t),t},o=function(e){return x(_,e)||{}},i=function(e){return w(_,e)}}else{var S=d("state");h[S]=!0,r=function(e,t){if(f(e,S))throw new g(v);return t.facade=e,a(e,S,t),t},o=function(e){return f(e,S)?e[S]:{}},i=function(e){return f(e,S)}}e.exports={set:r,get:o,has:i,enforce:y,getterFor:b}},614:function(e){e.exports=function(e){return"function"==typeof e}},4411:function(e,t,n){var r=n(1702),o=n(7293),i=n(614),s=n(648),c=n(5005),u=n(2788),l=function(){},a=[],f=c("Reflect","construct"),p=/^\s*(?:class|function)\b/,d=r(p.exec),h=!p.exec(l),v=function(e){if(!i(e))return!1;try{return f(l,a,e),!0}catch(t){return!1}},g=function(e){if(!i(e))return!1;switch(s(e)){case"AsyncFunction":case"GeneratorFunction":case"AsyncGeneratorFunction":return!1}try{return h||!!d(p,u(e))}catch(t){return!0}};g.sham=!0,e.exports=!f||o((function(){var e;return v(v.call)||!v(Object)||!v((function(){e=!0}))||e}))?g:v},4705:function(e,t,n){var r=n(7293),o=n(614),i=/#|\.prototype\./,s=function(e,t){var n=u[c(e)];return n==a||n!=l&&(o(t)?r(t):!!t)},c=s.normalize=function(e){return String(e).replace(i,".").toLowerCase()},u=s.data={},l=s.NATIVE="N",a=s.POLYFILL="P";e.exports=s},111:function(e,t,n){var r=n(614);e.exports=function(e){return"object"==typeof e?null!==e:r(e)}},1913:function(e){e.exports=!1},2190:function(e,t,n){var r=n(5005),o=n(614),i=n(7976),s=n(3307),c=Object;e.exports=s?function(e){return"symbol"==typeof e}:function(e){var t=r("Symbol");return o(t)&&i(t.prototype,c(e))}},3383:function(e,t,n){"use strict";var r,o,i,s=n(7293),c=n(614),u=n(30),l=n(9518),a=n(8052),f=n(5112),p=n(1913),d=f("iterator"),h=!1;[].keys&&(i=[].keys(),"next"in i?(o=l(l(i)),o!==Object.prototype&&(r=o)):h=!0);var v=void 0==r||s((function(){var e={};return r[d].call(e)!==e}));v?r={}:p&&(r=u(r)),c(r[d])||a(r,d,(function(){return this})),e.exports={IteratorPrototype:r,BUGGY_SAFARI_ITERATORS:h}},7497:function(e){e.exports={}},6244:function(e,t,n){var r=n(7466);e.exports=function(e){return r(e.length)}},6339:function(e,t,n){var r=n(7293),o=n(614),i=n(2597),s=n(9781),c=n(6530).CONFIGURABLE,u=n(2788),l=n(9909),a=l.enforce,f=l.get,p=Object.defineProperty,d=s&&!r((function(){return 8!==p((function(){}),"length",{value:8}).length})),h=String(String).split("String"),v=e.exports=function(e,t,n){"Symbol("===String(t).slice(0,7)&&(t="["+String(t).replace(/^Symbol\(([^)]*)\)/,"$1")+"]"),n&&n.getter&&(t="get "+t),n&&n.setter&&(t="set "+t),(!i(e,"name")||c&&e.name!==t)&&p(e,"name",{value:t,configurable:!0}),d&&n&&i(n,"arity")&&e.length!==n.arity&&p(e,"length",{value:n.arity});try{n&&i(n,"constructor")&&n.constructor?s&&p(e,"prototype",{writable:!1}):e.prototype&&(e.prototype=void 0)}catch(o){}var r=a(e);return i(r,"source")||(r.source=h.join("string"==typeof t?t:"")),e};Function.prototype.toString=v((function(){return o(this)&&f(this).source||u(this)}),"toString")},4758:function(e){var t=Math.ceil,n=Math.floor;e.exports=Math.trunc||function(e){var r=+e;return(r>0?n:t)(r)}},133:function(e,t,n){var r=n(7392),o=n(7293);e.exports=!!Object.getOwnPropertySymbols&&!o((function(){var e=Symbol();return!String(e)||!(Object(e)instanceof Symbol)||!Symbol.sham&&r&&r<41}))},8536:function(e,t,n){var r=n(7854),o=n(614),i=n(2788),s=r.WeakMap;e.exports=o(s)&&/native code/.test(i(s))},8523:function(e,t,n){"use strict";var r=n(9662),o=function(e){var t,n;this.promise=new e((function(e,r){if(void 0!==t||void 0!==n)throw TypeError("Bad Promise constructor");t=e,n=r})),this.resolve=r(t),this.reject=r(n)};e.exports.f=function(e){return new o(e)}},30:function(e,t,n){var r,o=n(9670),i=n(6048),s=n(748),c=n(3501),u=n(490),l=n(317),a=n(6200),f=">",p="<",d="prototype",h="script",v=a("IE_PROTO"),g=function(){},m=function(e){return p+h+f+e+p+"/"+h+f},y=function(e){e.write(m("")),e.close();var t=e.parentWindow.Object;return e=null,t},b=function(){var e,t=l("iframe"),n="java"+h+":";return t.style.display="none",u.appendChild(t),t.src=String(n),e=t.contentWindow.document,e.open(),e.write(m("document.F=Object")),e.close(),e.F},_=function(){try{r=new ActiveXObject("htmlfile")}catch(t){}_="undefined"!=typeof document?document.domain&&r?y(r):b():y(r);var e=s.length;while(e--)delete _[d][s[e]];return _()};c[v]=!0,e.exports=Object.create||function(e,t){var n;return null!==e?(g[d]=o(e),n=new g,g[d]=null,n[v]=e):n=_(),void 0===t?n:i.f(n,t)}},6048:function(e,t,n){var r=n(9781),o=n(3353),i=n(3070),s=n(9670),c=n(5656),u=n(1956);t.f=r&&!o?Object.defineProperties:function(e,t){s(e);var n,r=c(t),o=u(t),l=o.length,a=0;while(l>a)i.f(e,n=o[a++],r[n]);return e}},3070:function(e,t,n){var r=n(9781),o=n(4664),i=n(3353),s=n(9670),c=n(4948),u=TypeError,l=Object.defineProperty,a=Object.getOwnPropertyDescriptor,f="enumerable",p="configurable",d="writable";t.f=r?i?function(e,t,n){if(s(e),t=c(t),s(n),"function"===typeof e&&"prototype"===t&&"value"in n&&d in n&&!n[d]){var r=a(e,t);r&&r[d]&&(e[t]=n.value,n={configurable:p in n?n[p]:r[p],enumerable:f in n?n[f]:r[f],writable:!1})}return l(e,t,n)}:l:function(e,t,n){if(s(e),t=c(t),s(n),o)try{return l(e,t,n)}catch(r){}if("get"in n||"set"in n)throw u("Accessors not supported");return"value"in n&&(e[t]=n.value),e}},1236:function(e,t,n){var r=n(9781),o=n(6916),i=n(5296),s=n(9114),c=n(5656),u=n(4948),l=n(2597),a=n(4664),f=Object.getOwnPropertyDescriptor;t.f=r?f:function(e,t){if(e=c(e),t=u(t),a)try{return f(e,t)}catch(n){}if(l(e,t))return s(!o(i.f,e,t),e[t])}},8006:function(e,t,n){var r=n(6324),o=n(748),i=o.concat("length","prototype");t.f=Object.getOwnPropertyNames||function(e){return r(e,i)}},5181:function(e,t){t.f=Object.getOwnPropertySymbols},9518:function(e,t,n){var r=n(2597),o=n(614),i=n(7908),s=n(6200),c=n(8544),u=s("IE_PROTO"),l=Object,a=l.prototype;e.exports=c?l.getPrototypeOf:function(e){var t=i(e);if(r(t,u))return t[u];var n=t.constructor;return o(n)&&t instanceof n?n.prototype:t instanceof l?a:null}},7976:function(e,t,n){var r=n(1702);e.exports=r({}.isPrototypeOf)},6324:function(e,t,n){var r=n(1702),o=n(2597),i=n(5656),s=n(1318).indexOf,c=n(3501),u=r([].push);e.exports=function(e,t){var n,r=i(e),l=0,a=[];for(n in r)!o(c,n)&&o(r,n)&&u(a,n);while(t.length>l)o(r,n=t[l++])&&(~s(a,n)||u(a,n));return a}},1956:function(e,t,n){var r=n(6324),o=n(748);e.exports=Object.keys||function(e){return r(e,o)}},5296:function(e,t){"use strict";var n={}.propertyIsEnumerable,r=Object.getOwnPropertyDescriptor,o=r&&!n.call({1:2},1);t.f=o?function(e){var t=r(this,e);return!!t&&t.enumerable}:n},7674:function(e,t,n){var r=n(1702),o=n(9670),i=n(6077);e.exports=Object.setPrototypeOf||("__proto__"in{}?function(){var e,t=!1,n={};try{e=r(Object.getOwnPropertyDescriptor(Object.prototype,"__proto__").set),e(n,[]),t=n instanceof Array}catch(s){}return function(n,r){return o(n),i(r),t?e(n,r):n.__proto__=r,n}}():void 0)},2140:function(e,t,n){var r=n(6916),o=n(614),i=n(111),s=TypeError;e.exports=function(e,t){var n,c;if("string"===t&&o(n=e.toString)&&!i(c=r(n,e)))return c;if(o(n=e.valueOf)&&!i(c=r(n,e)))return c;if("string"!==t&&o(n=e.toString)&&!i(c=r(n,e)))return c;throw s("Can't convert object to primitive value")}},3887:function(e,t,n){var r=n(5005),o=n(1702),i=n(8006),s=n(5181),c=n(9670),u=o([].concat);e.exports=r("Reflect","ownKeys")||function(e){var t=i.f(c(e)),n=s.f;return n?u(t,n(e)):t}},2492:function(e,t,n){var r=n(7854);e.exports=r.Promise},9478:function(e,t,n){var r=n(9670),o=n(111),i=n(8523);e.exports=function(e,t){if(r(e),o(t)&&t.constructor===e)return t;var n=i.f(e),s=n.resolve;return s(t),n.promise}},7651:function(e,t,n){var r=n(6916),o=n(9670),i=n(614),s=n(4326),c=n(2261),u=TypeError;e.exports=function(e,t){var n=e.exec;if(i(n)){var l=r(n,e,t);return null!==l&&o(l),l}if("RegExp"===s(e))return r(c,e,t);throw u("RegExp#exec called on incompatible receiver")}},2261:function(e,t,n){"use strict";var r=n(6916),o=n(1702),i=n(1340),s=n(7066),c=n(2999),u=n(2309),l=n(30),a=n(9909).get,f=n(9441),p=n(7168),d=u("native-string-replace",String.prototype.replace),h=RegExp.prototype.exec,v=h,g=o("".charAt),m=o("".indexOf),y=o("".replace),b=o("".slice),_=function(){var e=/a/,t=/b*/g;return r(h,e,"a"),r(h,t,"a"),0!==e.lastIndex||0!==t.lastIndex}(),x=c.BROKEN_CARET,w=void 0!==/()??/.exec("")[1],k=_||w||x||f||p;k&&(v=function(e){var t,n,o,c,u,f,p,k=this,S=a(k),C=i(e),O=S.raw;if(O)return O.lastIndex=k.lastIndex,t=r(v,O,C),k.lastIndex=O.lastIndex,t;var E=S.groups,A=x&&k.sticky,R=r(s,k),I=k.source,P=0,T=C;if(A&&(R=y(R,"y",""),-1===m(R,"g")&&(R+="g"),T=b(C,k.lastIndex),k.lastIndex>0&&(!k.multiline||k.multiline&&"\n"!==g(C,k.lastIndex-1))&&(I="(?: "+I+")",T=" "+T,P++),n=new RegExp("^(?:"+I+")",R)),w&&(n=new RegExp("^"+I+"$(?!\\s)",R)),_&&(o=k.lastIndex),c=r(h,A?n:k,T),A?c?(c.input=b(c.input,P),c[0]=b(c[0],P),c.index=k.lastIndex,k.lastIndex+=c[0].length):k.lastIndex=0:_&&c&&(k.lastIndex=k.global?c.index+c[0].length:o),w&&c&&c.length>1&&r(d,c[0],n,(function(){for(u=1;ub)","g");return"b"!==e.exec("b").groups.a||"bc"!=="b".replace(e,"$
c")}))},4488:function(e){var t=TypeError;e.exports=function(e){if(void 0==e)throw t("Can't call method on "+e);return e}},8003:function(e,t,n){var r=n(3070).f,o=n(2597),i=n(5112),s=i("toStringTag");e.exports=function(e,t,n){e&&!n&&(e=e.prototype),e&&!o(e,s)&&r(e,s,{configurable:!0,value:t})}},6200:function(e,t,n){var r=n(2309),o=n(9711),i=r("keys");e.exports=function(e){return i[e]||(i[e]=o(e))}},5465:function(e,t,n){var r=n(7854),o=n(3072),i="__core-js_shared__",s=r[i]||o(i,{});e.exports=s},2309:function(e,t,n){var r=n(1913),o=n(5465);(e.exports=function(e,t){return o[e]||(o[e]=void 0!==t?t:{})})("versions",[]).push({version:"3.22.8",mode:r?"pure":"global",copyright:"© 2014-2022 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.22.8/LICENSE",source:"https://github.com/zloirock/core-js"})},6707:function(e,t,n){var r=n(9670),o=n(9483),i=n(5112),s=i("species");e.exports=function(e,t){var n,i=r(e).constructor;return void 0===i||void 0==(n=r(i)[s])?t:o(n)}},8710:function(e,t,n){var r=n(1702),o=n(9303),i=n(1340),s=n(4488),c=r("".charAt),u=r("".charCodeAt),l=r("".slice),a=function(e){return function(t,n){var r,a,f=i(s(t)),p=o(n),d=f.length;return p<0||p>=d?e?"":void 0:(r=u(f,p),r<55296||r>56319||p+1===d||(a=u(f,p+1))<56320||a>57343?e?c(f,p):r:e?l(f,p,p+2):a-56320+(r-55296<<10)+65536)}};e.exports={codeAt:a(!1),charAt:a(!0)}},1400:function(e,t,n){var r=n(9303),o=Math.max,i=Math.min;e.exports=function(e,t){var n=r(e);return n<0?o(n+t,0):i(n,t)}},5656:function(e,t,n){var r=n(8361),o=n(4488);e.exports=function(e){return r(o(e))}},9303:function(e,t,n){var r=n(4758);e.exports=function(e){var t=+e;return t!==t||0===t?0:r(t)}},7466:function(e,t,n){var r=n(9303),o=Math.min;e.exports=function(e){return e>0?o(r(e),9007199254740991):0}},7908:function(e,t,n){var r=n(4488),o=Object;e.exports=function(e){return o(r(e))}},7593:function(e,t,n){var r=n(6916),o=n(111),i=n(2190),s=n(8173),c=n(2140),u=n(5112),l=TypeError,a=u("toPrimitive");e.exports=function(e,t){if(!o(e)||i(e))return e;var n,u=s(e,a);if(u){if(void 0===t&&(t="default"),n=r(u,e,t),!o(n)||i(n))return n;throw l("Can't convert object to primitive value")}return void 0===t&&(t="number"),c(e,t)}},4948:function(e,t,n){var r=n(7593),o=n(2190);e.exports=function(e){var t=r(e,"string");return o(t)?t:t+""}},1694:function(e,t,n){var r=n(5112),o=r("toStringTag"),i={};i[o]="z",e.exports="[object z]"===String(i)},1340:function(e,t,n){var r=n(648),o=String;e.exports=function(e){if("Symbol"===r(e))throw TypeError("Cannot convert a Symbol value to a string");return o(e)}},6330:function(e){var t=String;e.exports=function(e){try{return t(e)}catch(n){return"Object"}}},9711:function(e,t,n){var r=n(1702),o=0,i=Math.random(),s=r(1..toString);e.exports=function(e){return"Symbol("+(void 0===e?"":e)+")_"+s(++o+i,36)}},3307:function(e,t,n){var r=n(133);e.exports=r&&!Symbol.sham&&"symbol"==typeof Symbol.iterator},3353:function(e,t,n){var r=n(9781),o=n(7293);e.exports=r&&o((function(){return 42!=Object.defineProperty((function(){}),"prototype",{value:42,writable:!1}).prototype}))},5112:function(e,t,n){var r=n(7854),o=n(2309),i=n(2597),s=n(9711),c=n(133),u=n(3307),l=o("wks"),a=r.Symbol,f=a&&a["for"],p=u?a:a&&a.withoutSetter||s;e.exports=function(e){if(!i(l,e)||!c&&"string"!=typeof l[e]){var t="Symbol."+e;c&&i(a,e)?l[e]=a[e]:l[e]=u&&f?f(t):p(t)}return l[e]}},6992:function(e,t,n){"use strict";var r=n(5656),o=n(1223),i=n(7497),s=n(9909),c=n(3070).f,u=n(654),l=n(1913),a=n(9781),f="Array Iterator",p=s.set,d=s.getterFor(f);e.exports=u(Array,"Array",(function(e,t){p(this,{type:f,target:r(e),index:0,kind:t})}),(function(){var e=d(this),t=e.target,n=e.kind,r=e.index++;return!t||r>=t.length?(e.target=void 0,{value:void 0,done:!0}):"keys"==n?{value:r,done:!1}:"values"==n?{value:t[r],done:!1}:{value:[r,t[r]],done:!1}}),"values");var h=i.Arguments=i.Array;if(o("keys"),o("values"),o("entries"),!l&&a&&"values"!==h.name)try{c(h,"name",{value:"values"})}catch(v){}},7727:function(e,t,n){"use strict";var r=n(2109),o=n(1913),i=n(2492),s=n(7293),c=n(5005),u=n(614),l=n(6707),a=n(9478),f=n(8052),p=i&&i.prototype,d=!!i&&s((function(){p["finally"].call({then:function(){}},(function(){}))}));if(r({target:"Promise",proto:!0,real:!0,forced:d},{finally:function(e){var t=l(this,c("Promise")),n=u(e);return this.then(n?function(n){return a(t,e()).then((function(){return n}))}:e,n?function(n){return a(t,e()).then((function(){throw n}))}:e)}}),!o&&u(i)){var h=c("Promise").prototype["finally"];p["finally"]!==h&&f(p,"finally",h,{unsafe:!0})}},4916:function(e,t,n){"use strict";var r=n(2109),o=n(2261);r({target:"RegExp",proto:!0,forced:/./.exec!==o},{exec:o})},5306:function(e,t,n){"use strict";var r=n(2104),o=n(6916),i=n(1702),s=n(7007),c=n(7293),u=n(9670),l=n(614),a=n(9303),f=n(7466),p=n(1340),d=n(4488),h=n(1530),v=n(8173),g=n(647),m=n(7651),y=n(5112),b=y("replace"),_=Math.max,x=Math.min,w=i([].concat),k=i([].push),S=i("".indexOf),C=i("".slice),O=function(e){return void 0===e?e:String(e)},E=function(){return"$0"==="a".replace(/./,"$0")}(),A=function(){return!!/./[b]&&""===/./[b]("a","$0")}(),R=!c((function(){var e=/./;return e.exec=function(){var e=[];return e.groups={a:"7"},e},"7"!=="".replace(e,"$")}));s("replace",(function(e,t,n){var i=A?"$":"$0";return[function(e,n){var r=d(this),i=void 0==e?void 0:v(e,b);return i?o(i,e,r,n):o(t,p(r),e,n)},function(e,o){var s=u(this),c=p(e);if("string"==typeof o&&-1===S(o,i)&&-1===S(o,"$<")){var d=n(t,s,c,o);if(d.done)return d.value}var v=l(o);v||(o=p(o));var y=s.global;if(y){var b=s.unicode;s.lastIndex=0}var E=[];while(1){var A=m(s,c);if(null===A)break;if(k(E,A),!y)break;var R=p(A[0]);""===R&&(s.lastIndex=h(c,f(s.lastIndex),b))}for(var I="",P=0,T=0;T=P&&(I+=C(c,P,F)+N,P=F+j.length)}return I+C(c,P)}]}),!R||!E||A)},3948:function(e,t,n){var r=n(7854),o=n(8324),i=n(8509),s=n(6992),c=n(8880),u=n(5112),l=u("iterator"),a=u("toStringTag"),f=s.values,p=function(e,t){if(e){if(e[l]!==f)try{c(e,l,f)}catch(r){e[l]=f}if(e[a]||c(e,a,t),o[t])for(var n in s)if(e[n]!==s[n])try{c(e,n,s[n])}catch(r){e[n]=s[n]}}};for(var d in o)p(r[d]&&r[d].prototype,d);p(i,"DOMTokenList")},3744:function(e,t){"use strict";t.Z=(e,t)=>{const n=e.__vccOpts||e;for(const[r,o]of t)n[r]=o;return n}},2119:function(e,t,n){"use strict";n.d(t,{PO:function(){return q},p7:function(){return nt}});var r=n(6252),o=n(2262); +/*! + * vue-router v4.0.16 + * (c) 2022 Eduardo San Martin Morote + * @license MIT + */ +const i="function"===typeof Symbol&&"symbol"===typeof Symbol.toStringTag,s=e=>i?Symbol(e):"_vr_"+e,c=s("rvlm"),u=s("rvd"),l=s("r"),a=s("rl"),f=s("rvl"),p="undefined"!==typeof window;function d(e){return e.__esModule||i&&"Module"===e[Symbol.toStringTag]}const h=Object.assign;function v(e,t){const n={};for(const r in t){const o=t[r];n[r]=Array.isArray(o)?o.map(e):e(o)}return n}const g=()=>{};const m=/\/$/,y=e=>e.replace(m,"");function b(e,t,n="/"){let r,o={},i="",s="";const c=t.indexOf("?"),u=t.indexOf("#",c>-1?c:0);return c>-1&&(r=t.slice(0,c),i=t.slice(c+1,u>-1?u:t.length),o=e(i)),u>-1&&(r=r||t.slice(0,u),s=t.slice(u,t.length)),r=E(null!=r?r:t,n),{fullPath:r+(i&&"?")+i+s,path:r,query:o,hash:s}}function _(e,t){const n=t.query?e(t.query):"";return t.path+(n&&"?")+n+(t.hash||"")}function x(e,t){return t&&e.toLowerCase().startsWith(t.toLowerCase())?e.slice(t.length)||"/":e}function w(e,t,n){const r=t.matched.length-1,o=n.matched.length-1;return r>-1&&r===o&&k(t.matched[r],n.matched[o])&&S(t.params,n.params)&&e(t.query)===e(n.query)&&t.hash===n.hash}function k(e,t){return(e.aliasOf||e)===(t.aliasOf||t)}function S(e,t){if(Object.keys(e).length!==Object.keys(t).length)return!1;for(const n in e)if(!C(e[n],t[n]))return!1;return!0}function C(e,t){return Array.isArray(e)?O(e,t):Array.isArray(t)?O(t,e):e===t}function O(e,t){return Array.isArray(t)?e.length===t.length&&e.every(((e,n)=>e===t[n])):1===e.length&&e[0]===t}function E(e,t){if(e.startsWith("/"))return e;if(!e)return t;const n=t.split("/"),r=e.split("/");let o,i,s=n.length-1;for(o=0;o({left:window.pageXOffset,top:window.pageYOffset});function M(e){let t;if("el"in e){const n=e.el,r="string"===typeof n&&n.startsWith("#");0;const o="string"===typeof n?r?document.getElementById(n.slice(1)):document.querySelector(n):n;if(!o)return;t=j(o,e)}else t=e;"scrollBehavior"in document.documentElement.style?window.scrollTo(t):window.scrollTo(null!=t.left?t.left:window.pageXOffset,null!=t.top?t.top:window.pageYOffset)}function L(e,t){const n=history.state?history.state.position-t:-1;return n+e}const $=new Map;function U(e,t){$.set(e,t)}function N(e){const t=$.get(e);return $.delete(e),t}let D=()=>location.protocol+"//"+location.host;function J(e,t){const{pathname:n,search:r,hash:o}=t,i=e.indexOf("#");if(i>-1){let t=o.includes(e.slice(i))?e.slice(i).length:1,n=o.slice(t);return"/"!==n[0]&&(n="/"+n),x(n,"")}const s=x(n,e);return s+r+o}function B(e,t,n,r){let o=[],i=[],s=null;const c=({state:i})=>{const c=J(e,location),u=n.value,l=t.value;let a=0;if(i){if(n.value=c,t.value=i,s&&s===u)return void(s=null);a=l?i.position-l.position:0}else r(c);o.forEach((e=>{e(n.value,u,{delta:a,type:A.pop,direction:a?a>0?R.forward:R.back:R.unknown})}))};function u(){s=n.value}function l(e){o.push(e);const t=()=>{const t=o.indexOf(e);t>-1&&o.splice(t,1)};return i.push(t),t}function a(){const{history:e}=window;e.state&&e.replaceState(h({},e.state,{scroll:F()}),"")}function f(){for(const e of i)e();i=[],window.removeEventListener("popstate",c),window.removeEventListener("beforeunload",a)}return window.addEventListener("popstate",c),window.addEventListener("beforeunload",a),{pauseListeners:u,listen:l,destroy:f}}function V(e,t,n,r=!1,o=!1){return{back:e,current:t,forward:n,replaced:r,position:window.history.length,scroll:o?F():null}}function G(e){const{history:t,location:n}=window,r={value:J(e,n)},o={value:t.state};function i(r,i,s){const c=e.indexOf("#"),u=c>-1?(n.host&&document.querySelector("base")?e:e.slice(c))+r:D()+e+r;try{t[s?"replaceState":"pushState"](i,"",u),o.value=i}catch(l){console.error(l),n[s?"replace":"assign"](u)}}function s(e,n){const s=h({},t.state,V(o.value.back,e,o.value.forward,!0),n,{position:o.value.position});i(e,s,!0),r.value=e}function c(e,n){const s=h({},o.value,t.state,{forward:e,scroll:F()});i(s.current,s,!0);const c=h({},V(r.value,e,null),{position:s.position+1},n);i(e,c,!1),r.value=e}return o.value||i(r.value,{back:null,current:r.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0),{location:r,state:o,push:c,replace:s}}function q(e){e=I(e);const t=G(e),n=B(e,t.state,t.location,t.replace);function r(e,t=!0){t||n.pauseListeners(),history.go(e)}const o=h({location:"",base:e,go:r,createHref:T.bind(null,e)},t,n);return Object.defineProperty(o,"location",{enumerable:!0,get:()=>t.location.value}),Object.defineProperty(o,"state",{enumerable:!0,get:()=>t.state.value}),o}function z(e){return"string"===typeof e||e&&"object"===typeof e}function H(e){return"string"===typeof e||"symbol"===typeof e}const W={path:"/",name:void 0,params:{},query:{},hash:"",fullPath:"/",matched:[],meta:{},redirectedFrom:void 0},K=s("nf");var Z;(function(e){e[e["aborted"]=4]="aborted",e[e["cancelled"]=8]="cancelled",e[e["duplicated"]=16]="duplicated"})(Z||(Z={}));function X(e,t){return h(new Error,{type:e,[K]:!0},t)}function Y(e,t){return e instanceof Error&&K in e&&(null==t||!!(e.type&t))}const Q="[^/]+?",ee={sensitive:!1,strict:!1,start:!0,end:!0},te=/[.+*?^${}()[\]/\\]/g;function ne(e,t){const n=h({},ee,t),r=[];let o=n.start?"^":"";const i=[];for(const a of e){const e=a.length?[]:[90];n.strict&&!a.length&&(o+="/");for(let t=0;t1&&(n.endsWith("/")?n=n.slice(0,-1):r=!0)}n+=a}}return n}return{re:s,score:r,keys:i,parse:c,stringify:u}}function re(e,t){let n=0;while(nt.length?1===t.length&&80===t[0]?1:-1:0}function oe(e,t){let n=0;const r=e.score,o=t.score;while(n0&&t[t.length-1]<0}const se={type:0,value:""},ce=/[a-zA-Z0-9_]/;function ue(e){if(!e)return[[]];if("/"===e)return[[se]];if(!e.startsWith("/"))throw new Error(`Invalid path "${e}"`);function t(e){throw new Error(`ERR (${n})/"${l}": ${e}`)}let n=0,r=n;const o=[];let i;function s(){i&&o.push(i),i=[]}let c,u=0,l="",a="";function f(){l&&(0===n?i.push({type:0,value:l}):1===n||2===n||3===n?(i.length>1&&("*"===c||"+"===c)&&t(`A repeatable param (${l}) must be alone in its segment. eg: '/:ids+.`),i.push({type:1,value:l,regexp:a,repeatable:"*"===c||"+"===c,optional:"*"===c||"?"===c})):t("Invalid state to consume buffer"),l="")}function p(){l+=c}while(u{s(p)}:g}function s(e){if(H(e)){const t=r.get(e);t&&(r.delete(e),n.splice(n.indexOf(t),1),t.children.forEach(s),t.alias.forEach(s))}else{const t=n.indexOf(e);t>-1&&(n.splice(t,1),e.record.name&&r.delete(e.record.name),e.children.forEach(s),e.alias.forEach(s))}}function c(){return n}function u(e){let t=0;while(t=0&&(e.record.path!==n[t].record.path||!me(e,n[t])))t++;n.splice(t,0,e),e.record.name&&!he(e)&&r.set(e.record.name,e)}function l(e,t){let o,i,s,c={};if("name"in e&&e.name){if(o=r.get(e.name),!o)throw X(1,{location:e});s=o.record.name,c=h(fe(t.params,o.keys.filter((e=>!e.optional)).map((e=>e.name))),e.params),i=o.stringify(c)}else if("path"in e)i=e.path,o=n.find((e=>e.re.test(i))),o&&(c=o.parse(i),s=o.record.name);else{if(o=t.name?r.get(t.name):n.find((e=>e.re.test(t.path))),!o)throw X(1,{location:e,currentLocation:t});s=o.record.name,c=h({},t.params,e.params),i=o.stringify(c)}const u=[];let l=o;while(l)u.unshift(l.record),l=l.parent;return{name:s,path:i,params:c,matched:u,meta:ve(u)}}return t=ge({strict:!1,end:!0,sensitive:!1},t),e.forEach((e=>i(e))),{addRoute:i,resolve:l,removeRoute:s,getRoutes:c,getRecordMatcher:o}}function fe(e,t){const n={};for(const r of t)r in e&&(n[r]=e[r]);return n}function pe(e){return{path:e.path,redirect:e.redirect,name:e.name,meta:e.meta||{},aliasOf:void 0,beforeEnter:e.beforeEnter,props:de(e),children:e.children||[],instances:{},leaveGuards:new Set,updateGuards:new Set,enterCallbacks:{},components:"components"in e?e.components||{}:{default:e.component}}}function de(e){const t={},n=e.props||!1;if("component"in e)t.default=n;else for(const r in e.components)t[r]="boolean"===typeof n?n:n[r];return t}function he(e){while(e){if(e.record.aliasOf)return!0;e=e.parent}return!1}function ve(e){return e.reduce(((e,t)=>h(e,t.meta)),{})}function ge(e,t){const n={};for(const r in e)n[r]=r in t?t[r]:e[r];return n}function me(e,t){return t.children.some((t=>t===e||me(e,t)))}const ye=/#/g,be=/&/g,_e=/\//g,xe=/=/g,we=/\?/g,ke=/\+/g,Se=/%5B/g,Ce=/%5D/g,Oe=/%5E/g,Ee=/%60/g,Ae=/%7B/g,Re=/%7C/g,Ie=/%7D/g,Pe=/%20/g;function Te(e){return encodeURI(""+e).replace(Re,"|").replace(Se,"[").replace(Ce,"]")}function je(e){return Te(e).replace(Ae,"{").replace(Ie,"}").replace(Oe,"^")}function Fe(e){return Te(e).replace(ke,"%2B").replace(Pe,"+").replace(ye,"%23").replace(be,"%26").replace(Ee,"`").replace(Ae,"{").replace(Ie,"}").replace(Oe,"^")}function Me(e){return Fe(e).replace(xe,"%3D")}function Le(e){return Te(e).replace(ye,"%23").replace(we,"%3F")}function $e(e){return null==e?"":Le(e).replace(_e,"%2F")}function Ue(e){try{return decodeURIComponent(""+e)}catch(t){}return""+e}function Ne(e){const t={};if(""===e||"?"===e)return t;const n="?"===e[0],r=(n?e.slice(1):e).split("&");for(let o=0;oe&&Fe(e))):[r&&Fe(r)];o.forEach((e=>{void 0!==e&&(t+=(t.length?"&":"")+n,null!=e&&(t+="="+e))}))}return t}function Je(e){const t={};for(const n in e){const r=e[n];void 0!==r&&(t[n]=Array.isArray(r)?r.map((e=>null==e?null:""+e)):null==r?r:""+r)}return t}function Be(){let e=[];function t(t){return e.push(t),()=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)}}function n(){e=[]}return{add:t,list:()=>e,reset:n}}function Ve(e,t,n,r,o){const i=r&&(r.enterCallbacks[o]=r.enterCallbacks[o]||[]);return()=>new Promise(((s,c)=>{const u=e=>{!1===e?c(X(4,{from:n,to:t})):e instanceof Error?c(e):z(e)?c(X(2,{from:t,to:e})):(i&&r.enterCallbacks[o]===i&&"function"===typeof e&&i.push(e),s())},l=e.call(r&&r.instances[o],t,n,u);let a=Promise.resolve(l);e.length<3&&(a=a.then(u)),a.catch((e=>c(e)))}))}function Ge(e,t,n,r){const o=[];for(const i of e)for(const e in i.components){let s=i.components[e];if("beforeRouteEnter"===t||i.instances[e])if(qe(s)){const c=s.__vccOpts||s,u=c[t];u&&o.push(Ve(u,n,r,i,e))}else{let c=s();0,o.push((()=>c.then((o=>{if(!o)return Promise.reject(new Error(`Couldn't resolve component "${e}" at "${i.path}"`));const s=d(o)?o.default:o;i.components[e]=s;const c=s.__vccOpts||s,u=c[t];return u&&Ve(u,n,r,i,e)()}))))}}return o}function qe(e){return"object"===typeof e||"displayName"in e||"props"in e||"__vccOpts"in e}function ze(e){const t=(0,r.f3)(l),n=(0,r.f3)(a),i=(0,r.Fl)((()=>t.resolve((0,o.SU)(e.to)))),s=(0,r.Fl)((()=>{const{matched:e}=i.value,{length:t}=e,r=e[t-1],o=n.matched;if(!r||!o.length)return-1;const s=o.findIndex(k.bind(null,r));if(s>-1)return s;const c=Xe(e[t-2]);return t>1&&Xe(r)===c&&o[o.length-1].path!==c?o.findIndex(k.bind(null,e[t-2])):s})),c=(0,r.Fl)((()=>s.value>-1&&Ze(n.params,i.value.params))),u=(0,r.Fl)((()=>s.value>-1&&s.value===n.matched.length-1&&S(n.params,i.value.params)));function f(n={}){return Ke(n)?t[(0,o.SU)(e.replace)?"replace":"push"]((0,o.SU)(e.to)).catch(g):Promise.resolve()}return{route:i,href:(0,r.Fl)((()=>i.value.href)),isActive:c,isExactActive:u,navigate:f}}const He=(0,r.aZ)({name:"RouterLink",compatConfig:{MODE:3},props:{to:{type:[String,Object],required:!0},replace:Boolean,activeClass:String,exactActiveClass:String,custom:Boolean,ariaCurrentValue:{type:String,default:"page"}},useLink:ze,setup(e,{slots:t}){const n=(0,o.qj)(ze(e)),{options:i}=(0,r.f3)(l),s=(0,r.Fl)((()=>({[Ye(e.activeClass,i.linkActiveClass,"router-link-active")]:n.isActive,[Ye(e.exactActiveClass,i.linkExactActiveClass,"router-link-exact-active")]:n.isExactActive})));return()=>{const o=t.default&&t.default(n);return e.custom?o:(0,r.h)("a",{"aria-current":n.isExactActive?e.ariaCurrentValue:null,href:n.href,onClick:n.navigate,class:s.value},o)}}}),We=He;function Ke(e){if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defaultPrevented&&(void 0===e.button||0===e.button)){if(e.currentTarget&&e.currentTarget.getAttribute){const t=e.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(t))return}return e.preventDefault&&e.preventDefault(),!0}}function Ze(e,t){for(const n in t){const r=t[n],o=e[n];if("string"===typeof r){if(r!==o)return!1}else if(!Array.isArray(o)||o.length!==r.length||r.some(((e,t)=>e!==o[t])))return!1}return!0}function Xe(e){return e?e.aliasOf?e.aliasOf.path:e.path:""}const Ye=(e,t,n)=>null!=e?e:null!=t?t:n,Qe=(0,r.aZ)({name:"RouterView",inheritAttrs:!1,props:{name:{type:String,default:"default"},route:Object},compatConfig:{MODE:3},setup(e,{attrs:t,slots:n}){const i=(0,r.f3)(f),s=(0,r.Fl)((()=>e.route||i.value)),l=(0,r.f3)(u,0),a=(0,r.Fl)((()=>s.value.matched[l]));(0,r.JJ)(u,l+1),(0,r.JJ)(c,a),(0,r.JJ)(f,s);const p=(0,o.iH)();return(0,r.YP)((()=>[p.value,a.value,e.name]),(([e,t,n],[r,o,i])=>{t&&(t.instances[n]=e,o&&o!==t&&e&&e===r&&(t.leaveGuards.size||(t.leaveGuards=o.leaveGuards),t.updateGuards.size||(t.updateGuards=o.updateGuards))),!e||!t||o&&k(t,o)&&r||(t.enterCallbacks[n]||[]).forEach((t=>t(e)))}),{flush:"post"}),()=>{const o=s.value,i=a.value,c=i&&i.components[e.name],u=e.name;if(!c)return et(n.default,{Component:c,route:o});const l=i.props[e.name],f=l?!0===l?o.params:"function"===typeof l?l(o):l:null,d=e=>{e.component.isUnmounted&&(i.instances[u]=null)},v=(0,r.h)(c,h({},f,t,{onVnodeUnmounted:d,ref:p}));return et(n.default,{Component:v,route:o})||v}}});function et(e,t){if(!e)return null;const n=e(t);return 1===n.length?n[0]:n}const tt=Qe;function nt(e){const t=ae(e.routes,e),n=e.parseQuery||Ne,i=e.stringifyQuery||De,s=e.history;const c=Be(),u=Be(),d=Be(),m=(0,o.XI)(W);let y=W;p&&e.scrollBehavior&&"scrollRestoration"in history&&(history.scrollRestoration="manual");const x=v.bind(null,(e=>""+e)),k=v.bind(null,$e),S=v.bind(null,Ue);function C(e,n){let r,o;return H(e)?(r=t.getRecordMatcher(e),o=n):o=e,t.addRoute(o,r)}function O(e){const n=t.getRecordMatcher(e);n&&t.removeRoute(n)}function E(){return t.getRoutes().map((e=>e.record))}function R(e){return!!t.getRecordMatcher(e)}function I(e,r){if(r=h({},r||m.value),"string"===typeof e){const o=b(n,e,r.path),i=t.resolve({path:o.path},r),c=s.createHref(o.fullPath);return h(o,i,{params:S(i.params),hash:Ue(o.hash),redirectedFrom:void 0,href:c})}let o;if("path"in e)o=h({},e,{path:b(n,e.path,r.path).path});else{const t=h({},e.params);for(const e in t)null==t[e]&&delete t[e];o=h({},e,{params:k(e.params)}),r.params=k(r.params)}const c=t.resolve(o,r),u=e.hash||"";c.params=x(S(c.params));const l=_(i,h({},e,{hash:je(u),path:c.path})),a=s.createHref(l);return h({fullPath:l,hash:u,query:i===De?Je(e.query):e.query||{}},c,{redirectedFrom:void 0,href:a})}function P(e){return"string"===typeof e?b(n,e,m.value.path):h({},e)}function T(e,t){if(y!==e)return X(8,{from:t,to:e})}function j(e){return J(e)}function $(e){return j(h(P(e),{replace:!0}))}function D(e){const t=e.matched[e.matched.length-1];if(t&&t.redirect){const{redirect:n}=t;let r="function"===typeof n?n(e):n;return"string"===typeof r&&(r=r.includes("?")||r.includes("#")?r=P(r):{path:r},r.params={}),h({query:e.query,hash:e.hash,params:e.params},r)}}function J(e,t){const n=y=I(e),r=m.value,o=e.state,s=e.force,c=!0===e.replace,u=D(n);if(u)return J(h(P(u),{state:o,force:s,replace:c}),t||n);const l=n;let a;return l.redirectedFrom=t,!s&&w(i,r,n)&&(a=X(16,{to:l,from:r}),oe(r,r,!0,!1)),(a?Promise.resolve(a):V(l,r)).catch((e=>Y(e)?Y(e,2)?e:re(e):te(e,l,r))).then((e=>{if(e){if(Y(e,2))return J(h(P(e.to),{state:o,force:s,replace:c}),t||l)}else e=q(l,r,!0,c,o);return G(l,r,e),e}))}function B(e,t){const n=T(e,t);return n?Promise.reject(n):Promise.resolve()}function V(e,t){let n;const[r,o,i]=ot(e,t);n=Ge(r.reverse(),"beforeRouteLeave",e,t);for(const c of r)c.leaveGuards.forEach((r=>{n.push(Ve(r,e,t))}));const s=B.bind(null,e,t);return n.push(s),rt(n).then((()=>{n=[];for(const r of c.list())n.push(Ve(r,e,t));return n.push(s),rt(n)})).then((()=>{n=Ge(o,"beforeRouteUpdate",e,t);for(const r of o)r.updateGuards.forEach((r=>{n.push(Ve(r,e,t))}));return n.push(s),rt(n)})).then((()=>{n=[];for(const r of e.matched)if(r.beforeEnter&&!t.matched.includes(r))if(Array.isArray(r.beforeEnter))for(const o of r.beforeEnter)n.push(Ve(o,e,t));else n.push(Ve(r.beforeEnter,e,t));return n.push(s),rt(n)})).then((()=>(e.matched.forEach((e=>e.enterCallbacks={})),n=Ge(i,"beforeRouteEnter",e,t),n.push(s),rt(n)))).then((()=>{n=[];for(const r of u.list())n.push(Ve(r,e,t));return n.push(s),rt(n)})).catch((e=>Y(e,8)?e:Promise.reject(e)))}function G(e,t,n){for(const r of d.list())r(e,t,n)}function q(e,t,n,r,o){const i=T(e,t);if(i)return i;const c=t===W,u=p?history.state:{};n&&(r||c?s.replace(e.fullPath,h({scroll:c&&u&&u.scroll},o)):s.push(e.fullPath,o)),m.value=e,oe(e,t,n,c),re()}let z;function K(){z||(z=s.listen(((e,t,n)=>{const r=I(e),o=D(r);if(o)return void J(h(o,{replace:!0}),r).catch(g);y=r;const i=m.value;p&&U(L(i.fullPath,n.delta),F()),V(r,i).catch((e=>Y(e,12)?e:Y(e,2)?(J(e.to,r).then((e=>{Y(e,20)&&!n.delta&&n.type===A.pop&&s.go(-1,!1)})).catch(g),Promise.reject()):(n.delta&&s.go(-n.delta,!1),te(e,r,i)))).then((e=>{e=e||q(r,i,!1),e&&(n.delta?s.go(-n.delta,!1):n.type===A.pop&&Y(e,20)&&s.go(-1,!1)),G(r,i,e)})).catch(g)})))}let Z,Q=Be(),ee=Be();function te(e,t,n){re(e);const r=ee.list();return r.length?r.forEach((r=>r(e,t,n))):console.error(e),Promise.reject(e)}function ne(){return Z&&m.value!==W?Promise.resolve():new Promise(((e,t)=>{Q.add([e,t])}))}function re(e){return Z||(Z=!e,K(),Q.list().forEach((([t,n])=>e?n(e):t())),Q.reset()),e}function oe(t,n,o,i){const{scrollBehavior:s}=e;if(!p||!s)return Promise.resolve();const c=!o&&N(L(t.fullPath,0))||(i||!o)&&history.state&&history.state.scroll||null;return(0,r.Y3)().then((()=>s(t,n,c))).then((e=>e&&M(e))).catch((e=>te(e,t,n)))}const ie=e=>s.go(e);let se;const ce=new Set,ue={currentRoute:m,addRoute:C,removeRoute:O,hasRoute:R,getRoutes:E,resolve:I,options:e,push:j,replace:$,go:ie,back:()=>ie(-1),forward:()=>ie(1),beforeEach:c.add,beforeResolve:u.add,afterEach:d.add,onError:ee.add,isReady:ne,install(e){const t=this;e.component("RouterLink",We),e.component("RouterView",tt),e.config.globalProperties.$router=t,Object.defineProperty(e.config.globalProperties,"$route",{enumerable:!0,get:()=>(0,o.SU)(m)}),p&&!se&&m.value===W&&(se=!0,j(s.location).catch((e=>{0})));const n={};for(const o in W)n[o]=(0,r.Fl)((()=>m.value[o]));e.provide(l,t),e.provide(a,(0,o.qj)(n)),e.provide(f,m);const i=e.unmount;ce.add(e),e.unmount=function(){ce.delete(e),ce.size<1&&(y=W,z&&z(),z=null,m.value=W,se=!1,Z=!1),i()}}};return ue}function rt(e){return e.reduce(((e,t)=>e.then((()=>t()))),Promise.resolve())}function ot(e,t){const n=[],r=[],o=[],i=Math.max(t.matched.length,e.matched.length);for(let s=0;sk(e,i)))?r.push(i):n.push(i));const c=e.matched[s];c&&(t.matched.find((e=>k(e,c)))||o.push(c))}return[n,r,o]}},9505:function(e,t,n){"use strict";n.d(t,{Z:function(){return o}});var r=n(6252);function o(e,t){return(0,r.wg)(),(0,r.iD)("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",fill:"currentColor","aria-hidden":"true"},[(0,r._)("path",{"fill-rule":"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zm.75-11.25a.75.75 0 00-1.5 0v4.59L7.3 9.24a.75.75 0 00-1.1 1.02l3.25 3.5a.75.75 0 001.1 0l3.25-3.5a.75.75 0 10-1.1-1.02l-1.95 2.1V6.75z","clip-rule":"evenodd"})])}},7163:function(e,t,n){"use strict";n.d(t,{Z:function(){return o}});var r=n(6252);function o(e,t){return(0,r.wg)(),(0,r.iD)("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",fill:"currentColor","aria-hidden":"true"},[(0,r._)("path",{"fill-rule":"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zm-.75-4.75a.75.75 0 001.5 0V8.66l1.95 2.1a.75.75 0 101.1-1.02l-3.25-3.5a.75.75 0 00-1.1 0L6.2 9.74a.75.75 0 101.1 1.02l1.95-2.1v4.59z","clip-rule":"evenodd"})])}},1818:function(e,t,n){"use strict";n.d(t,{Z:function(){return o}});var r=n(6252);function o(e,t){return(0,r.wg)(),(0,r.iD)("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",fill:"currentColor","aria-hidden":"true"},[(0,r._)("path",{"fill-rule":"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z","clip-rule":"evenodd"})])}},6758:function(e,t,n){"use strict";n.d(t,{Z:function(){return o}});var r=n(6252);function o(e,t){return(0,r.wg)(),(0,r.iD)("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",fill:"currentColor","aria-hidden":"true"},[(0,r._)("path",{"fill-rule":"evenodd",d:"M7.455 2.004a.75.75 0 01.26.77 7 7 0 009.958 7.967.75.75 0 011.067.853A8.5 8.5 0 116.647 1.921a.75.75 0 01.808.083z","clip-rule":"evenodd"})])}},8585:function(e,t,n){"use strict";n.d(t,{Z:function(){return o}});var r=n(6252);function o(e,t){return(0,r.wg)(),(0,r.iD)("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",fill:"currentColor","aria-hidden":"true"},[(0,r._)("path",{"fill-rule":"evenodd",d:"M2 10a8 8 0 1116 0 8 8 0 01-16 0zm6.39-2.908a.75.75 0 01.766.027l3.5 2.25a.75.75 0 010 1.262l-3.5 2.25A.75.75 0 018 12.25v-4.5a.75.75 0 01.39-.658z","clip-rule":"evenodd"})])}},4913:function(e,t,n){"use strict";n.d(t,{Z:function(){return o}});var r=n(6252);function o(e,t){return(0,r.wg)(),(0,r.iD)("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",fill:"currentColor","aria-hidden":"true"},[(0,r._)("path",{d:"M10 2a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 2zM10 15a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 15zM10 7a3 3 0 100 6 3 3 0 000-6zM15.657 5.404a.75.75 0 10-1.06-1.06l-1.061 1.06a.75.75 0 001.06 1.06l1.06-1.06zM6.464 14.596a.75.75 0 10-1.06-1.06l-1.06 1.06a.75.75 0 001.06 1.06l1.06-1.06zM18 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 0118 10zM5 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 015 10zM14.596 15.657a.75.75 0 001.06-1.06l-1.06-1.061a.75.75 0 10-1.06 1.06l1.06 1.06zM5.404 6.464a.75.75 0 001.06-1.06l-1.06-1.06a.75.75 0 10-1.061 1.06l1.06 1.06z"})])}},7886:function(e,t,n){"use strict";n.d(t,{Z:function(){return o}});var r=n(6252);function o(e,t){return(0,r.wg)(),(0,r.iD)("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor","aria-hidden":"true"},[(0,r._)("path",{"fill-rule":"evenodd",d:"M4.755 10.059a7.5 7.5 0 0112.548-3.364l1.903 1.903h-3.183a.75.75 0 100 1.5h4.992a.75.75 0 00.75-.75V4.356a.75.75 0 00-1.5 0v3.18l-1.9-1.9A9 9 0 003.306 9.67a.75.75 0 101.45.388zm15.408 3.352a.75.75 0 00-.919.53 7.5 7.5 0 01-12.548 3.364l-1.902-1.903h3.183a.75.75 0 000-1.5H2.984a.75.75 0 00-.75.75v4.992a.75.75 0 001.5 0v-3.18l1.9 1.9a9 9 0 0015.059-4.035.75.75 0 00-.53-.918z","clip-rule":"evenodd"})])}}}]); \ No newline at end of file diff --git a/web/static/logo-192x192.png b/web/static/logo-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..272a45b8587b1c491f9a8a78b1bba30519d07df2 GIT binary patch literal 8659 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 IPvNisAHWa`tN;K2 literal 0 HcmV?d00001 diff --git a/web/static/manifest.json b/web/static/manifest.json new file mode 100644 index 00000000..d0353120 --- /dev/null +++ b/web/static/manifest.json @@ -0,0 +1,24 @@ +{ + "id": "gatus", + "name": "Gatus", + "short_name": "Gatus", + "description": "Gatus is an advanced automated status page that lets you monitor your applications and configure alerts to notify you if there's an issue", + "lang": "en", + "scope": "/", + "start_url": "/", + "theme_color": "#f7f9fb", + "background_color": "#f7f9fb", + "display": "standalone", + "icons": [ + { + "src": "/logo-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/logo-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/web/static_test.go b/web/static_test.go new file mode 100644 index 00000000..a8b79ade --- /dev/null +++ b/web/static_test.go @@ -0,0 +1,74 @@ +package static + +import ( + "io/fs" + "strings" + "testing" +) + +func TestEmbed(t *testing.T) { + scenarios := []struct { + path string + shouldExist bool + expectedContainString string + }{ + { + path: "index.html", + shouldExist: true, + expectedContainString: "", + }, + { + path: "favicon.ico", + shouldExist: true, + expectedContainString: "", // not checking because it's an image + }, + { + path: "img/logo.svg", + shouldExist: true, + expectedContainString: "", + }, + { + path: "css/app.css", + shouldExist: true, + expectedContainString: "background-color", + }, + { + path: "js/app.js", + shouldExist: true, + expectedContainString: "function", + }, + { + path: "js/chunk-vendors.js", + shouldExist: true, + expectedContainString: "function", + }, + { + path: "file-that-does-not-exist.html", + shouldExist: false, + }, + } + staticFileSystem, err := fs.Sub(FileSystem, RootPath) + if err != nil { + t.Fatal(err) + } + for _, scenario := range scenarios { + t.Run(scenario.path, func(t *testing.T) { + content, err := fs.ReadFile(staticFileSystem, scenario.path) + if !scenario.shouldExist { + if err == nil { + t.Errorf("%s should not have existed", scenario.path) + } + } else { + if err != nil { + t.Errorf("opening %s should not have returned an error, got %s", scenario.path, err.Error()) + } + if len(content) == 0 { + t.Errorf("%s should have existed in the static FileSystem, but was empty", scenario.path) + } + if !strings.Contains(string(content), scenario.expectedContainString) { + t.Errorf("%s should have contained %s, but did not", scenario.path, scenario.expectedContainString) + } + } + }) + } +}

R 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