From 412872b07f357ca52cef493c7a8d25d2ec0400b3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 08:46:04 +0000 Subject: [PATCH 01/11] Initial plan From eaebbe64c6c1fc18f67de156559b4059c8919a6d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 08:52:19 +0000 Subject: [PATCH 02/11] Update flagsmith-common to v2.2.2 Co-authored-by: gagantrivedi <18366226+gagantrivedi@users.noreply.github.com> --- api/poetry.lock | 31 ++++++++++++++++++++++++++----- api/pyproject.toml | 2 +- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/api/poetry.lock b/api/poetry.lock index a1dfdd961ee6..ecee29066d27 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "annotated-types" @@ -1992,14 +1992,14 @@ resolved_reference = "303954ba54fd2f402c75806b3b9ba7f2d42aa426" [[package]] name = "flagsmith-common" -version = "2.2.0" +version = "2.2.2" description = "Flagsmith's common library" optional = false python-versions = "<4.0,>=3.11" groups = ["main", "dev", "split-testing", "workflows"] files = [ - {file = "flagsmith_common-2.2.0-py3-none-any.whl", hash = "sha256:c9ccfd9dd3d5be4ec2539d26e45688ce1038f4b5530816fee043052b1c8c0c9d"}, - {file = "flagsmith_common-2.2.0.tar.gz", hash = "sha256:6b7e959a402732600a16151814f76e56477ea02a3ae70719f78986fc3f559225"}, + {file = "flagsmith_common-2.2.2-py3-none-any.whl", hash = "sha256:61da597760c36740bd2f9803616d05e09a2842c6a7a381b581623d20880d2659"}, + {file = "flagsmith_common-2.2.2.tar.gz", hash = "sha256:28f2197bfa3df2b236b146f338193ecf7f9c2108f0ed1f281771426173b7db61"}, ] [package.dependencies] @@ -2866,6 +2866,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -4286,6 +4296,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -4293,8 +4304,16 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -4311,6 +4330,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -4318,6 +4338,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -5451,4 +5472,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = ">3.11,<3.13" -content-hash = "140218aab1ec3b021a8aecbfc893c1fcac62b4e11e8c2bd925831633f3aee0e2" +content-hash = "dc63ed394893ce05624753321f54892a640fc5b7126e08b58c4fc7fbae32e039" diff --git a/api/pyproject.toml b/api/pyproject.toml index 494fe2d168ca..8ee3b41fed22 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -163,7 +163,7 @@ pygithub = "2.1.1" hubspot-api-client = "^8.2.1" djangorestframework-dataclasses = "^1.3.1" pyotp = "^2.9.0" -flagsmith-common = "^2.2.0" +flagsmith-common = "^2.2.2" django-stubs = "^5.1.3" tzdata = "^2024.1" djangorestframework-simplejwt = "^5.5.1" From bf03f999ea4dde42c9cc4da415ee08b75954e43d Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Mon, 27 Oct 2025 08:54:26 +0000 Subject: [PATCH 03/11] docs: update release pipelines beta status (#6199) --- docs/docs/advanced-use/release-pipelines.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/docs/advanced-use/release-pipelines.md b/docs/docs/advanced-use/release-pipelines.md index 1a420516ac42..6ed7b7969e4c 100644 --- a/docs/docs/advanced-use/release-pipelines.md +++ b/docs/docs/advanced-use/release-pipelines.md @@ -1,8 +1,10 @@ # Release Pipelines -:::warning +:::info + +Release Pipelines is currently in a closed beta, please contact us if you'd like to gain access. -Release Pipelines is currenlty in alpha phase and is not available to the public. +Contact Us ::: From 98ed3fb82693193648301e2a509a21bcd0f8b902 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 12:25:11 +0530 Subject: [PATCH 04/11] ci: pre-commit autoupdate (#6208) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4b974122382c..afde79b11e02 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.14.1 + rev: v0.14.2 hooks: # Run the linter. - id: ruff From 195444c652a8712111ebe7d76207b7714e174f2c Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Tue, 28 Oct 2025 12:04:55 +0000 Subject: [PATCH 05/11] docs: complete restructure of Flagsmith documentation (#6210) Co-authored-by: Guilherme Co-authored-by: Robert Norrie Co-authored-by: htessaro Co-authored-by: lucaspr2 Co-authored-by: wadii --- README.md | 2 +- api/Makefile | 2 +- .../_category_.json | 5 + .../access-control/_category_.json | 5 + .../access-control/adfs.md} | 2 + .../access-control}/index.md | 12 +- .../access-control/ldap.md} | 31 +- .../access-control/oauth.md | 41 + .../access-control/okta.md | 33 + .../access-control}/rbac.md | 42 +- .../access-control/saml.md | 161 ++ .../billing-api-usage.md} | 24 +- .../data-management/_category_.json | 5 + .../data-management/bulk-import-and-export.md | 50 + .../data-management}/django-admin.png | Bin .../data-management/import-and-export.md | 24 + .../import-from-launchdarkly.md} | 36 +- .../organisations-import-export.md} | 9 +- .../governance-and-compliance/_category_.json | 5 + .../governance-and-compliance/api-usage.md | 16 + .../governance-and-compliance}/audit-logs.md | 10 +- .../change-requests.md | 56 + .../custom-fields.md | 132 + .../governance-and-compliance}/security.md | 13 +- .../system-limits.md | 20 +- .../usage-graph.png | Bin .../administration-and-security/index.mdx | 167 ++ .../platform-configuration/_category_.json | 5 + .../environment-settings.md | 4 +- .../platform-configuration}/metrics.md | 2 + .../usage-graph.png | Bin 0 -> 30217 bytes docs/docs/advanced-use/_category_.json | 5 - docs/docs/advanced-use/ab-testing.md | 89 - docs/docs/advanced-use/change-requests.md | 55 - docs/docs/advanced-use/custom-fields.md | 143 -- docs/docs/advanced-use/edge-proxy.md | 82 - docs/docs/advanced-use/flag-analytics.md | 43 - docs/docs/advanced-use/flag-management.md | 83 - docs/docs/advanced-use/real-time-flags.md | 109 - docs/docs/advanced-use/scheduled-flags.md | 38 - docs/docs/advanced-use/transient-traits.md | 587 ----- docs/docs/basic-features/_category_.json | 4 - docs/docs/basic-features/index.md | 84 - docs/docs/basic-features/managing-features.md | 105 - .../basic-features/managing-identities.md | 137 -- docs/docs/basic-features/segments.md | 315 --- docs/docs/best-practices/_category_.json | 5 + docs/docs/best-practices/defensive-coding.md | 70 + .../best-practices/efficient-api-usage.md | 76 + docs/docs/best-practices/flag-lifecycle.md | 79 + .../best-practices/integration-approaches.md | 69 + .../best-practices/mobile-app-versioning.md | 81 + .../docs/best-practices/testing-with-flags.md | 36 + docs/docs/best-practices/when-to-use-flags.md | 51 + docs/docs/clients/3rd-party.md | 22 - docs/docs/clients/client-side/_category_.json | 5 - docs/docs/clients/rest.md | 534 ---- .../deployment-self-hosting/_category_.json | 5 + .../_category_.json | 5 + .../troubleshooting.md | 21 + .../upgrades-and-rollbacks.md | 82 + .../using-the-django-admin.md | 55 + .../core-configuration/_category_.json | 8 + .../core-configuration/caching-strategies.md | 42 + .../core-configuration/email-setup.md | 50 + .../environment-variables.md | 60 + .../core-configuration/initial-setup.md | 28 + .../running-flagsmith-on-flagsmith.md | 26 + .../deployment-self-hosting/edge-proxy.md | 72 + .../enterprise-edition.md | 95 +- .../hosting-guides/_category_.json | 5 + .../cloud-providers/_category_.json | 5 + .../cloud-providers}/aptible.md | 25 +- .../hosting-guides/cloud-providers}/aws.md | 23 +- .../cloud-providers/google-cloud.md | 26 + .../hosting-guides/docker.md | 65 + .../hosting-guides/kubernetes-openshift.md} | 208 +- .../hosting-guides/manual-installation.md | 15 + .../hosting-guides/one-click-installers.md | 40 + docs/docs/deployment-self-hosting/index.md | 85 + .../scaling-and-performance/_category_.json | 6 + .../asynchronous-task-processor.md} | 52 +- .../scaling-and-performance/load-testing.md | 22 + .../scaling-and-performance/monitoring.md | 113 + .../sizing-and-scaling.md | 50 + .../using-influxdb-for-analytics.md | 133 + docs/docs/deployment/_category_.json | 5 - .../deployment/configuration/_category_.json | 5 - .../authentication/_category_.json | 4 - .../deployment/configuration/django-admin.md | 67 - .../configuration/sizing-and-scaling.md | 67 - .../configuration/troubleshooting.md | 28 - .../configuration/usage-report-example.png | Bin 73922 -> 0 bytes .../configuration/usage-reports.mdx | 20 - docs/docs/deployment/hosting/_category_.json | 5 - docs/docs/deployment/hosting/docker.md | 78 - docs/docs/deployment/hosting/google-cloud.md | 35 - docs/docs/deployment/hosting/locally-api.md | 605 ----- .../deployment/hosting/locally-edge-proxy.md | 314 --- .../deployment/hosting/locally-frontend.md | 127 - docs/docs/deployment/hosting/openshift.md | 14 - .../deployment/hosting/real-time/benchmark.js | 47 - .../hosting/real-time/deployment.md | 156 -- .../deployment/hosting/real-time/index.md | 109 - .../hosting/real-time/operations.md | 144 -- docs/docs/deployment/index.md | 745 ------ docs/docs/edge-api/Overview.md | 4 +- docs/docs/experimentation-ab-testing.md | 81 + docs/docs/flagsmith-concepts/_category_.json | 5 + docs/docs/flagsmith-concepts/data-model.md | 54 + docs/docs/flagsmith-concepts/identities.md | 110 + .../platform-architecture.md | 60 + .../docs/flagsmith-concepts/segments/index.md | 161 ++ .../segments/segment-rule-operators.md | 107 + .../targeting-and-rollouts/_category_.json | 5 + .../targeting-and-rollouts/index.md | 66 + docs/docs/getting-started/_category_.json | 5 + docs/docs/getting-started/feature-flags.md | 42 + docs/docs/getting-started/glossary.md | 43 + .../quick-start.md} | 72 +- docs/docs/guides-and-examples/_category_.json | 5 - .../guides-and-examples/defensive-coding.md | 100 - .../efficient-api-usage.md | 79 - .../guides-and-examples/flag-lifecycle.md | 62 - .../integration-approaches.md | 92 - .../micro-service-architectures.md | 45 - .../mobile-app-versioning.md | 88 - .../staged-feature-rollouts.md | 55 - .../testing-push-notifications.md | 66 - docs/docs/index.md | 58 - .../CLI.md | 4 +- .../_category_.json | 5 + .../flagsmith-api-overview}/_category_.json | 2 +- .../admin-api/authentication.md | 28 + .../admin-api/code-examples.md | 21 + .../flagsmith-api-overview/admin-api/index.md | 18 + .../flags-api/authentication.md | 24 + .../flags-api/code-examples.md | 42 + .../flagsmith-api-overview/flags-api/index.md | 17 + .../flagsmith-api-overview/index.md | 27 + .../integration-overview.md | 54 + .../openfeature.md | 16 +- .../sdks}/_category_.json | 2 +- .../sdks/client-side-sdks/_category_.json | 5 + .../sdks/client-side-sdks}/android.md | 5 +- .../sdks/client-side-sdks}/flutter.md | 15 +- .../sdks/client-side-sdks/index.md | 29 + .../sdks/client-side-sdks}/ios.mdx | 26 +- .../sdks/client-side-sdks}/javascript.md | 31 +- .../sdks/client-side-sdks}/nextjs-and-ssr.md | 13 +- .../sdks/client-side-sdks}/react.md | 9 +- .../sdks}/index.md | 41 +- .../sdks}/server-side.mdx | 42 +- docs/docs/integrations/apm/_category_.json | 5 - docs/docs/integrations/apm/sentry.md | 157 -- .../integrations/importers/_category_.json | 5 - docs/docs/integrations/index.md | 158 -- .../project-management/_category_.json | 5 - docs/docs/managing-flags/_category_.json | 4 + docs/docs/managing-flags/core-management.md | 90 + .../feature-health-metrics.md} | 37 +- docs/docs/managing-flags/flag-analytics.md | 35 + .../release-pipeline.md} | 9 +- .../managing-flags/rollout/_category_.json | 4 + .../rollout/rollout-by-attribute.md | 58 + .../rollout/rollout-by-percentage.md | 79 + docs/docs/managing-flags/scheduled-flags.md | 55 + docs/docs/managing-flags/tagging.md | 66 + docs/docs/performance/_category_.json | 5 + .../{advanced-use => performance}/edge-api.md | 70 +- docs/docs/performance/edge-proxy.md | 73 + docs/docs/performance/real-time-flags.md | 101 + docs/docs/platform/_category_.json | 5 - docs/docs/platform/releases.md | 475 ---- .../project-and-community/_category_.json | 5 + .../contributing.md | 24 +- .../project-and-community/release-notes.md | 128 + .../roadmap.md | 5 +- docs/docs/support/index.mdx | 34 - .../system-administration/_category_.json | 5 - docs/docs/system-administration/api-usage.md | 18 - .../system-administration/architecture.md | 11 - .../authentication/01-SAML/_category_.json | 4 - .../authentication/01-SAML/index.mdx | 194 -- .../authentication/02-Okta.md | 29 - .../authentication/03-OAuth.md | 30 - .../authentication/_category_.json | 4 - .../importing-and-exporting/_category_.json | 5 - .../importing-and-exporting/features.md | 69 - .../importing-and-exporting/index.md | 22 - docs/docs/system-administration/webhooks.md | 132 - .../third-party-integrations/_category_.json | 4 + .../analytics/_category_.json | 3 +- .../analytics/adobe.md | 2 +- .../analytics/amplitude.md | 9 +- .../analytics/heap.md | 13 +- .../analytics/mixpanel.md | 19 +- .../analytics/rudderstack.md | 8 +- .../analytics/segment.md | 8 +- .../ci-cd/_category_.json | 4 + .../third-party-integrations/ci-cd/index.md | 8 + .../ci-cd}/terraform.md | 19 +- docs/docs/third-party-integrations/index.mdx | 144 ++ .../_category_.json | 4 + .../appdynamics.md | 0 .../observability-and-monitoring}/datadog.md | 10 +- .../dynatrace.md | 12 +- .../observability-and-monitoring}/grafana.md | 16 +- .../new-relic.md} | 4 +- .../observability-and-monitoring/sentry.md | 109 + .../project-management/_category_.json | 4 + .../project-management/github.md | 16 +- .../project-management/jira.md | 16 +- .../project-management/servicenow.md | 2 +- .../project-management/slack.md | 10 +- .../webhook.md | 9 +- docs/docs/version-comparison.md | 60 - docs/package-lock.json | 2139 ++++++++++++----- docs/package.json | 2 + docs/src/components/Card/CardBody/index.js | 44 + docs/src/components/Card/CardFooter/index.js | 44 + docs/src/components/Card/CardHeader/index.js | 16 + docs/src/components/Card/CardImage/index.js | 20 + docs/src/components/Card/index.js | 16 + docs/src/css/custom.css | 249 +- docs/src/pages/index.module.css | 238 +- docs/src/pages/index.tsx | 170 ++ docs/static/img/full-logo.svg | 20 + docs/vercel.json | 566 ++++- 229 files changed, 7520 insertions(+), 8491 deletions(-) create mode 100644 docs/docs/administration-and-security/_category_.json create mode 100644 docs/docs/administration-and-security/access-control/_category_.json rename docs/docs/{system-administration/authentication/05-ADFS.md => administration-and-security/access-control/adfs.md} (84%) rename docs/docs/{system-administration/authentication => administration-and-security/access-control}/index.md (82%) rename docs/docs/{system-administration/authentication/04-LDAP.md => administration-and-security/access-control/ldap.md} (94%) create mode 100644 docs/docs/administration-and-security/access-control/oauth.md create mode 100644 docs/docs/administration-and-security/access-control/okta.md rename docs/docs/{system-administration => administration-and-security/access-control}/rbac.md (85%) create mode 100644 docs/docs/administration-and-security/access-control/saml.md rename docs/docs/{billing/index.mdx => administration-and-security/billing-api-usage.md} (82%) create mode 100644 docs/docs/administration-and-security/data-management/_category_.json create mode 100644 docs/docs/administration-and-security/data-management/bulk-import-and-export.md rename docs/docs/{system-administration/importing-and-exporting => administration-and-security/data-management}/django-admin.png (100%) create mode 100644 docs/docs/administration-and-security/data-management/import-and-export.md rename docs/docs/{system-administration/importing-and-exporting/launchdarkly.md => administration-and-security/data-management/import-from-launchdarkly.md} (54%) rename docs/docs/{system-administration/importing-and-exporting/organisations.md => administration-and-security/data-management/organisations-import-export.md} (94%) create mode 100644 docs/docs/administration-and-security/governance-and-compliance/_category_.json create mode 100644 docs/docs/administration-and-security/governance-and-compliance/api-usage.md rename docs/docs/{system-administration => administration-and-security/governance-and-compliance}/audit-logs.md (80%) create mode 100644 docs/docs/administration-and-security/governance-and-compliance/change-requests.md create mode 100644 docs/docs/administration-and-security/governance-and-compliance/custom-fields.md rename docs/docs/{system-administration => administration-and-security/governance-and-compliance}/security.md (87%) rename docs/docs/{system-administration => administration-and-security/governance-and-compliance}/system-limits.md (80%) rename docs/docs/{billing => administration-and-security/governance-and-compliance}/usage-graph.png (100%) create mode 100644 docs/docs/administration-and-security/index.mdx create mode 100644 docs/docs/administration-and-security/platform-configuration/_category_.json rename docs/docs/{system-administration => administration-and-security/platform-configuration}/environment-settings.md (86%) rename docs/docs/{system-administration => administration-and-security/platform-configuration}/metrics.md (97%) create mode 100644 docs/docs/administration-and-security/usage-graph.png delete mode 100644 docs/docs/advanced-use/_category_.json delete mode 100644 docs/docs/advanced-use/ab-testing.md delete mode 100644 docs/docs/advanced-use/change-requests.md delete mode 100644 docs/docs/advanced-use/custom-fields.md delete mode 100644 docs/docs/advanced-use/edge-proxy.md delete mode 100644 docs/docs/advanced-use/flag-analytics.md delete mode 100644 docs/docs/advanced-use/flag-management.md delete mode 100644 docs/docs/advanced-use/real-time-flags.md delete mode 100644 docs/docs/advanced-use/scheduled-flags.md delete mode 100644 docs/docs/advanced-use/transient-traits.md delete mode 100644 docs/docs/basic-features/_category_.json delete mode 100644 docs/docs/basic-features/index.md delete mode 100644 docs/docs/basic-features/managing-features.md delete mode 100644 docs/docs/basic-features/managing-identities.md delete mode 100644 docs/docs/basic-features/segments.md create mode 100644 docs/docs/best-practices/_category_.json create mode 100644 docs/docs/best-practices/defensive-coding.md create mode 100644 docs/docs/best-practices/efficient-api-usage.md create mode 100644 docs/docs/best-practices/flag-lifecycle.md create mode 100644 docs/docs/best-practices/integration-approaches.md create mode 100644 docs/docs/best-practices/mobile-app-versioning.md create mode 100644 docs/docs/best-practices/testing-with-flags.md create mode 100644 docs/docs/best-practices/when-to-use-flags.md delete mode 100644 docs/docs/clients/3rd-party.md delete mode 100644 docs/docs/clients/client-side/_category_.json delete mode 100644 docs/docs/clients/rest.md create mode 100644 docs/docs/deployment-self-hosting/_category_.json create mode 100644 docs/docs/deployment-self-hosting/administration-and-maintenance/_category_.json create mode 100644 docs/docs/deployment-self-hosting/administration-and-maintenance/troubleshooting.md create mode 100644 docs/docs/deployment-self-hosting/administration-and-maintenance/upgrades-and-rollbacks.md create mode 100644 docs/docs/deployment-self-hosting/administration-and-maintenance/using-the-django-admin.md create mode 100644 docs/docs/deployment-self-hosting/core-configuration/_category_.json create mode 100644 docs/docs/deployment-self-hosting/core-configuration/caching-strategies.md create mode 100644 docs/docs/deployment-self-hosting/core-configuration/email-setup.md create mode 100644 docs/docs/deployment-self-hosting/core-configuration/environment-variables.md create mode 100644 docs/docs/deployment-self-hosting/core-configuration/initial-setup.md create mode 100644 docs/docs/deployment-self-hosting/core-configuration/running-flagsmith-on-flagsmith.md create mode 100644 docs/docs/deployment-self-hosting/edge-proxy.md rename docs/docs/{deployment/configuration => deployment-self-hosting}/enterprise-edition.md (55%) create mode 100644 docs/docs/deployment-self-hosting/hosting-guides/_category_.json create mode 100644 docs/docs/deployment-self-hosting/hosting-guides/cloud-providers/_category_.json rename docs/docs/{deployment/hosting => deployment-self-hosting/hosting-guides/cloud-providers}/aptible.md (56%) rename docs/docs/{deployment/hosting => deployment-self-hosting/hosting-guides/cloud-providers}/aws.md (52%) create mode 100644 docs/docs/deployment-self-hosting/hosting-guides/cloud-providers/google-cloud.md create mode 100644 docs/docs/deployment-self-hosting/hosting-guides/docker.md rename docs/docs/{deployment/hosting/kubernetes.md => deployment-self-hosting/hosting-guides/kubernetes-openshift.md} (79%) create mode 100644 docs/docs/deployment-self-hosting/hosting-guides/manual-installation.md create mode 100644 docs/docs/deployment-self-hosting/hosting-guides/one-click-installers.md create mode 100644 docs/docs/deployment-self-hosting/index.md create mode 100644 docs/docs/deployment-self-hosting/scaling-and-performance/_category_.json rename docs/docs/{deployment/configuration/task-processor.md => deployment-self-hosting/scaling-and-performance/asynchronous-task-processor.md} (64%) create mode 100644 docs/docs/deployment-self-hosting/scaling-and-performance/load-testing.md create mode 100644 docs/docs/deployment-self-hosting/scaling-and-performance/monitoring.md create mode 100644 docs/docs/deployment-self-hosting/scaling-and-performance/sizing-and-scaling.md create mode 100644 docs/docs/deployment-self-hosting/scaling-and-performance/using-influxdb-for-analytics.md delete mode 100644 docs/docs/deployment/_category_.json delete mode 100644 docs/docs/deployment/configuration/_category_.json delete mode 100644 docs/docs/deployment/configuration/authentication/_category_.json delete mode 100644 docs/docs/deployment/configuration/django-admin.md delete mode 100644 docs/docs/deployment/configuration/sizing-and-scaling.md delete mode 100644 docs/docs/deployment/configuration/troubleshooting.md delete mode 100644 docs/docs/deployment/configuration/usage-report-example.png delete mode 100644 docs/docs/deployment/configuration/usage-reports.mdx delete mode 100644 docs/docs/deployment/hosting/_category_.json delete mode 100644 docs/docs/deployment/hosting/docker.md delete mode 100644 docs/docs/deployment/hosting/google-cloud.md delete mode 100644 docs/docs/deployment/hosting/locally-api.md delete mode 100644 docs/docs/deployment/hosting/locally-edge-proxy.md delete mode 100644 docs/docs/deployment/hosting/locally-frontend.md delete mode 100644 docs/docs/deployment/hosting/openshift.md delete mode 100644 docs/docs/deployment/hosting/real-time/benchmark.js delete mode 100644 docs/docs/deployment/hosting/real-time/deployment.md delete mode 100644 docs/docs/deployment/hosting/real-time/index.md delete mode 100644 docs/docs/deployment/hosting/real-time/operations.md delete mode 100644 docs/docs/deployment/index.md create mode 100644 docs/docs/experimentation-ab-testing.md create mode 100644 docs/docs/flagsmith-concepts/_category_.json create mode 100644 docs/docs/flagsmith-concepts/data-model.md create mode 100644 docs/docs/flagsmith-concepts/identities.md create mode 100644 docs/docs/flagsmith-concepts/platform-architecture.md create mode 100644 docs/docs/flagsmith-concepts/segments/index.md create mode 100644 docs/docs/flagsmith-concepts/segments/segment-rule-operators.md create mode 100644 docs/docs/flagsmith-concepts/targeting-and-rollouts/_category_.json create mode 100644 docs/docs/flagsmith-concepts/targeting-and-rollouts/index.md create mode 100644 docs/docs/getting-started/_category_.json create mode 100644 docs/docs/getting-started/feature-flags.md create mode 100644 docs/docs/getting-started/glossary.md rename docs/docs/{quickstart.md => getting-started/quick-start.md} (56%) delete mode 100644 docs/docs/guides-and-examples/_category_.json delete mode 100644 docs/docs/guides-and-examples/defensive-coding.md delete mode 100644 docs/docs/guides-and-examples/efficient-api-usage.md delete mode 100644 docs/docs/guides-and-examples/flag-lifecycle.md delete mode 100644 docs/docs/guides-and-examples/integration-approaches.md delete mode 100644 docs/docs/guides-and-examples/micro-service-architectures.md delete mode 100644 docs/docs/guides-and-examples/mobile-app-versioning.md delete mode 100644 docs/docs/guides-and-examples/staged-feature-rollouts.md delete mode 100644 docs/docs/guides-and-examples/testing-push-notifications.md delete mode 100644 docs/docs/index.md rename docs/docs/{clients => integrating-with-flagsmith}/CLI.md (90%) create mode 100644 docs/docs/integrating-with-flagsmith/_category_.json rename docs/docs/{integrations => integrating-with-flagsmith/flagsmith-api-overview}/_category_.json (54%) create mode 100644 docs/docs/integrating-with-flagsmith/flagsmith-api-overview/admin-api/authentication.md create mode 100644 docs/docs/integrating-with-flagsmith/flagsmith-api-overview/admin-api/code-examples.md create mode 100644 docs/docs/integrating-with-flagsmith/flagsmith-api-overview/admin-api/index.md create mode 100644 docs/docs/integrating-with-flagsmith/flagsmith-api-overview/flags-api/authentication.md create mode 100644 docs/docs/integrating-with-flagsmith/flagsmith-api-overview/flags-api/code-examples.md create mode 100644 docs/docs/integrating-with-flagsmith/flagsmith-api-overview/flags-api/index.md create mode 100644 docs/docs/integrating-with-flagsmith/flagsmith-api-overview/index.md create mode 100644 docs/docs/integrating-with-flagsmith/integration-overview.md rename docs/docs/{clients => integrating-with-flagsmith}/openfeature.md (61%) rename docs/docs/{clients => integrating-with-flagsmith/sdks}/_category_.json (70%) create mode 100644 docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/_category_.json rename docs/docs/{clients/client-side => integrating-with-flagsmith/sdks/client-side-sdks}/android.md (98%) rename docs/docs/{clients/client-side => integrating-with-flagsmith/sdks/client-side-sdks}/flutter.md (95%) create mode 100644 docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/index.md rename docs/docs/{clients/client-side => integrating-with-flagsmith/sdks/client-side-sdks}/ios.mdx (90%) rename docs/docs/{clients/client-side => integrating-with-flagsmith/sdks/client-side-sdks}/javascript.md (96%) rename docs/docs/{clients/client-side => integrating-with-flagsmith/sdks/client-side-sdks}/nextjs-and-ssr.md (91%) rename docs/docs/{clients/client-side => integrating-with-flagsmith/sdks/client-side-sdks}/react.md (92%) rename docs/docs/{clients => integrating-with-flagsmith/sdks}/index.md (86%) rename docs/docs/{clients => integrating-with-flagsmith/sdks}/server-side.mdx (97%) delete mode 100644 docs/docs/integrations/apm/_category_.json delete mode 100644 docs/docs/integrations/apm/sentry.md delete mode 100644 docs/docs/integrations/importers/_category_.json delete mode 100644 docs/docs/integrations/index.md delete mode 100644 docs/docs/integrations/project-management/_category_.json create mode 100644 docs/docs/managing-flags/_category_.json create mode 100644 docs/docs/managing-flags/core-management.md rename docs/docs/{advanced-use/feature-health.md => managing-flags/feature-health-metrics.md} (50%) create mode 100644 docs/docs/managing-flags/flag-analytics.md rename docs/docs/{advanced-use/release-pipelines.md => managing-flags/release-pipeline.md} (98%) create mode 100644 docs/docs/managing-flags/rollout/_category_.json create mode 100644 docs/docs/managing-flags/rollout/rollout-by-attribute.md create mode 100644 docs/docs/managing-flags/rollout/rollout-by-percentage.md create mode 100644 docs/docs/managing-flags/scheduled-flags.md create mode 100644 docs/docs/managing-flags/tagging.md create mode 100644 docs/docs/performance/_category_.json rename docs/docs/{advanced-use => performance}/edge-api.md (52%) create mode 100644 docs/docs/performance/edge-proxy.md create mode 100644 docs/docs/performance/real-time-flags.md delete mode 100644 docs/docs/platform/_category_.json delete mode 100644 docs/docs/platform/releases.md create mode 100644 docs/docs/project-and-community/_category_.json rename docs/docs/{platform => project-and-community}/contributing.md (64%) create mode 100644 docs/docs/project-and-community/release-notes.md rename docs/docs/{platform => project-and-community}/roadmap.md (64%) delete mode 100644 docs/docs/support/index.mdx delete mode 100644 docs/docs/system-administration/_category_.json delete mode 100644 docs/docs/system-administration/api-usage.md delete mode 100644 docs/docs/system-administration/architecture.md delete mode 100644 docs/docs/system-administration/authentication/01-SAML/_category_.json delete mode 100644 docs/docs/system-administration/authentication/01-SAML/index.mdx delete mode 100644 docs/docs/system-administration/authentication/02-Okta.md delete mode 100644 docs/docs/system-administration/authentication/03-OAuth.md delete mode 100644 docs/docs/system-administration/authentication/_category_.json delete mode 100644 docs/docs/system-administration/importing-and-exporting/_category_.json delete mode 100644 docs/docs/system-administration/importing-and-exporting/features.md delete mode 100644 docs/docs/system-administration/importing-and-exporting/index.md delete mode 100644 docs/docs/system-administration/webhooks.md create mode 100644 docs/docs/third-party-integrations/_category_.json rename docs/docs/{integrations => third-party-integrations}/analytics/_category_.json (64%) rename docs/docs/{integrations => third-party-integrations}/analytics/adobe.md (86%) rename docs/docs/{integrations => third-party-integrations}/analytics/amplitude.md (83%) rename docs/docs/{integrations => third-party-integrations}/analytics/heap.md (85%) rename docs/docs/{integrations => third-party-integrations}/analytics/mixpanel.md (82%) rename docs/docs/{integrations => third-party-integrations}/analytics/rudderstack.md (92%) rename docs/docs/{integrations => third-party-integrations}/analytics/segment.md (90%) create mode 100644 docs/docs/third-party-integrations/ci-cd/_category_.json create mode 100644 docs/docs/third-party-integrations/ci-cd/index.md rename docs/docs/{integrations => third-party-integrations/ci-cd}/terraform.md (88%) create mode 100644 docs/docs/third-party-integrations/index.mdx create mode 100644 docs/docs/third-party-integrations/observability-and-monitoring/_category_.json rename docs/docs/{integrations/apm => third-party-integrations/observability-and-monitoring}/appdynamics.md (100%) rename docs/docs/{integrations/apm => third-party-integrations/observability-and-monitoring}/datadog.md (81%) rename docs/docs/{integrations/apm => third-party-integrations/observability-and-monitoring}/dynatrace.md (74%) rename docs/docs/{integrations/apm => third-party-integrations/observability-and-monitoring}/grafana.md (77%) rename docs/docs/{integrations/apm/newrelic.md => third-party-integrations/observability-and-monitoring/new-relic.md} (95%) create mode 100644 docs/docs/third-party-integrations/observability-and-monitoring/sentry.md create mode 100644 docs/docs/third-party-integrations/project-management/_category_.json rename docs/docs/{integrations => third-party-integrations}/project-management/github.md (84%) rename docs/docs/{integrations => third-party-integrations}/project-management/jira.md (74%) rename docs/docs/{integrations => third-party-integrations}/project-management/servicenow.md (88%) rename docs/docs/{integrations => third-party-integrations}/project-management/slack.md (77%) rename docs/docs/{integrations => third-party-integrations}/webhook.md (92%) delete mode 100644 docs/docs/version-comparison.md create mode 100644 docs/src/components/Card/CardBody/index.js create mode 100644 docs/src/components/Card/CardFooter/index.js create mode 100644 docs/src/components/Card/CardHeader/index.js create mode 100644 docs/src/components/Card/CardImage/index.js create mode 100644 docs/src/components/Card/index.js create mode 100644 docs/src/pages/index.tsx create mode 100644 docs/static/img/full-logo.svg diff --git a/README.md b/README.md index 95fd2615de3a..26345a543ea2 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ # [Flagsmith](https://flagsmith.com/) is an Open-Source Feature Flagging Tool to Ship Faster & Control Releases -Change the way your team releases software. Roll out, segment, and optimise—with granular control. Stay secure with on-premise and private cloud hosting. +Change the way your team releases software. Roll out, segment, and optimise—with granular control. Stay secure with on-premise and private cloud hosting. * Feature flags: Release features behind the safety of a feature flag * Make changes remotely: Easily toggle individual features on and off, and make changes without deploying new code diff --git a/api/Makefile b/api/Makefile index aaff444aaf9c..5e550dae2421 100644 --- a/api/Makefile +++ b/api/Makefile @@ -151,7 +151,7 @@ integrate-private-tests: .PHONY: generate-docs generate-docs: - poetry run flagsmith docgen metrics > ../docs/docs/system-administration/metrics.md + poetry run flagsmith docgen metrics > ../docs/docs/administration-and-security/platform-configuration/metrics.md .PHONY: add-known-sdk-version add-known-sdk-version: diff --git a/docs/docs/administration-and-security/_category_.json b/docs/docs/administration-and-security/_category_.json new file mode 100644 index 000000000000..99aee2bc1f34 --- /dev/null +++ b/docs/docs/administration-and-security/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Administration and Security", + "position": 4, + "collapsed": true +} diff --git a/docs/docs/administration-and-security/access-control/_category_.json b/docs/docs/administration-and-security/access-control/_category_.json new file mode 100644 index 000000000000..4945d544c5ab --- /dev/null +++ b/docs/docs/administration-and-security/access-control/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Access Control", + "position": 2, + "collapsed": true +} \ No newline at end of file diff --git a/docs/docs/system-administration/authentication/05-ADFS.md b/docs/docs/administration-and-security/access-control/adfs.md similarity index 84% rename from docs/docs/system-administration/authentication/05-ADFS.md rename to docs/docs/administration-and-security/access-control/adfs.md index 5df425aa2c8f..3ecfa363fc68 100644 --- a/docs/docs/system-administration/authentication/05-ADFS.md +++ b/docs/docs/administration-and-security/access-control/adfs.md @@ -1,5 +1,7 @@ --- title: ADFS +sidebar_label: ADFS +sidebar_position: 20 --- :::tip diff --git a/docs/docs/system-administration/authentication/index.md b/docs/docs/administration-and-security/access-control/index.md similarity index 82% rename from docs/docs/system-administration/authentication/index.md rename to docs/docs/administration-and-security/access-control/index.md index 11a2e47c538f..088e6b5426d6 100644 --- a/docs/docs/system-administration/authentication/index.md +++ b/docs/docs/administration-and-security/access-control/index.md @@ -2,7 +2,7 @@ title: Authentication description: Customise how your users log in to the Flagsmith dashboard sidebar_label: Overview -sidebar_position: 1 +sidebar_position: 10 --- Flagsmith supports a variety of authentication methods for logging into the dashboard: @@ -21,9 +21,9 @@ Two-factor authentication requires a [Start-Up or Enterprise subscription](https Using the following authentication methods requires an [Enterprise subscription](https://flagsmith.com/pricing): -- [SAML](/system-administration/authentication/SAML) -- Active Directory (LDAP) -- Microsoft ADFS +- [SAML](/administration-and-security/access-control/saml) +- [Active Directory (LDAP)](/administration-and-security/access-control/ldap) +- [Microsoft ADFS](/administration-and-security/access-control/adfs) Please get in touch in order to integrate with LDAP or ADFS. @@ -37,7 +37,7 @@ and authentication methods you want to allow for your users, and when would be a restrictions. If you are self-hosting Flagsmith, you can restrict authentication methods per email domain from -[Django Admin](/deployment/configuration/django-admin): +[Django Admin](/deployment-self-hosting/administration-and-maintenance/using-the-django-admin): 1. On the Django Admin sidebar, click on "Domain auth methods". 2. Click "Add domain auth methods". @@ -50,7 +50,7 @@ If you are self-hosting Flagsmith, you can restrict authentication methods per e If you are self-hosting Flagsmith, you can disable password authentication by setting the `PREVENT_EMAIL_PASSWORD` environment variable on the Flagsmith API. This will also hide the username and password fields from the login screen. Note that this does not disable password authentication for -[Django Admin](/deployment/configuration/django-admin#email-and-password). +[Django Admin](/deployment-self-hosting/administration-and-maintenance/using-the-django-admin#email-and-password). If you have a private cloud Flagsmith instance, contact Flagsmith support to disable password authentication once you have successfully set up an alternative authentication method. diff --git a/docs/docs/system-administration/authentication/04-LDAP.md b/docs/docs/administration-and-security/access-control/ldap.md similarity index 94% rename from docs/docs/system-administration/authentication/04-LDAP.md rename to docs/docs/administration-and-security/access-control/ldap.md index a0c4722216de..f92063fc06ab 100644 --- a/docs/docs/system-administration/authentication/04-LDAP.md +++ b/docs/docs/administration-and-security/access-control/ldap.md @@ -1,5 +1,7 @@ --- title: LDAP +sidebar_label: LDAP +sidebar_position: 50 --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; @@ -10,37 +12,37 @@ LDAP authentication requires a self-hosted [Enterprise subscription](https://fla ::: -Flagsmith can be configured to use LDAP for authentication with [environment variables](#backend-environment-variables). -When enabled, it works by authenticating the user with username and password using the ldap server, fetching the user -details from the LDAP server (if the authentication was successful) and creating the user in the Django database. +Flagsmith can be configured to use LDAP for authentication with [environment variables](#backend-environment-variables). + +When LDAP is enabled, it works by authenticating the user with username and password using the ldap server, fetching the user details from the LDAP server (if the authentication was successful) and creating the user in the Django database. ## Using Microsoft Active Directory -By default, Flagsmith supports logging in via OpenLDAP. To connect to a Microsoft Active Directory, you need to modify -the following environment variables. +By default, Flagsmith supports logging in via OpenLDAP. To connect to a Microsoft Active Directory, you need to modify the following environment variables. + +Flagsmith provides different `LDAP_AUTH_FORMAT_USERNAME` settings to accommodate various Active Directory username formats: -For simple usernames (e.g. "username"): +- **Simple usernames** (e.g. "username"): ```txt LDAP_AUTH_FORMAT_USERNAME="django_python3_ldap.utils.format_username_active_directory" ``` -For down-level login name formats (e.g. "DOMAIN\username"): +- **Down-level login name formats** (e.g. "DOMAIN\username"): ```txt LDAP_AUTH_FORMAT_USERNAME="django_python3_ldap.utils.format_username_active_directory" LDAP_AUTH_ACTIVE_DIRECTORY_DOMAIN="DOMAIN" ``` -For user-principal-name formats (e.g. "user@domain.com"): +- **User-principal-name formats** (e.g. "user@domain.com"): ```txt LDAP_AUTH_FORMAT_USERNAME="django_python3_ldap.utils.format_username_active_directory_principal" LDAP_AUTH_ACTIVE_DIRECTORY_DOMAIN="domain.com" ``` -Depending on how your Active Directory server is configured, the following additional settings may match your server -better than the defaults used by django-python3-ldap: +Depending on how your Active Directory server is configured, the following additional settings may match your server better than the defaults used by django-python3-ldap: ```txt LDAP_AUTH_USER_FIELDS=username=sAMAccountName,email=mail,first_name=givenName,last_name=sn @@ -49,8 +51,7 @@ LDAP_AUTH_OBJECT_CLASS="user" ## Sync LDAP groups -You can synchronise Flagsmith users and groups with your LDAP (Directory) users and groups by running the following -command: +You can synchronise Flagsmith users and groups with your LDAP (Directory) users and groups by running the following command: ```bash python manage.py sync_ldap_users_and_groups @@ -63,7 +64,7 @@ Running this command will: - Remove users from group if they no longer belong to that group in Directory - Add users to group if they belong to a new group in Directory -:::note Before running this command, please make sure to set the following environment variables: +:::note Before running the synchronisation command, please make sure to set the following environment variables: - LDAP_SYNC_USER_USERNAME - LDAP_SYNC_USER_PASSWORD @@ -75,9 +76,7 @@ Running this command will: ## Backend environment variables -Note that some environment variables may be different depending on the image that you are using -(`flagsmith/flagsmith-api-ee` or `flagsmith/flagsmith-private-cloud`). Please select the correct tab below to ensure you -are using the correct environment variables. +Note that some environment variables may be different depending on the image that you are using (`flagsmith/flagsmith-api-ee` or `flagsmith/flagsmith-private-cloud`). Please select the correct tab below to ensure you are using the correct environment variables. diff --git a/docs/docs/administration-and-security/access-control/oauth.md b/docs/docs/administration-and-security/access-control/oauth.md new file mode 100644 index 000000000000..19c047bd08ae --- /dev/null +++ b/docs/docs/administration-and-security/access-control/oauth.md @@ -0,0 +1,41 @@ +--- +title: OAuth +sidebar_label: OAuth +sidebar_position: 40 +--- + +This guide explains how to set up OAuth authentication for Flagsmith using Google and GitHub as identity providers. OAuth allows your users to log in to Flagsmith using their existing credentials from these services. + +## Prerequisites + +- Administrative access to your Flagsmith instance to configure environment variables and Flagsmith on Flagsmith flags. +- An account with Google Cloud Console and/or GitHub with permissions to create OAuth applications. + +## Configure OAuth for Google + +Follow these steps to set up OAuth with Google: + +1. Follow Google's official guide on [Setting up OAuth 2.0](https://support.google.com/cloud/answer/6158849?hl=en) to create your OAuth 2.0 client ID and client secret. +- Create the Flagsmith on Flagsmith flag as detailed in the [deployment documentation](/deployment-self-hosting/core-configuration/environment-variables#oauth-google). + +## Configure OAuth for GitHub + +As a pre-requisite for this configuration make sure to have [Flagsmith on Flagsmith](/deployment-self-hosting/core-configuration/running-flagsmith-on-flagsmith) set up. Follow these steps to set up OAuth with GitHub: + +1. Configure the following environment variables: + - `GITHUB_CLIENT_ID` + - `GITHUB_CLIENT_SECRET` + +2. Configure OAuth for GitHub: + - [Create an OAuth GitHub application](https://docs.github.com/en/developers/apps/building-oauth-apps/creating-an-oauth-app) + - For the Authorization callback URL use: `https:///oauth/github` +3. Create the Flagsmith on Flagsmith flag as it shows [here](/deployment-self-hosting/core-configuration/environment-variables#oauth-github). + +Now you would be able to see the GitHub SSO option. + +
+ +## See Also + +- [Flagsmith Deployment Documentation](/deployment-self-hosting/core-configuration/running-flagsmith-on-flagsmith): For detailed information on setting up "Flagsmith on Flagsmith" and related configurations. +- [SAML SSO](/administration-and-security/access-control/saml): For information on configuring SAML-based SSO. \ No newline at end of file diff --git a/docs/docs/administration-and-security/access-control/okta.md b/docs/docs/administration-and-security/access-control/okta.md new file mode 100644 index 000000000000..195975d8f9f9 --- /dev/null +++ b/docs/docs/administration-and-security/access-control/okta.md @@ -0,0 +1,33 @@ +--- +title: Okta +sidebar_label: Okta +sidebar_position: 30 +--- + +Flagsmith can integrate with Okta single sign-on (SSO) by using SAML. We provide a first-party Okta integration to simplify the setup. This guide explains how to integrate Flagsmith with Okta SSO. + +## Prerequisites + +Before you begin, ensure you have: + +- Access to your Flagsmith organisation with permissions to create and manage SAML configurations. +- An Okta account with administrative access to add and configure applications. + + +## Configure Okta SSO + +Follow these steps to set up the Flagsmith Okta integration: + +1. Create a [Flagsmith SAML configuration](/administration-and-security/access-control/saml#setup). You can leave the identity provider metadata blank for now. +2. Add the [Flagsmith Okta integration](https://www.okta.com/integrations/flagsmith/) to your Okta account, and open it in the Okta dashboard. +3. Select the "Sign On" tab, and click "Edit". +4. Under "Advanced Sign-on Settings", fill out these fields and then click Save: + - **API Base URL** should be `https://api.flagsmith.com` on SaaS, or your API root URL otherwise. + - **SAML Organisation** should be the name of the SAML configuration you previously created. +5. Staying on the "Sign On" tab, find the "Metadata URL" in the "Sign on methods" section. Save this metadata to a file and upload it to the "IdP Metadata XML" of your Flagsmith SAML configuration. + +Once your Flagsmith SAML configuration has your Okta IdP metadata set, your users can log in to Flagsmith with Okta by clicking "Single Sign-On" at the login page, and typing the name of the SAML configuration you created. + +## User attributes + +By default, the Flagsmith Okta integration will map your users' Okta email address, given name and surname so that they are visible within Flagsmith. If you need to map different attributes, you can [customise the attribute mappings](/administration-and-security/access-control/saml#attribute-mapping) on your SAML configuration. diff --git a/docs/docs/system-administration/rbac.md b/docs/docs/administration-and-security/access-control/rbac.md similarity index 85% rename from docs/docs/system-administration/rbac.md rename to docs/docs/administration-and-security/access-control/rbac.md index ad82c1824f56..0bdd159a5eab 100644 --- a/docs/docs/system-administration/rbac.md +++ b/docs/docs/administration-and-security/access-control/rbac.md @@ -1,5 +1,7 @@ --- title: Role-based access control +sidebar_label: Role-based Access Control +sidebar_position: 1 --- :::info @@ -15,9 +17,9 @@ For example, RBAC allows you to achieve the following scenarios: - Only allow certain users to modify your production environments. - Grant a default set of permissions to all users that join your Flagsmith organisation. -- Lock down an [Admin API](/clients/rest/#private-admin-api-endpoints) key to a specific set of permissions. +- Lock down an [Admin API](/integrating-with-flagsmith/flagsmith-api-overview/admin-api) key to a specific set of permissions. - Provide Flagsmith permissions based on your enterprise identity provider's groups when using - [SAML single sign-on](/system-administration/authentication/SAML/). + [SAML single sign-on](/administration-and-security/access-control/saml). To add users to your Flagsmith organisation or to manage user permissions, click on your organisation name in the top left and open the **Users and Permissions** tab. @@ -49,7 +51,7 @@ following built-in roles: - _Organisation Administrator_ grants full access to everything in your Flagsmith organisation. - _User_ grants no access and requires you to assign permissions using custom roles and/or groups. -**Custom roles** can be assigned to users, groups or [Admin API](/clients/rest/#private-admin-api-endpoints) keys. Any +**Custom roles** can be assigned to users, groups or [Admin API](/integrating-with-flagsmith/flagsmith-api-overview/admin-api) keys. Any number of custom roles can be created and assigned. Creating, modifying or assigning roles requires organisation administrator permissions. @@ -73,14 +75,14 @@ link directly with them. Both options require organisation administrator permiss Permissions > Members**. Users can also join your organisation directly by logging in to Flagsmith using -[single sign-on](/system-administration/authentication/SAML/). +[single sign-on](/administration-and-security/access-control/saml). ### Email invites :::info If you are self-hosting Flagsmith, you must -[configure an email provider](/deployment/hosting/locally-api#email-environment-variables) before using email invites. +[configure an email provider](/deployment-self-hosting/core-configuration/email-setup) before using email invites. ::: @@ -115,7 +117,7 @@ options: - Add users by default to a group. When creating or editing a group, select the **Add new users by default** option. When a user logs in for the first time to your organisation, they will automatically be added to all groups that have this option enabled. -- [Use existing groups from your enterprise identity provider](/system-administration/authentication/SAML/#using-groups-from-your-saml-idp). +- [Use existing groups from your enterprise identity provider](/administration-and-security/access-control/saml#using-groups-from-your-saml-idp). Any time a user logs in using single sign-on, they will be made a member of any groups with matching external IDs. ## Deprecated features @@ -150,8 +152,8 @@ configure a role to create change requests only for features tagged with "market | Permission | Ability | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | -| Create Project | Allows creating projects in the organisation. Users are automatically granted Administrator permissions on any projects they create. | -| Manage User Groups | Allows adding or removing users from any group. | +| Create project | Allows creating projects in the organisation. Users are automatically granted Administrator permissions on any projects they create. | +| Manage user groups | Allows adding or removing users from any group. | ### Project @@ -160,11 +162,11 @@ configure a role to create change requests only for features tagged with "market | Permission | Ability | Supports Tags | | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | Administrator | Grants full read and write access to all environments, features, and segments. | | -| View Project | Allows viewing this project. The project is hidden from users without this permission. | | -| Create Environment | Allows creating new environments in this project. Users are automatically granted Administrator permissions on any environments they create. | | -| Create Feature | Allows creating new features in all environments. | | -| Delete Feature | Allows deleting features from all environments. | Yes | -| Manage Segments | Grants write access to segments in this project. | | +| View project | Allows viewing this project. The project is hidden from users without this permission. | | +| Create environment | Allows creating new environments in this project. Users are automatically granted Administrator permissions on any environments they create. | | +| Create feature | Allows creating new features in all environments. | | +| Delete feature | Allows deleting features from all environments. | Yes | +| Manage segments | Grants write access to segments in this project. | | | View audit log | Allows viewing all audit log entries for this project. | | ### Environment @@ -172,10 +174,10 @@ configure a role to create change requests only for features tagged with "market | Permission | Ability | Supports Tags | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------ | ------------- | | Administrator | Grants full read and write access to all feature states, overrides, identities, and change requests in this environment. | | -| View Environment | Allows viewing this environment. The environment is hidden from users without this permission. | | -| Update Feature State | Allows updating any feature state or values in this environment. | Yes | -| Manage Identities | Grants read and write access to identities in this environment. | | -| Manage Segment Overrides | Grants write access to segment overrides in this environment. | | -| Create Change Request | Allows creating change requests for features in this environment. | Yes | -| Approve Change Request | Allows approving or denying change requests in this environment. | Yes | -| View Identities | Grants read-only access to identities in this environment. | | +| View environment | Allows viewing this environment. The environment is hidden from users without this permission. | | +| Update feature state | Allows updating any feature state or values in this environment. | Yes | +| Manage identities | Grants read and write access to identities in this environment. | | +| Manage segment overrides | Grants write access to segment overrides in this environment. | | +| Create change request | Allows creating change requests for features in this environment. | Yes | +| Approve change request | Allows approving or denying change requests in this environment. | Yes | +| View identities | Grants read-only access to identities in this environment. | | diff --git a/docs/docs/administration-and-security/access-control/saml.md b/docs/docs/administration-and-security/access-control/saml.md new file mode 100644 index 000000000000..c80a9e0da590 --- /dev/null +++ b/docs/docs/administration-and-security/access-control/saml.md @@ -0,0 +1,161 @@ +--- +title: SAML single sign-on (SSO) +sidebar_label: SAML +sidebar_position: 10 +--- + +:::info + +SAML single sign-on requires an [Enterprise subscription](https://flagsmith.com/pricing). + +::: + +SAML (Security Assertion Markup Language) is a standard authentication and authorisation system. You can connect your existing identity provider to Flagsmith using SAML 2.0, which lets your users log in to your Flagsmith organisation using their existing credentials. + +SAML single sign-on also lets Flagsmith use the existing user groups from your identity provider. This lets you assign permissions to your Flagsmith users directly from their identity provider groups, and not have to manage group membership from Flagsmith. + +## Prerequisites + +- Your Flagsmith organisation must have an active Enterprise licence. + +## User Matching and Organisation Membership + +If you have preexisting Flagsmith users that use other authentication methods such as email and password, GitHub or Google, and you later set up SSO for these users, Flagsmith will see them as the same user as long as their email is the same (case-insensitive). All Flagsmith data including permissions are preserved as long as the email addresses match between SSO and non-SSO users. + +Users that log in using SSO can only belong to one Flagsmith organisation. If a user tries to log in with SSO, and they belong to more than one Flagsmith organisation, they will not be able to log in using SSO until they are removed from all other organisations. These users can still log in using a non-SSO method and remove themselves from the other organisations if they choose. + +### Self-Hosting Requirements + +If you are self-hosting Flagsmith, TLS is required. Encrypted SAML assertions are not supported. + +## Setup SAML + +:::tip + +If your identity provider is Okta, use the [Flagsmith Okta application](./index.md) +instead of following these steps. + +::: + +This is an overview of the steps required to configure SAML SSO: + +1. Create a Flagsmith SAML configuration. +2. Add your identity provider's SAML metadata to this Flagsmith SAML configuration. +3. Add your Flagsmith SAML configuration's service provider metadata to your identity provider. +4. Optional: Add external IDs to your Flagsmith groups to map your identity provider groups to Flagsmith groups. + +You can manage your Flagsmith SAML configurations from the Flagsmith dashboard. Click on your Flagsmith organisation name in the top left, then go to **Organisation Settings** > **SAML**. + +### SAML Configuration Options + +You can create multiple SAML configurations if you have multiple identity providers. In most cases, you will only need one. + +When creating a SAML configuration, the following options are available: + +**Name:** (**required**) A unique, URL-friendly name for the SAML configuration. This name must be unique across all Flagsmith organisations and forms part of the URL that your identity provider will post SAML messages to during authentication. Users must type this name when clicking "Single Sign-On" at the login screen. It cannot be changed after the SAML configuration is created. + +**Allow IdP-initiated**: If enabled, users will be able to log in directly from your identity provider without needing to visit the Flagsmith login page. + +**IdP metadata XML**: The SAML metadata from your identity provider. This typically includes information such as your identity provider's public key for SAML assertions. If you do not have this metadata yet, you can create the SAML configuration without it and come back to this step later. + +Once your Flagsmith SAML configuration is created, you can download its SAML metadata by clicking "Download Service Provider Metadata". Add this file to your identity provider to establish a trust relationship between it and Flagsmith. + +
+ + Additional options when self-hosting Flagsmith + + **Frontend URL** should point to the base URL of the Flagsmith dashboard. It is automatically prefilled with the + URL of the dashboard you are currently using. You only need to change this if your users will access the Flagsmith + dashboard using a different URL than the one you are currently using—for example, if you are connecting to Flagsmith + via port forwarding or a VPN that your users do not typically use. + +
+ +### Assertion consumer service URL + +Each Flagsmith SAML configuration has its own Assertion Consumer Service (ACS) URL, also known as single sign-on URL. Your identity provider will post SAML messages to this URL when a user logs in using SSO. The ACS URL for any SAML configuration is as follows, replacing `flagsmith.example.com` with your Flagsmith API's domain: + +``` +https://flagsmith.example.com/api/v1/auth/saml/YOUR_SAML_CONFIGURATION_NAME/response/ +``` + +## Attribute mapping + +Flagsmith will look for the following SAML attributes, in order, to uniquely identify a SAML user: + +- `subject-id` +- `uid` +- `NameID` + +Flagsmith also maps user attributes from the following claims in the SAML assertion: + +| Flagsmith attribute | Identity provider claim names | +|---------------------|------------------------------------------------------| +| Email | `mail`, `email` or `emailAddress` | +| First name | `gn`, `givenName` or the first part of `displayName` | +| Last name | `sn`, `surname` or the second part of `displayName` | +| Groups | `groups` | + +To add custom attribute mappings, edit your SAML configuration and open the **Attribute Mappings** tab. For example, +this mapping tells Flagsmith to look for user groups in a claim other than the default `groups` claim: + +
+ +## Permissions for SAML users + +By default, users logging in via SAML will have no permissions to view or modify anything in the Flagsmith dashboard. You can customise this by creating a [group](/administration-and-security/access-control/rbac) with the "Add new users by default" option enabled, and assigning your desired default permissions to that group. + +### Using groups from your identity provider + +Flagsmith can add or remove a user from groups based on your identity provider's SAML response when logging in. + +When a user logs in, Flagsmith will make them a member of all the groups listed in the `groups` claim from your identity provider's SAML assertion. Each value of the `groups` claim should correspond to the "External ID" of a Flagsmith group: + +
+ +For example, a SAML assertion with the following `groups` claim would assign the user to the Flagsmith groups with external IDs of `my_group` and `my_other_group`: + +```xml + + + my_group + + + my_other_group + + +``` + +By default, this claim must be named exactly `groups`. Some identity providers such as Microsoft Entra ID add a namespace to their claims such as `http://schemas.microsoft.com/ws/2008/06/identity/claims/groups`. If this is the case, or your groups claim has a different name, you must tell Flagsmith which claim it should look at by [creating an attribute mapping](#attribute-mapping). + +## Force users to log in with SSO + +Once you have confirmed your SAML configuration is working, you can prevent users logging in using other authentication methods with any of these options: + +* [Restrict authentication methods per email domain](/administration-and-security/access-control/#domain-auth). +* If you have a private Flagsmith instance, +[disable password authentication](/administration-and-security/access-control/#disable-password). + +## Always use a specific SAML configuration + +When a user clicks on "Single Sign-On" at the Flagsmith login screen, they will be prompted to enter the name of a SAML configuration. If you are self-hosting Flagsmith, you can skip this step and always use a specific SAML configuration by setting up the `sso_idp` [Flagsmith-on-Flagsmith](https://docs.flagsmith.com/deployment#running-flagsmith-on-flagsmith) flag. The text value of this flag should be the name of the SAML configuration to use. + +If you are using Flagsmith private cloud, [contact Flagsmith support](https://www.flagsmith.com/contact-us) once you have created your SAML configuration and validated it works correctly. + +## Canonicalization methods + +Some identity providers require the service provider to support canonicalization methods that are not allowed by default. You can see the methods that are enabled by default [here](https://github.com/IdentityPython/pysaml2/blob/88feeba03c2f891a31a86cbb24b210070aab1fdc/src/saml2/xmldsig/__init__.py#L67-L70). + +You can enable additional canonicalization methods by setting the `EXTRA_ALLOWED_CANONICALIZATIONS` environment variable to a comma-separated list of canonicalization method URIs. For example: + +```sh +EXTRA_ALLOWED_CANONICALIZATIONS=http://www.w3.org/TR/2001/REC-xml-c14n-20010315#,http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments +``` + +## Force SSL after authentication + +You can configure Flagsmith to ignore the `X-Forwarded-Proto` HTTP header and always use HTTPS for the ACS URL by setting the `SAML_FORCE_SSL` environment variable to `True`. + +## Troubleshooting + +If you need to [contact Flagsmith support](https://www.flagsmith.com/contact-us) or an administrator for help with SSO logins, the best way is to record and share a [HAR file](https://support.zendesk.com/hc/en-us/articles/4408828867098-Generating-a-HAR-file-for-troubleshooting) from your web browser where you try to log in to Flagsmith using your SAML identity provider. diff --git a/docs/docs/billing/index.mdx b/docs/docs/administration-and-security/billing-api-usage.md similarity index 82% rename from docs/docs/billing/index.mdx rename to docs/docs/administration-and-security/billing-api-usage.md index 757f20af1ee4..1639652fad1d 100644 --- a/docs/docs/billing/index.mdx +++ b/docs/docs/administration-and-security/billing-api-usage.md @@ -1,6 +1,6 @@ --- title: Billing and API Usage -sidebar_position: 120 +sidebar_position: 1 --- When you use Flagsmith on Flagsmith-managed infrastructure (SaaS or private cloud), your pricing plan has a limit of API @@ -31,7 +31,7 @@ The following screenshot shows an example usage graph from the Flagsmith dashboa When your application is integrated with Flagsmith, it makes requests to the Flagsmith API to perform actions such as retrieving the current state of its flags. Some of these actions count towards your limit of billable API requests. The -billable API requests that your application makes depend mainly on which [flag evaluation mode](/clients) it uses. +billable API requests that your application makes depend mainly on which [flag evaluation mode](/integrating-with-flagsmith/integration-overview) it uses. **Remote Evaluation** is the default for all applications. Applications using Remote Evaluation call the Flagsmith API when they need to fetch the flags for the current environment or user. @@ -39,15 +39,15 @@ billable API requests that your application makes depend mainly on which [flag e - `/identities`: [Get identity flags](https://api.flagsmith.com/api/v1/docs/#/api/api_v1_environments_identities_create) **Local Evaluation** is optional, and only for server-side applications. Each individual application or -[Edge Proxy](/advanced-use/edge-proxy) instance polls the Flagsmith API at a configurable interval (60 seconds by +[Edge Proxy](/performance/edge-proxy) instance polls the Flagsmith API at a configurable interval (60 seconds by default) to fetch the flags for the current environment and all users in one API request. - `/environment-document`: [Get environment document](https://api.flagsmith.com/api/v1/docs/#/api/api_v1_environment-document_list) The following requests are not billable: -- [Admin API](/clients/rest#private-admin-api-endpoints) requests. -- Requests made by Flagsmith SDKs to track [Flag Analytics](/advanced-use/flag-analytics). -- Connecting to a [real-time flag updates](/advanced-use/real-time-flags) stream. +- [Admin API](/integrating-with-flagsmith/flagsmith-api-overview/admin-api) requests. +- Requests made by Flagsmith SDKs to track [Flag Analytics](/managing-flags/flag-analytics). +- Connecting to a [real-time flag updates](/performance/real-time-flags) stream. ### Example: client-side application @@ -58,7 +58,7 @@ look like this: production environment (1 API request). 2. The user logs in to their account. Now that we know the user's identity, their device calls Flagsmith to get the flags for this specific user, taking into account their - [A/B tests or progressive rollouts](/advanced-use/ab-testing), [segments](/basic-features/segments) and user-specific + [A/B tests or progressive rollouts](/experimentation-ab-testing), [segments](/flagsmith-concepts/segments) and user-specific flags (1 API request). Any user data sent by your application is stored by Flagsmith as traits. 3. The user performs some action that should update their stored data in Flagsmith. The device calls Flagsmith to update this user's traits and receives their latest flags in return (1 API request). @@ -89,7 +89,7 @@ its lifecycle could look like this: - An instance of your application, running in a container or server, using the Flagsmith SDK with Local Evaluation enabled. -- An [Edge Proxy](/advanced-use/edge-proxy) container instance. +- An [Edge Proxy](/performance/edge-proxy) container instance. Following the example above, if you used the default polling frequency of 60 seconds, you could estimate your monthly API usage as: @@ -99,7 +99,7 @@ API usage as: ## What happens if I exceed my API call limit? If you exceed your API call limit, your account may be blocked or charged for overages depending on your pricing plan. -For more details, see [Traffic Limits](/system-administration/system-limits#traffic-limits). +For more details, see [Traffic Limits](/administration-and-security/governance-and-compliance/system-limits#traffic-limits). Accounts on paid plans are never blocked automatically. @@ -117,12 +117,12 @@ application. For example, when a user visits a certain part of your application a mobile application is brought to the foreground. * Consider using an Edge Proxy as your source of flags instead of calling the Flagsmith API directly. * If you have a suitable backend, consider evaluating flags server-side instead. This enables other usage patterns that -result in fewer API calls, such as [Local Evaluation](/clients#local-evaluation) and custom cache implementations. +result in fewer API calls, such as [Local Evaluation](/integrating-with-flagsmith/integration-overview#local-evaluation-mode) and custom cache implementations. For server-side applications: -* Consider using [Local Evaluation](/clients#local-evaluation), or deploying [Edge Proxies](/advanced-use/edge-proxy). +* Consider using [Local Evaluation](/integrating-with-flagsmith/integration-overview#local-evaluation-mode), or deploying [Edge Proxies](/performance/edge-proxy). * Reduce the polling rate for Edge Proxies and any SDKs using Local Evaluation. * If your use case allows for it, use the Flagsmith SDK in Offline Mode. -Using [real-time flag updates](/advanced-use/real-time-flags) can also reduce or eliminate the need for polling. +Using [real-time flag updates](/performance/real-time-flags) can also reduce or eliminate the need for polling. diff --git a/docs/docs/administration-and-security/data-management/_category_.json b/docs/docs/administration-and-security/data-management/_category_.json new file mode 100644 index 000000000000..de5603c3ba4d --- /dev/null +++ b/docs/docs/administration-and-security/data-management/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Data Management", + "position": 4, + "collapsed": true +} diff --git a/docs/docs/administration-and-security/data-management/bulk-import-and-export.md b/docs/docs/administration-and-security/data-management/bulk-import-and-export.md new file mode 100644 index 000000000000..6e459e73dff8 --- /dev/null +++ b/docs/docs/administration-and-security/data-management/bulk-import-and-export.md @@ -0,0 +1,50 @@ +--- +title: Bulk Import and Export +sidebar_label: Bulk Import & Export +sidebar_position: 30 +--- + +The bulk import and export of feature data associated with a given environment is possible in Flagsmith. The feature data that is exported includes multivariate features, but does not include other data such as tags, owners, group owners, etc. This functionality is useful for transferring features between any running instances of Flagsmith, even when only a subset of features (for example, importing features of a given tag) is needed. + +## What is exported? + +We **will** export the following data: + +- Flags +- Flag States (both boolean and text values) +- Multivariate values and weights + +We **will not** export the following data: + +- Feature-based Segments +- Segment overrides +- Flag [custom fields](/administration-and-security/governance-and-compliance/custom-fields) +- Flag Schedules +- Tags associated with Flags +- Individual and group owners associated with Flags + +## Exporting + +On the Export tab of the project settings page, it is possible to export the project's features using the environment's values. Simply select the source environment from the drop-down list and, if required, select one or more feature tags to filter the features. + +By clicking the Export Features button, the feature export should quickly process and a list of processed feature exports is available at the bottom of the page. + +:::tip +Feature exports are available for only two weeks, so if an export is needed for a longer period, be sure to make a local backup. +::: + +## Importing + +On the Import tab of the project settings page, you will find the feature import functionality, complete with file upload at the bottom of the page. + +:::caution +The target environment is the environment to inherit the features of the exported environment. All other environments will be set to the values defined when the feature was initially created. Use with caution, especially when using the Overwrite Destructive merge strategy. +::: + +### Merge Strategy + +Since a feature may have an identical name, it is important to carefully select a merge strategy during a feature import. + +The first option is the Skip strategy, which allows an import to process a feature export and, at any time a feature has a pre-existing feature present, it skips the import for that given feature. For example, if you are importing ten features but two of them were already there, only eight features will be added to the project. This strategy is best for organisations that want to retain their existing data as closely as possible. + +The second option is the Overwrite Destructive strategy. In contrast to the Skip strategy, the Overwrite Destructive method overwrites your existing features, and it is important to remember that this is across all environments. This strategy is most useful only when every feature that was included in the export was vetted to be authoritative in the target project. \ No newline at end of file diff --git a/docs/docs/system-administration/importing-and-exporting/django-admin.png b/docs/docs/administration-and-security/data-management/django-admin.png similarity index 100% rename from docs/docs/system-administration/importing-and-exporting/django-admin.png rename to docs/docs/administration-and-security/data-management/django-admin.png diff --git a/docs/docs/administration-and-security/data-management/import-and-export.md b/docs/docs/administration-and-security/data-management/import-and-export.md new file mode 100644 index 000000000000..f9815b441998 --- /dev/null +++ b/docs/docs/administration-and-security/data-management/import-and-export.md @@ -0,0 +1,24 @@ +--- +title: Import and Export +sidebar_label: Import and Export +sidebar_position: 10 +--- + +You can import and export your data between Flagsmith organisations, instances, or other feature flagging services. + +We recommend understanding the [Flagsmith data model](/flagsmith-concepts/data-model) before importing or +exporting any data. + +## Flagsmith import/export + +There are several ways of importing or exporting Flagsmith data: + +* [Bulk feature import/export](bulk-import-and-export). Copy all or a subset of features + between Flagsmith projects. +* [Organisation import/export](organisations-import-export). Export an entire + Flagsmith organisation and copy it to a different Flagsmith instance. +* Use the [Admin API](/integrating-with-flagsmith/flagsmith-api-overview/admin-api) to manually manage your Flagsmith data. + +## Import from third-party services + +You can [import LaunchDarkly flags and segments](import-from-launchdarkly) into Flagsmith. diff --git a/docs/docs/system-administration/importing-and-exporting/launchdarkly.md b/docs/docs/administration-and-security/data-management/import-from-launchdarkly.md similarity index 54% rename from docs/docs/system-administration/importing-and-exporting/launchdarkly.md rename to docs/docs/administration-and-security/data-management/import-from-launchdarkly.md index 8b6c9d585c2a..770eb5bfc4f1 100644 --- a/docs/docs/system-administration/importing-and-exporting/launchdarkly.md +++ b/docs/docs/administration-and-security/data-management/import-from-launchdarkly.md @@ -1,13 +1,13 @@ --- -title: LaunchDarkly Migrator - Migrate from LaunchDarkly +title: Import from LaunchDarkly description: Migrate your flags and project from LaunchDarkly into Flagsmith -sidebar_label: LaunchDarkly -sidebar_position: 10 +sidebar_label: Import from LaunchDarkly +sidebar_position: 20 --- # LaunchDarkly Migrator - Migrate from LaunchDarkly -You can import your Flags and Segments from LaunchDarkly into Flagsmith. +This guide explains how to migrate your flags and segments from LaunchDarkly into Flagsmith. :::caution @@ -17,17 +17,28 @@ import has finished. ::: +## Prerequisites + +- **LaunchDarkly access token**: You will need an access token from your LaunchDarkly account. To generate the token: + 1. Log in to your LaunchDarkly account. + 2. Navigate to **Account settings** > **Authorization** > **Access tokens**. + 3. Create a new Access Token. + +## Integration Setup + :::caution Import operations will overwrite existing environments and flags in your project. ::: -## Integration Setup +Follow these steps to set up the integration and initiate the import: -1. Create a LaunchDarkly Access Token. In LaunchDarkly: Account settings > Authorization > Access tokens. -2. Create a new Project within Flagsmith, then go to Project Settings > Import. Add your Token generated in step 1. -3. The import will begin immediately. +1. Import into Flagsmith: + - Create a new project within Flagsmith, or select an existing one. + - Go to **project settings** > **Import**. + - Paste the Access Token previously generated into the provided field. +2. Start the import: The import process will begin immediately upon adding the token. ## What we will import @@ -39,16 +50,15 @@ All of the LaunchDarkly `Environments` within the Project will be copied into Fl ### Flags -LaunchDarkly `Flags` will be copied into Flagsmith. +LaunchDarkly `Flags` will be copied into Flagsmith as follows: -#### Boolean Flags +#### Boolean flags -Boolean LaunchDarkly flags are imported into Flagsmith with the appropriate boolean state, with no flag value set on the -Flagsmith side. +Boolean LaunchDarkly flags are imported into Flagsmith with the appropriate boolean state, with no flag value set on the Flagsmith side. Boolean values will be taken from the `_summary -> on` field of within LaunchDarkly. -#### Multivariate Flags +#### Multivariate flags Multivariate LaunchDarkly flags will be imported into Flagsmith as MultiVariate Flagsmith flag values. diff --git a/docs/docs/system-administration/importing-and-exporting/organisations.md b/docs/docs/administration-and-security/data-management/organisations-import-export.md similarity index 94% rename from docs/docs/system-administration/importing-and-exporting/organisations.md rename to docs/docs/administration-and-security/data-management/organisations-import-export.md index 490a9d28ff19..b52411152a59 100644 --- a/docs/docs/system-administration/importing-and-exporting/organisations.md +++ b/docs/docs/administration-and-security/data-management/organisations-import-export.md @@ -1,6 +1,7 @@ --- -title: Organisations -sidebar_position: 110 +title: Organisations Import and Export +sidebar_label: Organisations Import and Export +sidebar_position: 40 --- You can import and export an entire Flagsmith organisation. This lets you: @@ -176,7 +177,7 @@ following methods: * From the Flagsmith dashboard, click your organisation name in the top left. The organisation ID is displayed in the URL bar: `https://flagsmith.example.com/organisation/YOUR_ORGANISATION_ID/...`. -* From [Django Admin](/deployment/configuration/django-admin), browse to the Organisations section in the sidebar. +* From [Django Admin](/deployment-self-hosting/administration-and-maintenance/using-the-django-admin), browse to the Organisations section in the sidebar. Here you can see all of your organisations and their IDs. * If you have an Admin API key, call the [List Organisations API endpoint](https://api.flagsmith.com/api/v1/docs/#/api/api_v1_organisations_list). This @@ -233,7 +234,7 @@ S3. ### Accessing an imported organisation After you import an organisation, you will need to add your Flagsmith user to it. To do this, edit the imported -organisation from [Django Admin](/deployment/configuration/django-admin) and add your user to it with Admin permissions: +organisation from [Django Admin](/deployment-self-hosting/administration-and-maintenance/using-the-django-admin) and add your user to it with Admin permissions: ![](django-admin.png) diff --git a/docs/docs/administration-and-security/governance-and-compliance/_category_.json b/docs/docs/administration-and-security/governance-and-compliance/_category_.json new file mode 100644 index 000000000000..04dde92bedd9 --- /dev/null +++ b/docs/docs/administration-and-security/governance-and-compliance/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Governance and Compliance", + "position": 3, + "collapsed": true +} diff --git a/docs/docs/administration-and-security/governance-and-compliance/api-usage.md b/docs/docs/administration-and-security/governance-and-compliance/api-usage.md new file mode 100644 index 000000000000..49931fa7b5e5 --- /dev/null +++ b/docs/docs/administration-and-security/governance-and-compliance/api-usage.md @@ -0,0 +1,16 @@ +--- +title: API Usage +--- + +Flagsmith will track API calls made by the SDKS and store them in its data-store. You can view this data by going to the Organisation settings page and clicking on **Usage**. You can also drill down into Projects and Environments. Flagsmith tracks the following request types: + +1. Get Flags +2. Get Identity Flags +3. Set Identity Traits +4. Get [Environment Document](/integrating-with-flagsmith/integration-overview) (for local evaluation SDKs) + +![API Usage](/img/api-usage.png) + +## Flag Usage + +Flagsmith also tracks Flag Evaluations with [Flag Analytics](/managing-flags/flag-analytics). diff --git a/docs/docs/system-administration/audit-logs.md b/docs/docs/administration-and-security/governance-and-compliance/audit-logs.md similarity index 80% rename from docs/docs/system-administration/audit-logs.md rename to docs/docs/administration-and-security/governance-and-compliance/audit-logs.md index fcbd186b7ac5..f032957bcb60 100644 --- a/docs/docs/system-administration/audit-logs.md +++ b/docs/docs/administration-and-security/governance-and-compliance/audit-logs.md @@ -1,5 +1,7 @@ --- title: Audit Logs +sidebar_label: Audit Logs +sidebar_position: 30 --- Every action taken within the Flagsmith administration application is tracked and logged. This allows you to easily @@ -11,7 +13,7 @@ after. ## Audit Log Web Hooks You can also stream your Audit Logs into your own infrastructure using -[Audit Log Web Hooks](/system-administration/webhooks#audit-log-web-hooks). +[Audit Log Web Hooks](/third-party-integrations/webhook#audit-log-webhooks). ## Event Types @@ -19,19 +21,19 @@ Flagsmith records the following events into the Audit Log. ### Environments -- New environment created within a Project +- New environment created within a project - Environment meta-data updated ### Flags -- New Flag created +- New flag created - Flag state changed - Flag deleted - Multivariate flag state changed ### Segments -- New Segment created +- New segment created - Segment rule updated - Segment condition added - Segment condition updated diff --git a/docs/docs/administration-and-security/governance-and-compliance/change-requests.md b/docs/docs/administration-and-security/governance-and-compliance/change-requests.md new file mode 100644 index 000000000000..b455d5f727e9 --- /dev/null +++ b/docs/docs/administration-and-security/governance-and-compliance/change-requests.md @@ -0,0 +1,56 @@ +--- +title: Change Requests +sidebar_label: Change Requests +sidebar_position: 10 +--- + +You can use Change Requests to add workflow control to the changing of flag values. Change Requests allow a user to propose a change to a flag value, and then require that change is approved by a number of other team members. + +You can use Change Requests to ensure that, for example, a change to a flag in Production has to be approved by another team member. They work in a similar way to Pull Requests in git. This guide explains how to use Change Requests to add workflow control to changing flag values. You will learn how to set up, create, approve, and publish Change Requests. + +## Prerequisites + +- Change Requests are available only with our Scale-Up and Enterprise plans. Make sure you are subscribed to one of these. + +## Set Up Change Requests + +Change Requests are configured at the environment level. To enable and set up Change Requests: + +1. Go to the **Environment Settings Page**. +2. Enable the **Change Request** setting. +3. Select the required number of approvals for each Change Request to be applied. + +## Create a Change Request + +Once an environment is configured with Change Requests enabled, attempting to change a flag value will prompt you to create a new Change Request. + +:::info + +Any user with permission to *update* a feature within the environment can create a Change Request. + +::: + +When creating a Change Request, you will need to provide the following: + +* The **title** of the Change Request. +* An **optional description** of the reason for the Change Request. +* Any number of **assignees**. These individuals will receive an email notification about the Change Request. + +## Approve a Change Request + +Change Requests awaiting approval are listed in the **Change Request** area. + +:::info + +Any user with permission to write to the environment containing the Change Request can approve it. + +::: + +1. Click on a **Change Request** to view its details. +2. Review the current and new Flag values. + +## Publish a Change Request + +When the required number of approvals have been made, you will be able to publish the Change Request. + +The Change Request will immediately come into effect once the **Publish Change** button is clicked. diff --git a/docs/docs/administration-and-security/governance-and-compliance/custom-fields.md b/docs/docs/administration-and-security/governance-and-compliance/custom-fields.md new file mode 100644 index 000000000000..dcd0970f6817 --- /dev/null +++ b/docs/docs/administration-and-security/governance-and-compliance/custom-fields.md @@ -0,0 +1,132 @@ +--- +title: Custom Fields +sidebar_label: Custom Fields +sidebar_position: 20 +--- + +:::info + +Custom fields are available on [Enterprise plans](https://flagsmith.com/pricing). + +Custom fields were introduced in Flagsmith [2.116.0](https://github.com/Flagsmith/flagsmith/releases/tag/v2.116.0). + +::: + +This guide explains how to define and manage custom fields in Flagsmith. Custom fields allow you to store additional, relevant information alongside Flagsmith entities such as flags, segments, or environments. You can use them to: + +- Relate every flag to a ticket in your issue tracker so that everyone knows what a flag does and what state of development it is in. +- Add a link from Flagsmith segments to the corresponding segments in your analytics platform. +- Add a detailed description to your Flagsmith environments. + +You can define **custom fields** that are shown when creating or modifying [**flags**](/managing-flags/core-management), [**segments**](/flagsmith-concepts/segments) or [**environments**](/flagsmith-concepts/data-model#environments). Custom fields are defined per [organisation](/flagsmith-concepts/data-model#organisations). + +The following screenshot shows a custom field named "Ticket URL" that is displayed to Flagsmith users when editing or creating a feature: + +![](/img/metadata/metadata-example.png) + +## Prerequisities + +- To define custom fields, you must have administrator permissions for the relevant project. +- To update the values of custom fields, you must have edit permissions for the specific entity. + +## Define custom fields + +Follow these steps to define a custom field: + +1. Navigate to **Organisation Settings**. +2. Select **Custom Fields**. +3. Click on the option to create a new custom field. +4. A custom field can have the following properties: + + - **Name**: the visible name of the field as it will be presented to users. + - **Description**: a short description of what the field is for. It is only shown when editing custom field definitions. + - **Type**: validates this field's data when saving to be one of the following types: + - **String**: A single line of text. + - **Multi-line string**: One or more lines of text. + - **Integer**: Whole numbers without a decimal point. + - **Boolean**: A binary choice between true or false. + - **URL**: An absolute URL with an explicit scheme as defined by Python's + [urlparse](https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlparse) function. For example, + `https://flagsmith.com` is considered valid but `/example/foo` is not. + - **Entities**: Which Flagsmith entities to display these fields for during creation or editing. The following options + are allowed: + - Features + - Segments + - Environments + - **Required**: Lets you choose whether a field is required for each entity it is associated with. For example, you could make a ticket URL required for features but optional for segments. + +## Modify Existing Fields + +Field data types are only validated when saving. Modifying existing field data types will not delete or modify existing data. + +Changing an optional field into a required field will prevent you from creating or updating the relevant entities without providing a value for that field. + +Deleting custom fields will also delete all data associated with that field. + +## Consume custom fields + +Custom fields are mainly intended to be read by humans visiting the Flagsmith dashboard, typically for traceability or accountability purposes. + +Custom field values associated with features belong to the features themselves, and not an environment's feature state; you cannot override custom field values in different environments, segments or identities. They are not returned to applications that consume flags. + +Custom field values are added directly to the `metadata` field of the entity they are defined in, which can be read using the [Flagsmith Admin API](/integrating-with-flagsmith/flagsmith-api-overview/admin-api). For example, to fetch a feature's custom fields, use the [endpoint to fetch a feature by ID](https://api.flagsmith.com/api/v1/docs/#/api/api_v1_projects_features_read): + +```shell +curl "https://api.flagsmith.com/api/v1/projects/YOUR_PROJECT_ID/features/YOUR_FEATURE_ID/" \ + -H "Authorization: Api-Key YOUR_API_KEY" +``` + +This returns information about the feature itself, including `metadata` which contains the custom field values: + +```json +{ + ... + "metadata": [ + { + "id": 123, + "model_field": 128, + "field_value": "https://example.com/FOO-123" + }, + { + "id": 124, + "model_field": 129, + "field_value": "Example Team" + } + ] +} +``` + +To fetch the field definitions themselves, use the +[endpoint to fetch metadata model fields](https://api.flagsmith.com/api/v1/docs/#/api/api_v1_organisations_metadata-model-fields_list). +For example: + +```shell +curl "https://api.flagsmith.com/api/v1/organisations/YOUR_ORGANISATION_ID/metadata-model-fields/ \ + -H "Authorization: Api-Key YOUR_API_KEY" +``` + +This returns the definition of each custom field: + +```json +{ + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "id": 178, + "name": "Ticket URL", + "type": "url", + "description": "URL to ticket in our issue tracker", + "organisation": 15467 + }, + { + "id": 179, + "name": "Product code", + "type": "int", + "description": "Product code associated with this feature", + "organisation": 15467 + } + ] +} +``` diff --git a/docs/docs/system-administration/security.md b/docs/docs/administration-and-security/governance-and-compliance/security.md similarity index 87% rename from docs/docs/system-administration/security.md rename to docs/docs/administration-and-security/governance-and-compliance/security.md index bebc10d713b8..b3f96442f7f5 100644 --- a/docs/docs/system-administration/security.md +++ b/docs/docs/administration-and-security/governance-and-compliance/security.md @@ -1,5 +1,7 @@ --- title: Security +sidebar_label: Security +sidebar_position: 40 --- ## Preventing Client SDKS from setting Traits @@ -8,16 +10,13 @@ There may be use-cases where you want to prevent client-side SDKs from setting t setting `plan=silver` as a trait, and then enabling/disabling features based on that plan, a malicious user could, with a client-side SDK, update their trait to `plan=gold` and unlock features they have not paid for. -You can prevent this by disabling the "Persist traits when using client-side SDK keys" option. This option defaults to "On". -Turning it "Off" will not allow client-side SDKs to write Traits to Flagsmith. In order to write traits, you will need -to use a [server-side SDK and server-side Key](/clients). +You can prevent this by disabling the "Persist traits when using client-side SDK keys" option. This option defaults to "On". Turning it "Off" will not allow client-side SDKs to write traits to Flagsmith. In order to write traits, you will need to use a [server-side SDK and server-side Key](/integrating-with-flagsmith/integration-overview). This is a per-Environment setting. ## Environment Banners -You can optionally provide a coloured banner for your Environments in each Environment Settings page. This helps you -identify sensitive Environments before toggling Flags. +You can optionally provide a coloured banner for your environments in each Environment Settings page. This helps you identify sensitive environments before toggling flags. ## Hide Sensitive Data @@ -121,7 +120,7 @@ To this: :::info -All fields mentioned are not part of the response generated by the [Edge API](/advanced-use/edge-api.md). +All fields mentioned are not part of the response generated by the [Edge API](/edge-api/overview). ::: @@ -232,7 +231,7 @@ To this :::info -Responses generated by [Edge API](/advanced-use/edge-api.md) already excludes all the above-mentioned fields apart from +Responses generated by [Edge API](/edge-api/overview) already excludes all the above-mentioned fields apart from `traits` ::: diff --git a/docs/docs/system-administration/system-limits.md b/docs/docs/administration-and-security/governance-and-compliance/system-limits.md similarity index 80% rename from docs/docs/system-administration/system-limits.md rename to docs/docs/administration-and-security/governance-and-compliance/system-limits.md index 0c7a5b2931ef..4bbe768ce207 100644 --- a/docs/docs/system-administration/system-limits.md +++ b/docs/docs/administration-and-security/governance-and-compliance/system-limits.md @@ -1,24 +1,26 @@ --- title: System Limits +sidebar_label: System Limits +sidebar_position: 60 --- In order to ensure consistent performance, Flagsmith has the following limitations. ### Entity Counts -- **400** Features per Project -- **100** Segments per Project -- **100** Segment Overrides per Environment -- **100** Segment Rules Conditions +- **400** features per project +- **100** segments per project +- **100** segment overrides per environment +- **100** segment rules conditions ### Entity Data Elements -- Maximum size of a Flag String Value is **20,000 bytes** -- Maximum size of an Identity Trait Value is **2,000 bytes** +- Maximum size of a flag string value is **20,000 bytes** +- Maximum size of an identity trait value is **2,000 bytes** ### Segment Data Elements -- Maximum size of a Segment Rule Value is **1,000 bytes** +- Maximum size of a segment rule value is **1,000 bytes** ## Overriding Limits @@ -29,7 +31,7 @@ Please contact us if you want to override the current system limits. ### Self Hosted You can modify the system limits on a per-Project basis. These limits are defined in the database against the Project. -The easiest way to modify them is with the [Django admin](/deployment/configuration/django-admin.md) interface. +The easiest way to modify them is with the [Django admin](/deployment-self-hosting/administration-and-maintenance/using-the-django-admin) interface. ## Traffic Limits @@ -73,7 +75,7 @@ the end of your billing period. ### Admin API Rate Limit -Requests made to [Admin API endpoints](/clients/rest#private-admin-api-endpoints) (i.e., non-SDK endpoints) are subject +Requests made to [Admin API endpoints](/integrating-with-flagsmith/flagsmith-api-overview/admin-api) (i.e., non-SDK endpoints) are subject to a default rate limit of 500 requests per minute. If you are self-hosting, you have the flexibility to modify this limit by adjusting the value of the environment diff --git a/docs/docs/billing/usage-graph.png b/docs/docs/administration-and-security/governance-and-compliance/usage-graph.png similarity index 100% rename from docs/docs/billing/usage-graph.png rename to docs/docs/administration-and-security/governance-and-compliance/usage-graph.png diff --git a/docs/docs/administration-and-security/index.mdx b/docs/docs/administration-and-security/index.mdx new file mode 100644 index 000000000000..1a2243e49035 --- /dev/null +++ b/docs/docs/administration-and-security/index.mdx @@ -0,0 +1,167 @@ +--- +title: Administration and Security +description: Manage access control, compliance, data management, and platform configuration +--- + +import Card from '../../src/components/Card'; +import Link from '@docusaurus/Link'; +import { IonIcon } from '@ionic/react'; +import { + key, + people, + shield, + analytics, + documentText, + lockClosed, + cog, + server, + statsChart, + archive, + settings, + laptop, + warning +} from 'ionicons/icons'; + +This section provides comprehensive guidance for administrators on managing Flagsmith at an organisational level. From user authentication and role-based access control to platform configuration and compliance monitoring. + +## Manage Access Control and Authentication + +Configure how users log in and what they can access within your Flagsmith organisation. + +
+ + +
+
+ +
+

User Management & Permissions

+
+

Control user roles, groups, and fine-grained permissions

+
+ + +
+
+ +
+

Authentication Management

+
+

Set up authentication methods including SSO, SAML, LDAP, and 2FA

+
+ + +
+
+ +
+

Enterprise SSO

+
+

Single sign-on integration with SAML, Okta, LDAP, and Microsoft ADFS

+
+ +
+ +## Governance and Compliance + +Ensure your feature flag practices meet compliance requirements and organisational governance standards. + +
+ + +
+
+ +
+

Security Controls

+
+

Platform security settings and best practices

+
+ + +
+
+ +
+

Audit & Compliance

+
+

Track changes and access for compliance auditing

+
+ + +
+
+ +
+

System Limits

+
+

Understand platform limits and request overrides

+
+ +
+ +## Data Management + +Handle data migration, import/export operations, and backup strategies for your Flagsmith data. + +
+ + +
+
+ +
+

Bulk Import & Export

+
+

Mass operations for features, segments, and identities

+
+ + +
+
+ +
+

Organisation Migration

+
+

Migrate complete organisations between instances

+
+ +
+ +## Platform Configuration + +Configure environment-specific settings and platform-wide options for your Flagsmith deployment. + +
+ + +
+
+ +
+

Environment Settings

+
+

Configure environment-level options and configurations

+
+ + +
+
+ +
+

Platform Metrics

+
+

System monitoring and performance metrics

+
+ + +
+
+ +
+

API Usage & Monitoring

+
+

Track and analyse API usage and billing

+
+ +
diff --git a/docs/docs/administration-and-security/platform-configuration/_category_.json b/docs/docs/administration-and-security/platform-configuration/_category_.json new file mode 100644 index 000000000000..f4e818037002 --- /dev/null +++ b/docs/docs/administration-and-security/platform-configuration/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Platform Configuration", + "position": 5, + "collapsed": true +} diff --git a/docs/docs/system-administration/environment-settings.md b/docs/docs/administration-and-security/platform-configuration/environment-settings.md similarity index 86% rename from docs/docs/system-administration/environment-settings.md rename to docs/docs/administration-and-security/platform-configuration/environment-settings.md index 3b911a1cb6df..7f27d3fab85f 100644 --- a/docs/docs/system-administration/environment-settings.md +++ b/docs/docs/administration-and-security/platform-configuration/environment-settings.md @@ -1,5 +1,7 @@ --- title: Environment Settings +sidebar_label: Environment Settings +sidebar_position: 10 --- ## Use Consistent Hashing @@ -20,4 +22,4 @@ split operator) when evaluated against the remote API. Evaluations in local eval ## Custom fields Optional or required custom fields can be defined when creating or updating environments. -[Learn more](/advanced-use/custom-fields.md) +[Learn more](/administration-and-security/governance-and-compliance/custom-fields) diff --git a/docs/docs/system-administration/metrics.md b/docs/docs/administration-and-security/platform-configuration/metrics.md similarity index 97% rename from docs/docs/system-administration/metrics.md rename to docs/docs/administration-and-security/platform-configuration/metrics.md index 7863a71ba34e..e58a4d14bec0 100644 --- a/docs/docs/system-administration/metrics.md +++ b/docs/docs/administration-and-security/platform-configuration/metrics.md @@ -1,5 +1,7 @@ --- title: Metrics +sidebar_label: Metrics +sidebar_position: 20 --- ## Prometheus diff --git a/docs/docs/administration-and-security/usage-graph.png b/docs/docs/administration-and-security/usage-graph.png new file mode 100644 index 0000000000000000000000000000000000000000..d02fc58fb1442a91debe7c45764cf0715b4759f6 GIT binary patch literal 30217 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 hVlrDUaCWu9+g Custom Fields**. - -Custom fields have the following properties: - -- **Name**: the visible name of the field as it will be presented to users. -- **Description**: a short description of what the field is for. It is only shown when editing custom field definitions. -- **Type**: validates this field's data when saving to be one of the following types: - - **String**: A single line of text. - - **Multi-line string**: One or more lines of text. - - **Integer**: Whole numbers without a decimal point. - - **Boolean**: A binary choice between true or false. - - **URL**: An absolute URL with an explicit scheme as defined by Python's - [urlparse](https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlparse) function. For example, - `https://flagsmith.com` is considered valid but `/example/foo` is not. -- **Entities**: Which Flagsmith entities to display these fields for during creation or editing. The following options - are allowed: - - Features - - Segments - - Environments -- **Required**: Lets you choose whether a field is required for each entity it is associated with. For example, you - could make a ticket URL required for features but optional for segments. - -## Modifying existing fields - -Field data types are only validated when saving. Modifying existing field data types will not delete or modify existing -data. - -Changing an optional field into a required field will prevent you from creating or updating the relevant entities -without providing a value for that field. - -Deleting custom fields will also delete all data associated with that field. - -## Consuming custom fields - -Custom fields are mainly intended to be read by humans visiting the Flagsmith dashboard, typically for traceability or -accountability purposes. - -Custom field values associated with features belong to the features themselves, and not an environment's feature state; -you cannot override custom field values in different environments, segments or identities. They are not returned to -applications that consume flags. - -Custom field values are added directly to the `metadata` field of the entity they are defined in, which can be read -using the [Flagsmith Admin API](/clients/rest#private-admin-api-endpoints). For example, to fetch a feature's custom -fields, use the -[endpoint to fetch a feature by ID](https://api.flagsmith.com/api/v1/docs/#/api/api_v1_projects_features_read): - -```shell -curl "https://api.flagsmith.com/api/v1/projects/YOUR_PROJECT_ID/features/YOUR_FEATURE_ID/" \ - -H "Authorization: Api-Key YOUR_API_KEY" -``` - -This returns information about the feature itself, including `metadata` which contains the custom field values: - -```json -{ - ... - "metadata": [ - { - "id": 123, - "model_field": 128, - "field_value": "https://example.com/FOO-123" - }, - { - "id": 124, - "model_field": 129, - "field_value": "Example Team" - } - ] -} -``` - -To fetch the field definitions themselves, use the -[endpoint to fetch metadata model fields](https://api.flagsmith.com/api/v1/docs/#/api/api_v1_organisations_metadata-model-fields_list). -For example: - -```shell -curl "https://api.flagsmith.com/api/v1/organisations/YOUR_ORGANISATION_ID/metadata-model-fields/ \ - -H "Authorization: Api-Key YOUR_API_KEY" -``` - -This returns the definition of each custom field: - -```json -{ - "count": 2, - "next": null, - "previous": null, - "results": [ - { - "id": 178, - "name": "Ticket URL", - "type": "url", - "description": "URL to ticket in our issue tracker", - "organisation": 15467 - }, - { - "id": 179, - "name": "Product code", - "type": "int", - "description": "Product code associated with this feature", - "organisation": 15467 - } - ] -} -``` diff --git a/docs/docs/advanced-use/edge-proxy.md b/docs/docs/advanced-use/edge-proxy.md deleted file mode 100644 index d67108555527..000000000000 --- a/docs/docs/advanced-use/edge-proxy.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -title: Edge Proxy ---- - -The Flagsmith Edge Proxy is a service that you host yourself, that allows you to run an instance of the Flagsmith Engine -close to your servers. If you are running Flagsmith within a server-side environment and you want to have very low -latency flags, you have two options: - -1. Run the Edge Proxy within in your own infrastructure and connect to it from your server-side SDKs -2. Run your server-side SDKs in [Local Evaluation Mode](/clients#local-evaluation). - -The main benefit to running the Edge Proxy is that you reduce your polling requests against the Flagsmith API itself. - -The main benefit to running server side SDKs in [Local Evaluation Mode](/clients#local-evaluation) is that you get the -lowest possible latency. - -## How does it work - -:::info - -The Edge Proxy has the same [caveats as running our SDK in Local Evaluation mode.](/clients/#local-evaluation). - -::: - -You can think of the Edge Proxy as a copy of our Python Server Side SDK, running in -[Local Evaluation Mode](/clients#local-evaluation), with an API interface that is compatible with the Flagsmith SDK API. - -The Edge Proxy runs as a lightweight Docker container. It connects to the Flagsmith API (either powered by us at -api.flagsmith.com or self hosted by you) to get Environment Flags and Segment rules. You can then point the Flagsmith -SDKs to your Edge Proxy; it implements all the current SDK endpoints. This means you can serve a very large number of -requests close to your infrastructure and users, at very low latency. Check out the [architecture below](#architecture). - -The Proxy also acts as a local cache, allowing you to make requests to the Proxy without hitting the core API. - -## Performance - -The Edge Proxy can currently serve ~2,000 requests per second (RPS) at a mean latency of ~7ms on an M1 MacBook Pro with -a simple set of flags. Working with more complex Environments with many Segment rules will bring this RPS number down. - -It is stateless and hence close to perfectly scalable being deployed behind a load balancer. - -## Managing Traits - -There is one caveat with the Edge Proxy. Because it is entirely stateless, it is not able to persist Trait data into any -sort of datastore. This means that you _have_ to provide the full complement of Traits when requesting the Flags for a -particular Identity. Our SDKs all provide relevant methods to achieve this. An example using `curl` would read as -follows: - -```bash -curl -X "POST" "http://localhost:8000/api/v1/identities/?identifier=do_it_all_in_one_go_identity" \ - -H 'X-Environment-Key: n9fbf9h3v4fFgH3U3ngWhb' \ - -H 'Content-Type: application/json; charset=utf-8' \ - -d $'{ - "traits": [ - { - "trait_value": 123.5, - "trait_key": "my_trait_key" - }, - { - "trait_value": true, - "trait_key": "my_other_key" - } - ], - "identifier": "do_it_all_in_one_go_identity" -}' -``` - -Note that the Edge Proxy will currently _not_ send the Trait data back to the Core API. - -## Deployment and Configuration - -Please see the [hosting documentation](/deployment/hosting/locally-edge-proxy). - -## Architecture - -The standard Flagsmith architecture: - -![Image](/img/edge-proxy-existing.svg) - -With the proxy added to the mix: - -![Image](/img/edge-proxy-proxy.svg) diff --git a/docs/docs/advanced-use/flag-analytics.md b/docs/docs/advanced-use/flag-analytics.md deleted file mode 100644 index 06d9176f728e..000000000000 --- a/docs/docs/advanced-use/flag-analytics.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: Flag Analytics ---- - -## Overview - -Flag Analytics allow you to track how often individual Flags are evaluated within the Flagsmith SDK. - -To view Analytics for a particular flag, browse to the relevant environment and click on a single flag to edit that -flag. - -![Image](/img/flag-analytics.png) - -Flag Analytics can be really useful when removing flags from Flagsmith. More often than not, flags can be removed from -your codebase and platform once they have been rolled out and everyone is comfortable with them running in production. - -Once you have removed the evaluation code from your code base, its nice to be sure that all references to that flag have -been removed, and that removing the flag itself from Flagsmith will not cause any unforeseen issues. Flag Analytics help -with this. - -Flag Analytics can also be helpful when identifying integration issues. Occasionally errors can creep into your code -that cause multiple needless evaluations of a flag. Again, these analytics can help isolate these situations. - -## Enabling Flag Analytics? - -:::info - -The Flag Analytics data will be visible in the Dashboard between 30 minutes and 1 hour after it has been collected. - -::: - -Flag analytics are disabled by default in our SDKs. You need to explicitly enable it when you initialize the Flagsmith -client. Please refer to the corresponding SDK documentation for more details. For the Javascript family SDKs please -refer to [Initialisation options](https://docs.flagsmith.com/clients/javascript#initialisation-options). - -## How does it work? - -Every time a flag is evaluated within the SDK (generally a call to a method like -`flagsmith.hasFeature("myCoolFeature")`), the SDK keeps a track of the flag name along with an evaluation count. - -Every `n` seconds (currently set to 10 seconds in the JS SDK) the SDK sends a message to the Flagsmith API with the list -of flags that have been evaluated and their count. If no flags have been evaluated in that time window, no message is -sent. diff --git a/docs/docs/advanced-use/flag-management.md b/docs/docs/advanced-use/flag-management.md deleted file mode 100644 index e8da7a97ae76..000000000000 --- a/docs/docs/advanced-use/flag-management.md +++ /dev/null @@ -1,83 +0,0 @@ -# Flag Management - -Managing larger numbers of flags is made easier using some of the tools built into Flagsmith. - -## Tagging - -You can create tags within Flagsmith and tag Flags in order to organise them. Tags can also be used to filter the list -of Flags in the event that you have a large number. - -:::info Protected Tags - -Tags with the following names will prevent users from being able to delete tagged Flags via the dashboard: - -- `protected` -- `donotdelete` -- `permanent` - -::: - -## Server-side only Flags - -When creating a flag, you can optionally define it as "Server-Side Only". Enabling this option for the flag will prevent -it from being returned to Client-side SDKs. - -## Hide disabled Flags from SDKs - -To prevent letting your users know about your upcoming features and to cut down on payload, enabling this will prevent -the API from returning any flags that are disabled. - -You can set this at both the Project and Environment level. - -## Flag Archiving - -You can also archive Flags within Flagsmith. Archived flags will continue to be sent to your SDKs when you get the flags -for your Environment, but by default they are hidden from the main list of flags. - -You can set a Flag as Archived from the Flag settings tab. - -Archived flags are often used when you have customers running older versions of your mobile app. You may well have -finished with a flag, but you can't remove it as there are still some older versions of your app out there that depend -on that flag. Archiving the flag helps to keep your main list of flags under control. - -## Case Sensitive Flags - -By default, Flagsmith stores flags with lower case characters in order to minimise human error. If you want to store -flags in a case-sensitive manner you can do this as a Project-wide setting from the Project Settings page. - -:::tip - -We don't recommend making your Flags case sensitive. This can lead to bugs related to case sensitivity and flags not -being found at runtime. - -::: - -## Feature Name Regular Expressions - -You can enforce feature name String formatting by way of a regular expression in the Project Settings area. If you want -flags to always be lower case, or camel case, or whatever your preference, you can set it here. - -## Flag Owners - -You can specify members of your team as owners of individual Flags. This helps in larger teams when you need to identify -who is responsible for a particular flag. - -## Flag Defaults - -By default, when you create a feature with a value and enabled state it acts as a default for your other Environments. -In the Project Settings page, you have the option of enabling the setting 'Prevent flag defaults' to prevent this -behaviour. When this setting is enabled the user is not able to provide defaults when creating the feature. The feature -will be created with an empty value and will be turned off in all environments. Users are then required to modify the -state / value of the feature in each environment individually. - -## Comparing Flags - -You can compare Flags both across Environments and for individual Flags. - -### Environment Comparison - -Use the "Compare" menu item to get an overview of how flag values differ between any two Environments: - -### Flag Comparison - -You can also view the values of a single Flag against all the Environments within the Project: diff --git a/docs/docs/advanced-use/real-time-flags.md b/docs/docs/advanced-use/real-time-flags.md deleted file mode 100644 index 7bc5ec84cdd6..000000000000 --- a/docs/docs/advanced-use/real-time-flags.md +++ /dev/null @@ -1,109 +0,0 @@ ---- -title: Real-time flag updates -sidebar_label: Real-time Flags ---- - -When an application fetches its current feature flags, it usually caches the flags for a certain amount of time to make -[efficient use](/guides-and-examples/efficient-api-usage) of the Flagsmith API and network resources. In some cases, you -may want an application to be notified about feature flag updates without needing to repeatedly call the Flagsmith API. -You can achieve this by subscribing to real-time flag updates. - -## Prerequisites - -Real-time flag updates require an Enterprise subscription. - -If you are self-hosting Flagsmith, real-time flag updates require -[additional infrastructure](/deployment/hosting/real-time). - -## Setup - -To enable real-time flag updates for your Flagsmith project: - -1. Log in to the Flagsmith dashboard as a user with project administrator permissions. -2. Navigate to **Project Settings > SDK Settings**. -3. Enable **Real-time updates**. - -Applications using a supported Flagsmith SDK do not subscribe to real-time flag updates by default. Refer to your SDK's -documentation for subscribing to real-time flag updates. - -## How it works - -The following sequence diagram shows how a typical application would use real-time flag updates. -[Billable API requests](/billing) are highlighted in yellow. - -```mermaid -sequenceDiagram - rect rgb(255,245,173) - Application->>Flagsmith: Fetch initial flags - Flagsmith->>Application: #nbsp - end - Application->>Flagsmith: Connect to update stream - Flagsmith->>Application: #nbsp - Flagsmith Administrator->>Flagsmith: Update flag state - Flagsmith->>Flagsmith Administrator: #nbsp - Flagsmith-->>Application: Flag update event - rect rgb(255,245,173) - Application->>Flagsmith: Fetch latest flags - Flagsmith->>Application: #nbsp - end - Application-->Application: Store latest update timestamp -``` - -Your application subscribes to real-time flag updates by opening a long-lived -[server-sent events (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) connection to Flagsmith, -which is specific to its current environment. - -When the environment is updated in some way, either via the Flagsmith dashboard or the -[Admin API](/clients/rest#private-admin-api-endpoints), all clients connected to that environment's real-time stream -will receive a message containing the latest update's timestamp. If your application's latest flags are older than the -received timestamp, it requests the latest flags from Flagsmith. When your application receives the latest flags, you -must propagate the latest flag state throughout your application as necessary. - -## Limitations - -Real-time flag update events only contain a timestamp indicating when any flag in the environment was last updated. -Applications must still call the Flagsmith API to get the actual flags for their current environment or user. - -Only changes made to environments or projects result in flag update events. For example, the following operations will -cause updates to be sent: - -- Manually toggling a flag on or off, or changing its value. -- A [scheduled Change Request](/advanced-use/scheduled-flags) for a feature goes live. -- Creating or updating segment overrides for a feature. -- Changing a segment definition. - -Identity-level operations _will not_ cause updates to be sent: - -- Updating an identity's traits. -- Creating or updating an identity override. - -The following SDK clients support subscribing to real-time flag updates: - -- JavaScript -- Android -- iOS -- Flutter -- Python -- Ruby - -## Implementation details - -The event source URL used by Flagsmith SDKs is: - -``` -https://realtime.flagsmith.com/sse/environments/ENVIRONMENT_ID/stream -``` - -Each real-time flag event message is a JSON object containing a Unix epoch timestamp of the environment's last update: - -```json -{ - "updated_at": 3133690620000 -} -``` - -You can test real-time flag updates by using cURL to connect to the event source URL: - -``` -curl -H 'Accept: text/event-stream' -N -i https://realtime.flagsmith.com/sse/environments/ENVIRONMENT_ID/stream -``` diff --git a/docs/docs/advanced-use/scheduled-flags.md b/docs/docs/advanced-use/scheduled-flags.md deleted file mode 100644 index 1fff9714945d..000000000000 --- a/docs/docs/advanced-use/scheduled-flags.md +++ /dev/null @@ -1,38 +0,0 @@ -# Scheduled Flags - -:::tip - -Scheduled Flags are part of our Scale-Up and Enterprise plans. - -::: - -## Overview - -You can use Scheduled Flags to queue up changes to Flags to be modified automatically in a future point in time. - -You can create a Scheduled Flag change in 1 of two ways: - -- As part of a Change Request. -- If you are not enforcing Change Requests, you can schedule the Flag change when modifying a Flag. - -## Creating a Scheduled Flag change as part of a Change Request - -Once an Environment is configured with Change Requests enabled, attempting to change a flag value will prompt you to -create a new Change Request. - -You will need to provide: - -- The title of the Change Request -- Optionally a description of the reason for the Change Request -- The Date and Time that you want the flag change to take effect - -## Creating a stand-alone Scheduled Flag change - -If the Environment you are working with does not have Change Requests enabled, you can create a Scheduled Flag Change -directly when editing the Flag. - -## Scheduled Flags and Change Requests - -Scheduled Flags pending go live will appear in the Change Request area. - -Once the Schedule of the flag has passed the Scheduled Flag will move to the "Closed" list. diff --git a/docs/docs/advanced-use/transient-traits.md b/docs/docs/advanced-use/transient-traits.md deleted file mode 100644 index 7c13e2378e75..000000000000 --- a/docs/docs/advanced-use/transient-traits.md +++ /dev/null @@ -1,587 +0,0 @@ ---- -title: Transient Traits and Identities ---- - -import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; - -By default, Flagsmith [stores all Traits](/basic-features/managing-identities#identity-and-trait-storage) associated -with an Identity to evaluate flags. This default behavior works for most use cases. However, there are scenarios where -you may want more control over which Traits or Identities are stored. Using the Flagsmith API and SDKs, you can mark -Traits and Identities as transient — meaning they are used for flag evaluation but are not stored. - -## Overview and Use Cases - -Transient Identities and Traits are particularly useful when you need to run flag evaluations without storing specific -data in Flagsmith. Below are common scenarios where transient Traits or Identities come in handy. - -### Skipping Personal Identifiable Information (PII) - -You can mark sensitive Traits, such as `email`, `phoneNumber`, or `locality`, as transient to enable segmentation -without storing the actual data. For example, you may want to target users based on email domains while avoiding the -storage of email addresses. - -Considering you have a Segment condition where `"email"` ends with `"example.com"`. To get flags for this Segment: - -```javascript -flagsmith.updateContext({ - identity: { - identifier: 'test-user-with-transient-email', - traits: { - email: { value: 'alice@example.com', transient: true }, - }, - }, -}); -``` - -After making the SDK call, if you check the Identities tab, you'll see the `test-user-with-transient-email` Identity -without the `email` Trait being stored. - -### Anonymous Identities - -If you choose to provide a blank (`""`) or null identifier, Flagsmith will automatically treat the Identity as -transient. In this case, an auto-generated identifier is returned, based on a hash of the provided Traits. This ensures -consistent flag evaluations without persisting the Identity in the Flagsmith dashboard. - -If no Traits and a null/blank identifier provided, a random UUIDv4 identifier is generated and used for flag evaluation. - -```javascript -flagsmith.init({ - evaluationContext: { - identity: { - identifier: null, - traits: { paymentPreference: 'cash' }, - }, - }, -}); -let identifierToUseLater = flagsmith.getContext().identity.identifier; -``` - -This approach is useful for scenarios such as A/B testing in e-commerce, where you want to include users who haven't -registered or logged in. - -:::info - -Transient identifiers generated by Flagsmith API are consistent across flag evaluations, meaning the same identifier -will be returned for identical sets of Traits. - -To generate truly unique identifiers, include additional unique Trait values. - -::: - -### Ephemeral Contexts - -In some cases, you may need to temporarily override certain Traits for a session or device without overwriting the -stored value. For example, imagine you’ve persisted the `screenOrientation` Trait as `landscape` for a user: - -```javascript -flagsmith.init({ - evaluationContext: { - identity: { - identifier: 'my-user', - traits: { screenOrientation: 'landscape' }, - }, - }, -}); -``` - -If you want to switch the `screenOrientation` to `portrait` for a specific session without overwriting the existing -value, you can mark the Trait as transient: - -```javascript -flagsmith.setTrait('screenOrientation', { value: 'portrait', transient: true }); -``` - -Until you set another Trait value, the `screenOrientation` used for flag evaluations will be `portrait` in this specific -SDK instance. The `landscape` value will remain stored in Flagsmith for other evaluations. - -## Usage - -### Server-side SDKs - - - - -Mark a Trait as transient: - -```bash -curl --request POST 'https://edge.api.flagsmith.com/api/v1/identities/' \ ---header 'X-Environment-Key: ' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "identifier":"identifier_5", - "traits": [ - { - "trait_key": "my_trait_key", - "trait_value": 123.5, - "transient": true - }, - { - "trait_key": "my_other_key", - "trait_value": true - } - ] -}' -``` - -Mark the whole Identity as transient: - -```bash -curl --request POST 'https://edge.api.flagsmith.com/api/v1/identities/' \ ---header 'X-Environment-Key: ' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "identifier":"identifier_5", - "traits": [ - { - "trait_key": "my_trait_key", - "trait_value": 123.5 - }, - { - "trait_key": "my_other_key", - "trait_value": true - } - ], - "transient": true -}' -``` - - - - -:::info - -Transient Traits and Identities are supported starting from Python SDK version 3.8.0. - -::: - -Mark a Trait as transient: - -```python -identity_flags = flagsmith.get_identity_flags( - identifier="my-user", - traits={ - "my_trait_key": {"value":123.5, "transient": true}, - "my_other_key": True - } -) -``` - -Mark the whole Identity as transient: - -```python -identity_flags = flagsmith.get_identity_flags( - identifier="my-user", - transient=True, - traits={ - "my_trait_key": 123.5, - "my_other_key": True - } -) -``` - - - - -:::info - -Transient Traits and Identities are supported starting from Java SDK version 7.4.0. - -::: - -Mark a Trait as transient: - -```java -import com.flagsmith.models.TraitConfig; - -Map traits = new HashMap(); -traits.put("my_trait_key", new TraitConfig(123.5, true)); -traits.put("my_other_key", true); - -Flags flags = flagsmith.getIdentityFlags("my-user", traits); -``` - -Mark the whole Identity as transient: - -```java -import com.flagsmith.models.TraitConfig; - -Map traits = new HashMap(); -traits.put("my_trait_key", 123.5); -traits.put("my_other_key", true); - -Flags flags = flagsmith.getIdentityFlags("my-user", traits, true); -``` - - - - -:::info - -Transient Traits and Identities are supported starting from .NET SDK version 5.4.0. - -::: - -Mark a Trait as transient: - -```csharp -var traitList = new List { new Trait("my-trait-key", 123.5, true), new Trait("my_other_key", true) }; - -var flags = _flagsmithClient.GetIdentityFlags("my-user", traitList).Result; -``` - -Mark the whole Identity as transient: - -```csharp -var traitList = new List { new Trait("my-trait-key", 123.5), new Trait("my_other_key", true) }; - -var flags = _flagsmithClient.GetIdentityFlags("my-user", traitList, true).Result; -``` - - - - -:::info - -Transient Traits and Identities are supported starting from NodeJS SDK version 4.0.0. - -::: - -Mark a Trait as transient: - -```javascript -const flags = await flagsmith.getIdentityFlags('my-user', { - my_trait_key: { value: 123.5, transient: true }, - my_other_key: true, -}); -``` - -Mark the whole Identity as transient: - -```javascript -const flags = await flagsmith.getIdentityFlags('my-user', { my_trait_key: 123.5, my_other_key: true }, true); -``` - - - - -:::info - -Transient Traits and Identities are supported starting from Ruby SDK version 4.2.0. - -::: - -Mark a Trait as transient: - -```ruby -$flags = $flagsmith.get_identity_flags('my-user', my_trait_key: { value: 123.5, transient: true }, my_other_key: true ) -``` - -Mark the whole Identity as transient: - -```ruby -$flags = $flagsmith.get_identity_flags('my-user', true, my_trait_key: 123.5, my_other_key: true) -``` - - - - -:::info - -Transient Traits and Identities are supported starting from PHP SDK version 4.4.0. - -::: - -Mark a Trait as transient: - -```php -$flags = $flagsmith->getIdentityFlags('my-user', (object)['my_trait_key' => (object)['transient' => true, 'value' => 123.5], 'my_other_key' => true]); -``` - -Mark the whole Identity as transient: - -```php -$flags = $flagsmith->getIdentityFlags('my-user', (object)['my_trait_key' => 123.5, 'my_other_key' => true], true); -``` - - - - -:::info - -Transient Traits and Identities are supported starting from Go SDK version 4.0.0. - -::: - -Mark a Trait as transient: - -```go -flags, _ := client.GetFlags( - ctx, - &flagsmith.NewEvaluationContext( - "my-user", - map[string]*interface{}{ - "my_trait_key": NewTraitEvaluationContext(123.5, true), - "my_other_key": true, - }, - ) -) -``` - -Mark the whole Identity as transient: - -```go -flags, _ := client.GetFlags( - ctx, - &flagsmith.NewTransientEvaluationContext( - "my-user", - map[string]*interface{}{ - "my_trait_key": 123.5, - "my_other_key": true, - }, - ) -) -``` - - - - -:::info - -Transient Traits and Identities are supported starting from Rust SDK version 2.0.0. - -::: - -Mark a Trait as transient: - -```rust -use flagsmith::models::SDKTrait; -use flagsmith_flag_engine::types::{FlagsmithValue, FlagsmithValueType}; - -let identifier = "delboy@trotterstraders.co.uk"; - -let traits = vec![ - SDKTrait::new_with_transient( - "my_trait_key".to_string(), - FlagsmithValue { - value: "123.5".to_string(), - value_type: FlagsmithValueType::Float, - }, - true, - ), - SDKTrait::new( - "my_other_key".to_string(), - FlagsmithValue { - value: "true".to_string(), - value_type: FlagsmithValueType::Bool, - }, - ), -]; - -let identity_flags = flagsmith.get_identity_flags(identifier, Some(traits), None).unwrap(); -``` - -Mark the whole Identity as transient: - -```rust -use flagsmith::models::SDKTrait; -use flagsmith_flag_engine::types::{FlagsmithValue, FlagsmithValueType}; - -let identifier = "delboy@trotterstraders.co.uk"; - -let traits = vec![ - SDKTrait::new( - "my_trait_key".to_string(), - FlagsmithValue { - value: "123.5".to_string(), - value_type: FlagsmithValueType::Float, - }, - ), - SDKTrait::new( - "my_other_key".to_string(), - FlagsmithValue { - value: "true".to_string(), - value_type: FlagsmithValueType::Bool, - }, - ), -]; - -let identity_flags = flagsmith.get_identity_flags(identifier, Some(traits), true).unwrap(); -``` - - - - -:::info - -Transient Traits and Identities are supported starting from Elixir SDK version 2.2.0. - -::: - -Mark a Trait as transient: - -```elixir -{:ok, flags} = Flagsmith.Client.get_identity_flags( - client_configuration, - "my-user", - [ - %{trait_key: "my_trait_key", trait_value: 123.5, transient: true}, - %{trait_key: "my_other_key", trait_value: true}, - ] -) -``` - -Mark the whole Identity as transient: - -```elixir -{:ok, flags} = Flagsmith.Client.get_identity_flags( - client_configuration, - "my-user", - [ - %{trait_key: "my_trait_key", trait_value: 123.5}, - %{trait_key: "my_other_key", trait_value: true}, - ], - true -) -``` - - - - -### Client-side SDKs - - - - -:::info - -Transient Traits and Identities are supported starting from JavaScript SDK version 5.0.0. - -::: - -Mark a Trait as transient: - -```javascript -flagsmith.setTrait('my_trait_key', { value: 123.5, transient: true }); -``` - -Mark the whole Identity as transient: - -```javascript -flagsmith.setContext({ - identity: { - identifier: 'my-user', - transient: true, - traits: { my_trait_key: 123.5, my_other_key: true }, - }, -}); -``` - - - - -:::info - -Transient Traits and Identities are supported starting from Android SDK version 2.2.0. - -::: - -Mark a Trait as transient: - -```kotlin -flagsmith.getFeatureFlags(identity = "my-user", traits = listOf(Trait(key = "my_trait_key", value = 123.5, transient: true), Trait(key = "my_other_key", value = true))) { result -> - result.fold( - onSuccess = { onFlagsSuccess }, - onFailure = { onFlagsFailure } - ) -} -``` - -Mark the whole Identity as transient: - -```kotlin -flagsmith.getFeatureFlags(identity = "my-user", traits = listOf(Trait(key = "my_trait_key", value = 123.5), Trait(key = "my_other_key", value = true)), transient = true) { result -> - result.fold( - onSuccess = { onFlagsSuccess }, - onFailure = { onFlagsFailure } - ) -} -``` - - - - -:::info - -Transient Traits and Identities are supported starting from Flutter SDK version 6.0.0. - -::: - -Mark a Trait as transient: - -```dart -var user = Identity(identifier: 'my-user'); -var traits = [ - Trait(key: 'my_trait_key', value: 123.5, transient: true), - Trait(key: 'my_other_key', value: true), -]; -final flags = await fs.getFeatureFlags(user: user, traits: traits); -``` - -Mark the whole Identity as transient: - -```dart -var user = Identity(identifier: 'my-user', transient: true); -var traits = [ - Trait(key: 'my_trait_key', value: 123.5), - Trait(key: 'my_other_key', value: true), -]; -final flags = await fs.getFeatureFlags(user: user, traits: traits); -``` - - - - -:::info - -Transient Traits and Identities are supported starting from iOS SDK version 3.8.0. - -::: - -Mark a Trait as transient: - -```swift -Flagsmith.shared.getFeatureFlags(forIdentity: "my-user", traits: [Trait(key: "my_trait_key", value: 123.5, transient: true), Trait("my_other_key", value: true)]) {(result) in - switch result { - case .success(let flags): - for flag in flags { - let name = flag.feature.name - let value = flag.value?.stringValue - let enabled = flag.enabled - print(name, "= enabled:", enabled, "value:", value ?? "nil") - } - case .failure(let error): - print(error) - } -} -``` - -Mark the whole Identity as transient: - -```swift -Flagsmith.shared.getFeatureFlags(forIdentity: "my-user", transient: true, traits: [Trait(key: "my_trait_key", value: 123.5), Trait("my_other_key", value: true)]) {(result) in - switch result { - case .success(let flags): - for flag in flags { - let name = flag.feature.name - let value = flag.value?.stringValue - let enabled = flag.enabled - print(name, "= enabled:", enabled, "value:", value ?? "nil") - } - case .failure(let error): - print(error) - } -} -``` - - - diff --git a/docs/docs/basic-features/_category_.json b/docs/docs/basic-features/_category_.json deleted file mode 100644 index 04b4a76f4995..000000000000 --- a/docs/docs/basic-features/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Basic Features", - "position": 30 -} diff --git a/docs/docs/basic-features/index.md b/docs/docs/basic-features/index.md deleted file mode 100644 index 54356e991e1e..000000000000 --- a/docs/docs/basic-features/index.md +++ /dev/null @@ -1,84 +0,0 @@ ---- -title: Feature Flags - An Overview -sidebar_position: 1 ---- - -Feature Flags are a development methodology that allow you to ship code and features before they are finished. This -greatly benefits Continuous Integration and Continuous Deployment (CI/CD). The typical workflow for this is as follows. - -1. You are about to start work on a new feature. Lets imaging you are going to implement a sharing button with your - application. -2. Create a new Feature Flag in Flagsmith, calling it "sharing_button". Set it to enabled on your development - environment, and disabled on your production environment. -3. Start working on the feature. Whenever you write code that shows the button within the UI, wrap it in a conditional - statement, testing against the value of the flag "sharing button". Only show the button if the flag is set to True. -4. Because your button only shows when the "sharing_button" flag is set to True, you are safe to commit your code as you - work on the feature. Your code will be live within the production platform, but the functionality is hidden behind - the flag. -5. Once you are happy with your Feature, you can enable the "sharing_button" for other members of your team and with - Beta testers. -6. If everything is working as intended, flip the "sharing_button" flag to True for everyone in your production - environment, and your feature is rolled out. - -If you want to learn more about Feature Flags, -[Flickr wrote the seminal blog post on it in 2009](https://code.flickr.net/2009/12/02/flipping-out/) - -## Flagsmith Model - -Here's a high level overview of the data model for Flagsmith. Fear not - it's not as complex as it looks! - -![Image](/img/flagsmith-model.svg) - -OK let's break this down. - -### Organisations - -Organisations are a way for you and other team members to manage projects and their features. Users can be members of -multiple organisations. - -### Projects - -Projects contain one or more Environments that share a single set of Features across all of the Environments within the -Project. Organisations can have any number of Projects. - -### Environments - -Environments are a way to separate the configuration of your features. For example, your project's Development and -Staging environments might have a feature configured as on while it is turned off in your Production environment. A -project can have any number of environments. - -### Features - -Features are shared between all the Environments within the Project, but their values/states can be modified between -Environments. - -### Identities - -Identities are a particular user registration for one of your Project's environments. Registering identities within the -client application allows you to manage features for individual users. Identity features can be overridden from your -environment defaults. For example, joe@yourwebsite.com would be a different identity in your development environment to -the one in production, and they can have different features enabled for each environment. - -For more info see [Identities](/basic-features/managing-identities). - -### Traits - -You can store any number of Traits against an Identity. Traits are key:value pairs that can store any type of data. Some -examples of traits that you might store against an Identity might be: - -- The number of times the user has logged in. -- If they have accepted the application terms and conditions. -- Their preference for application theme. -- If they have performed certain actions within your application. - -For more info see [Traits](/basic-features/managing-identities.md#identity-traits). - -### Segments - -Segments are a way to define a group of users by traits such as number of times logged in, device, location or any -number of custom defined traits. - -Similarly to individual users, you will be able to override environment defaults for features. For example showing -certain features for a "power user" segment. - -For more info see [Segments](/basic-features/segments.md). diff --git a/docs/docs/basic-features/managing-features.md b/docs/docs/basic-features/managing-features.md deleted file mode 100644 index 90b40f2a8d4a..000000000000 --- a/docs/docs/basic-features/managing-features.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -title: Managing Features -description: Feature Flags allow you to ship code and features before they are finished. ---- - -Flags in Flagsmith are _created and shared at a Project level_, but _overridden at an Environment level_. They can also -be overridden on a [per Identity](/basic-features/managing-identities.md) or [per Segment](/basic-features/segments.md) -basis. - -Flags within Flagsmith are a combination of both: - -- A Boolean value - the `Flag State` - -and then optionally: - -- A String/Integer/Boolean value - the `Flag Value` - -or - -- A selected Multivariate String/Integer/Boolean - the `Flag Value` - -You are free to use either the `Flag State`, or the `Flag Value` or a combination of both `Flag State` and `Flag Value` -within each flag. You don't have to provide or use a `Flag Value`. If you just want a boolean flag, you can just ignore -the `Flag Value` altogether. - -:::important - -The maximum size of each String Value is **_20,000 bytes_**. - -::: - -## Examples of Use - -This allows you to use Flagsmith in the multiple ways: - -- Showing and hiding features in your application. E.g. Controlling a new User Interface element within your application - using the boolean `Flag State` -- Configuring environment variables/keys in your application. E.g. Setting the database URL for your API using the - String `Flag Value`, or setting the Google Analytics API key in your front end. -- Configuring String values used within your application remotely. E.g. You might want to define different colour - schemes for your application banner depending on the Environment. - -If you provide a `Flag Value` to a flag, this will always be included and returned within the -[Flagsmith SDKS](/clients/rest/) and API, regardless of the boolean `Flag State`. - -## Creating a new Feature Flag - -You can create a new feature flag by going to the Flags page within any Environment and hitting the Create Feature -button. - -Flags default to On (true) or Off (false). You can also optionally store and override String and Integer values. The -Flagsmith SDKs allow you to call both `hasFeature` as well as `getValue` on the same flag. These calls will retrieve -both the Boolean value as well as the String/Numerical value if specified. The SDKs generally return False/Null if the -flag is missing or the value is not set, but there are variations between different languages. - -## Multi-Variate Flags - -You can create a Multivariate Flag if you want the `Flag Value` to be one value out of a selection that you define. Each -Environment within a Project can then define and select which value to return based on this list. Multivariate Flags are -useful in 2 core use-cases: - -1. You want to be able to control the `Flag Value` from a pre-selected list. -2. You want to run an A/B test. [Learn more here](/advanced-use/ab-testing.md). - -Multi-Variate Flag values are defined as a "Control" and "Variations". The Control value is always sent as the Flag -Value when you get the Flags for the Environment without passing in a -[User Identity](/basic-features/managing-identities.md). - -:::important - -The Control and Variant weightings **_only_** come into effect if you are getting the Flags for a particular Identity. -If you are just retrieving the flags for an Environment without passing in an Identity, you will **_always_** receive -the Control value. - -::: - -If you are getting the Flags for an Identity, the Flagsmith engine will send the value based on the defined Weightings, -as specified within the Environment. - -
- -In the screenshot above, roughly half our user population will receive the value `normal`, roughly one quarter (25%) -will receive `large` and roughly one quarter (25%) will receive `huge`. Note that you can use 100% as a weighting to -ensure all your users receive the same variant. - -:::important - -Multi Variate _values_ are defined at the Project level, but the _weightings_ are defined at the Environment level. Each -variate String Value will be the same amongst all Environments. Consequently, changing the _value_ of a variation in one -Environment will change that value for all the other Environments within the Project. - -The _weightings_ of each variation, on the other hand, are defined at the Environment level. Changing a Variate -weighting in the `development` environment, for example, will not change the corresponding variation weighting in any -other Environments within the Project. - -::: - -### Multi-Variate Flag Use Cases - -The primary use case for using Multi-Variate flags is to drive [A/B tests](/advanced-use/ab-testing.md). - -### Custom fields - -Optional or required custom fields can be defined when creating or updating features. -[Learn more](/advanced-use/custom-fields.md) diff --git a/docs/docs/basic-features/managing-identities.md b/docs/docs/basic-features/managing-identities.md deleted file mode 100644 index 23ef28d0b6fb..000000000000 --- a/docs/docs/basic-features/managing-identities.md +++ /dev/null @@ -1,137 +0,0 @@ ---- -title: Managing Identities -description: Manage user traits and properties independently of your application. ---- - -Feature flags are great, but they can be a very blunt tool, only allowing you to enable or disable flags across your -entire user base. In order to target users more precisely, and to be able to perform -[staged feature roll-outs](/guides-and-examples/staged-feature-rollouts.md) or -[A/B and multi-variate tests](/advanced-use/ab-testing.md), you need to _Identify your Users_. - -Identities are created within Flagsmith automatically the first time they are identified from your client SDKs. -Generally you'd make a call to identify a user with a unique string/id whenever they log into your app/site. The SDK -will then send an API message to the Flagsmith API, with the relevant Identity information. - -:::tip - -The SDK part of the Flagsmith API is public by design; the Environment Key is designed to be public. When identifying -users, it is important to use an Identity value that is not easy to guess. For example, if you used an incrementing -integer to identify your users, it would be trivial to request Identities by enumerating this integer. This would -effectively give public access to any user traits that are associated with users. - -We strongly recommend using an unguessable, unidentifiable Identity Key, such as a -[GUID](https://en.wikipedia.org/wiki/Universally_unique_identifier), when identifying your users, to prevent -unintentionally leaking Identity trait data. - -::: - -## Identity Overrides - -Once you have uniquely identified a user, you can then override features for that user from your Environment defaults. -For example, you've pushed a feature into production, but the relevant feature flag is still hiding that feature to all -of your users. You can now override that flag for your own user, and test that feature. Once you are happy with -everything, you can roll that feature out to all of your users by enabling the flag itself. - -Identities are specific and individual for each Environment within your project. For example, joe@yourwebsite.com would -be a different identity in your development environment to the one in production, and they can have different features -enabled for each environment. - -## Identity Feature Flags - -By default, Identities receive the default flags for their environment. The main use-case for identities is to be able -to override flags and configs on a per-identity basis. You can do this by navigating to the Users page, finding the User -and modifying their Flags. - -## Identity Traits - -You can also use Flagsmith to store 'Traits' against identities. Traits are key/value pairs that are associated with -individual Identities for a particular Environment. Traits have two purposes outlined below, but the main use case is to -drive [Segments](segments.md). - -:::important - -The maximum size of each individual Trait Value is **_2000 bytes_**. You cannot store more data than that in a single -trait, and the API will return a 500 error if you try to do so. - -::: - -### Using Traits to drive Segments - -Let's say you are working on a mobile app, and you want to control a feature based on the version of the application -that the Identity is using. When you integrate the Flagsmith SDK, you would pass the application version number to the -Flagsmith platform as a trait key/value pair: - -```java -String identifier = "user_512356" -Map traits = new HashMap(); -traits.put("app_version", YourApplication.getVersion()); - -Flags flags = flagsmith.getIdentityFlags(identifier, traits); -``` - -Here we are setting the trait key `app_version` with the value of `YourApplication.getVersion()`.You can now create a -[Segment](segments.md) that is based on the application version and manage features based on the application version. - -Traits are completely free-form. You can store any number of traits, with any relevant information you see fit, in the -platform and then use Segments to control features based on these Trait values. - -## Identity and Trait Storage - -Identities are persisted within the Flagsmith platform, along with any Traits that have been assigned to them. When -Flags are evaluated for an Identity, the full complement of Traits stored within the platform are used, even if they -were not all sent as part of the request. - -This can be useful if, at runtime, your application does not have all the relevant Trait data available for that -particular Identity; any Traits provided will be combined with the Traits stored within Flagsmith before the evaluation -engine runs. - -There are some [exceptions to this rule](/clients#server-side-sdks) with Server Side SDKs running in local evaluation -mode. - -:::info - -Note that, when using our SaaS platform, there might be a short delay from the initial request to write or update traits -for an identity and them being used in subsequent evaluations. - -::: - -### Using Traits as a data-store - -Traits can also be used to store additional data about your users that would be cumbersome to store within your -application. Some possible uses for traits could be: - -- Storing whether the user has accepted a new set of terms and conditions. -- Storing the last viewed page of the application so that you can resume the users place later, across any device. - -Generally if they are lower-value pieces of information about your user, it might be simpler/easier to store them in -Flagsmith rather than in your core application. - -Traits are stored natively as either numbers, strings or booleans. - -## Traits powering Segments - -Traits can be used within your application, but they can also be used to power [Segments](/basic-features/segments.md). - -## Trait Value Data Types - -:::tip - -You can remove a trait by sending `null` as the trait value. - -::: - -Trait values can be stored as one of four different data types: - -- Boolean -- String (max length 2000 bytes) -- Int (32 bit signed) -- Float (typically has a range of around 1E-307 to 1E+308 with a precision of at least 15 digits) - -If you need to store 64 bit integers or very high precision floats we suggest storing them as Strings and then doing the -type conversion within the SDK. - -## Bulk Uploading Identities and Traits - -Identities are lazily created within Flagsmith. There might be instances where you want to push Identity and Trait data -into the platform outside of a user session. We have a -[code example for this](/clients/rest#bulk-uploading-identities-and-traits). diff --git a/docs/docs/basic-features/segments.md b/docs/docs/basic-features/segments.md deleted file mode 100644 index 792f97a6d681..000000000000 --- a/docs/docs/basic-features/segments.md +++ /dev/null @@ -1,315 +0,0 @@ ---- -description: Group your users based on a set of rules, then control Feature Flags and Remote Config for those groups. ---- - -import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; - -# Segments - -A segment is a subset of [identities](/basic-features/managing-identities.md), defined by a set of rules that match -identity [traits](managing-identities.md#identity-traits) or other [context values](#context-values). An identity always -belongs to a single environment and can belong to any number of segments. - -Once you have defined a segment, you can create **segment overrides** for features within an environment. A segment -override allows you to control the state of a feature only for identities that belong to a specific segment. This is -similar to how [identity overrides](managing-identities.md#identity-overrides) let you control the state of features for -an explicit set of identities that is known in advance. - -Because segments are driven by identity traits, your application must identify the user when retrieving flags in order -for segment overrides to be applied. If your user is not identified, no overrides will be applied and all flags will be -returned exactly how they are defined in the current environment. - -Segments and segment overrides can be used to implement many scenarios. For example: - -- Test features in production before they are released by overriding them only for internal users or a QA team -- Deliver features only to "power users" who have logged in a certain number of times, have used specific functionality - within your application, or any combination of factors -- Force a group of users into a specific [A/B test](advanced-use/ab-testing.md) variation by overriding weightings on - [multivariate flags](managing-features.md#multi-variate-flags) -- Override behaviour based on the [application version number](/guides-and-examples/mobile-app-versioning.md), e.g. by - using the SemVer rule operators -- Control features based on the time of day, date, weekday, etc. by passing it as a trait when evaluating flags for an - identity - -## Security and privacy - -The Flagsmith API to set user traits, e.g. the `setTraits` method from the JavaScript SDK, does not require -authentication or credentials. This means that users can change their own traits, which could be a security problem if -you are using segments for authorisation or access control. If you must use segments for access control, make sure to -disable the -["Persist traits when using client-side SDK keys" option](system-administration/security.md#preventing-client-sdks-from-setting-traits) -on every environment that needs it, and use server-side SDKs to set traits instead. You can still use client-side SDKs -to read traits and flags derived from segments in this case. - -Segment names and definitions might include sensitive or proprietary information that you do not want to expose to your -users. Because of this, segments are transparent to applications and are not included in API responses when using -[remote evaluation mode](/clients#remote-evaluation). - -Segment definitions _are_ served to clients running in [local evaluation mode](/clients#local-evaluation), as this -allows them to calculate segments without making requests to the Flagsmith API. This is only an implementation detail -and no segment information is exposed when retrieving flags using any SDK method. - -## Creating project or feature-specific segments - -Segments created from the Segments page of the Flagsmith dashboard can be used to override any feature within a single -project. - -To create a segment override, click on a feature in a specific environment and go to the Segment Overrides tab. - -If you need to create a segment that will only ever be used to override a single feature, you can create a -**feature-specific segment** by clicking on "Create Feature-Specific Segment" when creating a segment override. -Feature-specific segments are otherwise functionally identical to project segments. By default, feature-specific -segments are not shown in the Segments page, unless you enable the "Include Feature-Specific" option. - -Once created, project segments cannot be changed into feature-specific segments or vice versa. - -## Order of rules within segments - -Segment rules are evaluated in order, i.e. from top to bottom when viewed in the Flagsmith dashboard. - -For example, consider the following segment: - -1. 10% percentage split over identifier -2. `is_subscriber = true` - -This segment would first select 10% of _all_ identities, and then choose subscribers from that cohort. Instead, if we -used the opposite order: - -1. `is_subscriber = true` -2. 10% percentage split over identifier - -This would first select all subscriber identities, and then randomly choose 10% of them. - -## Multiple segment overrides for one feature - -If a feature has multiple segment overrides, they are evaluated in order, i.e. from top to bottom when viewed in the -Flagsmith dashboard. The first matching override will be used to determine the state of a feature for a given identity. - -## Flag evaluation precedence - -Identity overrides always take precedence over segment overrides. Simply put, the order of precedence when evaluating a -flag is: - -1. Identity overrides -2. Segment overrides -3. Default value for the current environment - -## Context values - -:::warning - -Currently, context values are only available for remote evaluation. In local evaluation, rules using context values will evaluate to `false`. - -::: - -In addition to identity [traits](managing-identities.md#identity-traits), you can use the following context values as -Segment rule properties: - -- **Identifier**: a unique identifier for an identity, used for segment and multivariate feature flag targeting, and displayed in the Flagsmith UI. Useful for bucketing via the - [`% Split`](?operators=percent#operator-details) operator, or managing a list of users via the - [`In`](?operators=in#operator-details) operator. -- **Identity Key**: a key used when selecting a value for a multivariate feature, or for % split segmentation. Set to an internal identifier or a composite value based on the environment key and identifier, depending on Flagsmith implementation. -- **Environment Name**: an environment's human-readable name. Useful for restricting Segments to certain environments. - -Context values can be used to control your targeting more precisely: - -### Gradual rollout across tenants - -Suppose your application supports users who can belong to multiple organisations. Now, you want to roll out a new -feature to 20% of your organisations, ensuring that users only have access to the feature when they are part of an organisation included in that 20%. If they switch to an organisation outside this group, they will no longer have access to the feature. - -Here's how you might define a segment to achieve this: - -| Rule Order | Property | Operator | Value | -| ---------- | ------------------------- | -------- | ----- | -| 1 | `organisation_name` Trait | % Split | 20 | - -This setup, along with a segment override, instructs the evaluation engine to enable the feature if the user's current organisation belongs to consistent 20% of all organisations. - -:::info Use transient traits for multi-tenancy - -To avoid persisting the `organisation_name` trait on the user identity, mark it as -[transient](../advanced-use/transient-traits.md). - -::: - -### Restrict evaluation to select environments - -Segments and segment overrides are defined at the project level. This means they apply across all environments, so you -may want to restrict your targeting to specific environments — for example, to avoid exposing features in production -while still running complex evaluations in staging or development. - -Here’s how you could define such a segment: - -| Rule Order | Property | Operator | Value | -| ---------- | ---------------- | ------------------- | ------------ | -| 1 | Environment Name | Does Not Match (!=) | `production` | -| 2 | Identifier | % Split | 20 | - -This setup will enable the feature for 20% of users, but only in non-production environments. - -## Trait data types - -Each individual trait value is always stored as one of the following data types: - -- String -- Boolean -- Integer -- Float - -Values in segment rules, on the other hand, are always stored as strings. When segment rules are evaluated, rule values -will be coerced to be the same type as the trait value. If the rule value cannot be coerced, that rule will evaluate as -false. This provides some flexibility if you ever need to change the data type of a trait, e.g. from boolean to string, -while maintaining backwards and forwards compatibility in your application. - -For example, consider the following code using the JavaScript SDK: - -```javascript -flagsmith.identify('example_user_1234'); -flagsmith.setTrait('accepted_cookies', true); -``` - -The value of the `accepted_cookies` trait will be stored as a boolean for this identity. If you define a segment rule -like `accepted_cookies = true`, the rule value `true` is stored as a string. Because the `accepted_cookies` was stored -as a boolean for this identity, the segment engine will coerce the rule's string value into a boolean, and things will -work as expected. - -Suppose later on you needed to store a third possible state for the trait `accepted_cookies`, for example if users can -partially accept cookies. Your application can start storing this trait as a string without needing to modify your -existing segment: - -```javascript -flagsmith.setTrait('accepted_cookies', 'partial'); -``` - -This would continue to work as expected for identities that already have this trait set as a string value. Always -storing the trait as a string would also work, for example: - -```javascript -flagsmith.setTrait('accepted_cookies', 'true'); -``` - -The following string trait values will evaluate to `true`: - -- `"True"` -- `"true"` -- `"1"` - -## Rule operators reference - -All rule operators are case-sensitive. - -| Name | Description | -| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Exactly Matches (=)` | Trait value is equal to the rule value | -| `Does not match (!=)` | Trait value is not equal to the rule value | -| `% Split` | Identity is in the percentage bucket. [Learn more](?operators=percent#operator-details) | -| `>` | Trait value is greater than the rule value | -| `>=` | Trait value is greater than or equal to the rule value | -| `<` | Trait value is less than the rule value | -| `<=` | Trait value is less than or equal to the rule value | -| `In` | Trait value is equal to any element in a comma-separated list (case-sensitive). [Learn more](?operators=in#operator-details) | -| `Contains` | Rule value is a substring of the trait value | -| `Does not contain` | Rule value is not a substring of the trait value | -| `Matches regex` | Trait value matches the given regular expression | -| `Is set` | Trait value is set for given identity and trait key | -| `Is not set` | Trait value is not set for given identity and trait key | -| `SemVer` | Trait value is compared against the rule value according to [Semantic Versioning](https://semver.org/). [Learn more](?operators=semver#operator-details) | - -### Operator details - - - - -The `In` operator lets you match a trait value against a comma-separated list of values. For example, the segment rule -value might read `21,682,8345`. This would match against a trait value of `682` but not against a trait value of `683` -or `834`. - -The `In` operator can be useful to build segments that represent a specific set of tenants in your application. For -example, you could create a segment with the following rule: `tenant_id In tenant_1,tenant_2,tenant_3` - - - - -[SemVer](https://semver.org/) operators compare semantic version values. Consider the following segment rule: - -`version` `SemVer >=` `4.2.52` - -This segment would include all users that have a `version` trait set to `4.2.52` or greater. For example, any of the -following `version` values would match: - -- `4.2.53` -- `4.10.0` -- `5.0.0` - -Versions are compared as defined by the [Semantic Versioning specification](https://semver.org/#spec-item-11). - - - - -You can use Percentage Split to drive [A/B tests](/advanced-use/ab-testing) and -[staged feature rollouts](/guides-and-examples/staged-feature-rollouts#creating-staged-rollouts). - -Percentage Split deterministically assigns an Identity to a bucket based on a provided [context value](#context-values) -or a trait. This means that Segment overrides that use Percentage Split will always result in the same feature state and value for -a given identity. - -If you create a Segment with a single Percentage Split rule of 10% over identifier, Identities who are members of that -split will be guaranteed to also be in that split if it is changed to a value higher than 10%. - -If the Percentage Split is reduced in value, some Identities will be removed from that Percentage Split to maintain the -balance. The algorithm is fairly simple and good to understand - it is -[described here](/guides-and-examples/staged-feature-rollouts#how-does-it-work). - - - - -This operator performs a [modulo operation](https://en.wikipedia.org/wiki/Modulo_operation), which returns the remainder -of dividing a numeric trait value by a given divisor. The operator accepts rule value in the format `divisor|remainder`. -For example: - -`user_id` `%` `2|0` - -This segment will include all identities having an `user_id` trait that is divisible by 2, i.e. even numbers. This is -equivalent to the following expression in many programming languages: - -`user_id % 2 == 0` - - - - -### Minimum SDK versions for local evaluation mode - -When running in local evaluation mode, SDK clients evaluate segment rules locally, which means they must be updated to -support the latest operators. - -If an SDK client tries to evaluate a segment rule that has an unrecognised operator, that rule will silently evaluate to -`false`. The table below lists the minimum required SDK version required by each operator: - -| | Modulo | In | -| ------- | ------ | ----- | -| Python | 2.3.0 | 3.3.0 | -| Java | 5.1.0 | 7.1.0 | -| .NET | 4.2.0 | 5.0.0 | -| Node.js | 2.4.0 | 2.5.0 | -| Ruby | 3.1.0 | 3.2.0 | -| PHP | 3.1.0 | 4.1.0 | -| Go | 2.2.0 | 3.1.0 | -| Rust | 0.2.0 | 1.3.0 | -| Elixir | 1.1.0 | 2.0.0 | - -## Limits - -These are the default limits for segments and rules: - -- 100 segments per project -- 100 segment overrides per environment -- 100 rules per segment override -- 1000 bytes per segment rule value - -See the [documentation on System Limits](system-administration/system-limits.md) for more details. - -## Custom fields - -Optional or required custom fields can be defined when creating or updating segments. -[Learn more](/advanced-use/custom-fields.md) diff --git a/docs/docs/best-practices/_category_.json b/docs/docs/best-practices/_category_.json new file mode 100644 index 000000000000..ece803d6475e --- /dev/null +++ b/docs/docs/best-practices/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Best Practices", + "position": 6, + "collapsed": true +} diff --git a/docs/docs/best-practices/defensive-coding.md b/docs/docs/best-practices/defensive-coding.md new file mode 100644 index 000000000000..195eedb20e5c --- /dev/null +++ b/docs/docs/best-practices/defensive-coding.md @@ -0,0 +1,70 @@ +--- +title: Defensive Coding and Designing for Failure +sidebar_label: Defensive Coding +sidebar_position: 4 +--- + +Introducing feature flags and remote config to your applications can provide a wealth of benefits, but there are also a few drawbacks. Fortunately, the majority of these can be avoided through [defensive coding](#defensive-coding) and sensible approaches to default flags. + +In addition to the approaches you can take integrating our SDKs, there are a number of [design for failure](#designing-for-failure) concepts that are built into the platform architecture to ensure that Flagsmith provides a reliable, dependable solution. + +## Defensive Coding + +### Don't expect a 200 response from the Flagsmith API + +First up - we care deeply about our [SaaS uptime and stability](http://status.flagsmith.com/). Whilst no one has 100% uptime, in our experience there are numerous situations where your application is unable to get a response from our API: + +- They are using a mobile device, open your app, and step into a lift. +- They are using a web application in a hotel that has the craziest DNS setup you have ever seen. +- As above but for TLS certificates. +- They are running a DNS blocker that has over-zealous blacklists. + +The list goes on and on, but the bottom line is that whilst our API being down is extremely unlikely, you cannot rely on 200's from our API in 100% of cases. + +So with that in mind, here are some rules you can follow to avoid any issues stemming from the above. + +### Don't block your application waiting on our response + +The solution here really depends on which of our SDKs you are using. By default our Client SDKs will not block your main application thread, and are designed to work around an asynchronous callback model. + +Where our Server Side SDKs are being used, it really depends on if you are using them in [local or remote evaluation mode](/integrating-with-flagsmith/integration-overview). When running in local evaluation mode, once the SDKs have received a response from the API with the Environment related data, they will keep that data in memory. In the event of the SDKs then not receiving an update, they will continue to function. + +In the event that the SDKs aren't able to contact the API at all, they will time out and resort to [Default flags](#progressively-enhance-your-application-with-default-flags). When running in remote evaluation mode, you will need to decide what the best approach is based on your particular application. Again, [Default flags](#progressively-enhance-your-application-with-default-flags) can help here. + +### Progressively enhance your application with default flags + +The most effective way of dealing with these issues is to provide a sane set of default values for _all of your flags_. The Flagsmith SDKs all have provision for specifying default values for both flag boolean and flag text values. We strongly recommend setting defaults for all of your flags as a matter of routine. + +Your application should operate in a default, safe mode and its behaviour should only be modified or enhanced with flags on receiving an API response. + +### Cache flags where possible + +Our Javascript SDK has the capability of caching the last received flags in the localStorage of the user's browser. When the browser starts a new session, the last cached flags will be used while waiting for a response from the API for a fresh set of flags. This pattern helps if the browser never receives a response from the API. + +## Designing for Failure + +### The Edge API + +When our SDKs request their flags, they will make requests to our [Edge API](/performance/edge-api). This infrastructure is serverless both at the compute and data-store, as well as being replicated across eight AWS regions. We also provide latency based routing and regional fault tolerance. + +What this means is that your application will be served by the nearest region to the request. In addition to this, in the event of the failure of an entire AWS region, requests will automatically be routed to the next nearest region. + +### Caching, Performant SDKs + +Our client-side SDKs will always remember their last known flags and use these in the event that they cannot reach our API; for example if they are on a mobile device set to flight mode. + +In addition to this, by default our client-sde SDKs will only make a network call when they are initialised. Subsequent requests for flag evaluation will happen locally in memory in fractions of a millisecond. + +### Server side SDKs and local evaluation mode + +If you need sub-millisecond latency for end-to-end flag evaluation, for example in the event that you are running a multi-variate test on a landing page of your website, you can employ one of our Server Side SDKs running in [local evaluation mode](/integrating-with-flagsmith/integration-overview) mode. This will provide sub-millisecond latency of the entire flag evaluation rules engine, running locally within your server infrastructure, allowing you to run multivariate tests with zero latency impact. + +### No proxy-server required + +We do not require you to run any infrastructure whatsoever to run our platform. There are no relays, proxies, caches or CDNs in between your client request and our API. + +We do have an [Edge Proxy](/performance/edge-proxy) if required, but it is entirely optional. + +### Bring your own CDN or DNS if you wish + +Because we don't do any caching, you are free to add your own reverse proxy into the architecture if you wish. Customers often do this to provide an additional layer of security (for example, only allowing authenticated clients to get their flags). They also do this to ensure that requests from their application don't make requests to a third party domain, for example by setting up their own DNS namespace for our API. diff --git a/docs/docs/best-practices/efficient-api-usage.md b/docs/docs/best-practices/efficient-api-usage.md new file mode 100644 index 000000000000..18aecf8594ca --- /dev/null +++ b/docs/docs/best-practices/efficient-api-usage.md @@ -0,0 +1,76 @@ +--- +title: Efficient API Usage +sidebar_label: Efficient API Usage +sidebar_position: 5 +--- + +It is good engineering practise to reduce the frequency of API calls made from your applications to Flagsmith. There are a number of reasons for this: + +- Reducing network activity means your applications use less client resources like mobile device battery, CPU and network. +- You don't need to reflect state updates in your application as often. +- It saves you money! Whether you are self hosting or using our SaaS API, reducing API call volume costs you less. + +## Client Side + +### Getting Flags Once Per Session + +The most common, most efficient workflow we have found with people using Flagsmith on the client side (browsers, mobile apps etc) is the following: + +1. The user opens the app for the first time, as an anonymous user. +2. The application loads, using default flag values as defined in code. +3. The application makes a request for the flags for the environment (_not_ the Identity as the user is still unknown at this stage). +4. The user logs into the application. A request is then made for the identity flags (along with any traits for that identity). +5. This data is then cached locally on the device and used for the duration of the user's session. +6. When the user reopens the app (for example, the following day) the cached values in the previous step are used. The application then re-requests the identity flags (in case any flags have changed in the meantime) and caches the data. + +### Setting Traits Efficiently + +Every time a trait as set via the SDK, they will make a request to the Flagsmith API with the trait data and receive an updated set of flags. + +In order to reduce these calls, we recommend setting the full complement of traits in a single SDK call. There's more info around achieving this in our [Javascript FAQ](/integrating-with-flagsmith/sdks/client-side-sdks/javascript). + +### Real Time Flag Updates + +In our experience, most applications do not benefit a great deal from real time flag updates. In addition, and especially with client-side flags, thought needs to be given to ensuring features/UI widgets don't appear/disappear in real time due to flag changes. + +That being said, there are use-cases for real time flags. Using our [real-time flags service](/performance/real-time-flags) negates the need to poll the API from the client SDK, which can significantly reduce API usage. + +## Server Side + +### Local Evaluation Mode + +The most efficient way of evaluating feature flags on the server is using [local evaluation mode](/integrating-with-flagsmith/integration-overview). There are [some caveats](/integrating-with-flagsmith/integration-overview), so please be aware of them! + +### CDN Usage + +There are 3 main API calls the Flagsmith SDK can make: + +1. Get the [Environment Document](/integrating-with-flagsmith/integration-overview) for + [Local Evaluation mode](/integrating-with-flagsmith/integration-overview). +2. Get the Flags for an Environment. +3. Get the Flags for an Identity. + +Of these 3, the first two are candidates for caching. If your project can tolerate a longer period of time between someone modifying a flag in Flagsmith and that flag change being reflected within the SDK, you can place a cache between Flagsmith and your server side SDKs. The easiest way to do this is with a CDN, specifying the TTL to whatever you can tolerate, and overriding the Flagsmith API URL within the SDK to point to your CDN. + +Note that you almost certainly _don't_ want to cache the Get the Flags for an Identity. + +### Edge Proxy + +Using the [Edge Proxy](/performance/edge-proxy) can significantly reduce API usage. A single instance of the Edge Proxy makes one API request by default every 10 seconds to the Flagsmith API. With that request data it can then serve potentially thousands of requests per second. + +Consideration needs to be given to the caveats of running the Edge Proxy, but its deployment can have a dramatic effect on reducing API call volume. + +## Micro-service Architectures + +Choosing the right project structure for your micro-services can have a significant impact on API usage. When you have multiple services, should you create a single Flagsmith project for all of them, or one project per service? + +The answer depends on how coupled your services are. + +If your micro-services are tightly coupled, they are a good candidate for sharing a single Flagsmith project. This means your services only need to fetch and cache one environment document, which is more efficient. A common example is when multiple services share a database and you need to coordinate a schema change. Using a single project with shared flags makes this much easier and safer than coordinating deployments. + +On the other hand, if your services are loosely coupled with well-defined interface boundaries, it's generally better to have one Flagsmith project per micro-service. If a service's API is stable and agreed upon, the consuming services don't need to know about its internal feature flags. + +When deciding, also consider: + +- **Permissions:** With a single project, all team members can see and manage all flags. If you need to restrict access for different teams, separate projects might be a better choice. +- **Shared Segments:** If your services share a similar user context, using a single project allows them to share segment definitions, which can be very helpful. diff --git a/docs/docs/best-practices/flag-lifecycle.md b/docs/docs/best-practices/flag-lifecycle.md new file mode 100644 index 000000000000..c14a3cf4d63c --- /dev/null +++ b/docs/docs/best-practices/flag-lifecycle.md @@ -0,0 +1,79 @@ +--- +title: Feature Flags Lifecycles +sidebar_label: Feature Flags Lifecycles +sidebar_position: 1 +--- + +Feature Flags generally have two lifecycles: + +1. Short-Lived Flags +2. Long-Lived Flags + +Lets go over each type in detail. + +## Short-Lived Flags + +Short-lived flags are designed to be removed from your code and from Flagsmith at some point in the future. Their typical lifecycle is normally something like: + +1. Create flag in Flagsmith. +2. Add the flag to your application's code. +3. Toggle the flag and/or apply Segment overrides to control your application's behaviour. +4. Once you are finished with the flag, remove it from your codebase. +5. Deploy your application, so that there is no reference to the flag. +6. Remove the flag from Flagsmith. + +Short lived flags are typically used for the following use-scenarios: + +### Feature Roll-Outs + +The most common use of flags in general is to decouple the deployment of a feature from its release. When using a flag to achieve this, you will generally remove it from your code and from Flagsmith once you are happy with the feature and it is rolled out to all your users. + +Once that's done, there is no reason to have the flag existing either in Flagsmith or your code, hence it is considered good practise to remove it from both. + +### Experimentation + +You can use Multi-variate flags to drive [A/B and multivariate tests](/experimentation-ab-testing). Once your experiment is complete, there is typically no need for the flag to remain, and hence it can be removed. + +## Long-Lived Flags + +Conversely, sometimes you will create flags that are long-lived - quite possibly for the lifetime of the application. Here are a few use cases where this approach can be used. + +### Kill Switches + +There are often times where you need to be remotely remove a feature, area of your application or sometimes the application as a whole (in the event of a large deployment, for example). In these instances, flags can be used as 'kill switches' to remove a feature altogether, or maybe to prevent users from using the application in the event of downtime. + +Kill switches are generally long-lived; they often exist in the event of an unexpected event or error, and so they need to be long-lived in case they need to be put to use. + +### Feature Management Flags + +You can make use of [Segments](/flagsmith-concepts/segments) and Flags to control how different features are enabled or disabled depending on the user. For example, you can send a Trait `plan` with the relevant user value (e.g. `scale-up`) to Flagsmith, then create a Segment that defines all users on the `scale-up` plan. You can then show or hide features based on this Segment and plan. + +When employing feature flags in this manner, generally you would never remove this Flag or Segment, as you are using them to drive platform features for the lifetime of the application. + +## Why use a dedicated feature flag service? + +Using a dedicated feature flag service like Flagsmith offers numerous advantages over building an in-house solution. A dedicated service provides a robust, reliable, and scalable platform for managing feature flags, allowing you to focus on your core product development. + +With a service like Flagsmith, you get: +- A user-friendly interface for managing flags, environments, and user segments. +- SDKs for various languages and frameworks, simplifying integration. +- Advanced features like staged rollouts, A/B testing, and remote configuration. +- A secure and scalable infrastructure, ensuring high availability and low latency. + +## How does Flagsmith help? + +Flagsmith is designed to help you implement best practices for feature flag management. It provides a comprehensive set of tools to manage the entire lifecycle of your feature flags. + +With Flagsmith, you can: + +- **Organise flags:** Use projects and environments to manage flags for different applications and deployment stages. +- **Control flag access:** Use role-based access control to manage who can view and modify flags. +- **Automate flag changes:** Schedule flag changes to coincide with releases or other events. +- **Track flag usage:** Use analytics to understand how your flags are being used and to identify any potential issues. +- **Integrate with your existing tools:** Flagsmith integrates with a wide range of tools, including CI/CD pipelines, monitoring and alerting systems, and more. + +## Further Reading + +- For a deeper dive into the different types of feature flag lifecycles, check out the [Feature Flags Lifecycles guide](/best-practices/flag-lifecycle). +- Learn more about [A/B and multivariate testing](/experimentation-ab-testing) to see how experimentation can be managed with Flagsmith. +- Explore how [Segments](/flagsmith-concepts/segments) can help you target features to specific groups of users. diff --git a/docs/docs/best-practices/integration-approaches.md b/docs/docs/best-practices/integration-approaches.md new file mode 100644 index 000000000000..84f89035159c --- /dev/null +++ b/docs/docs/best-practices/integration-approaches.md @@ -0,0 +1,69 @@ +--- +title: Integration Approaches +sidebar_label: Integration Approaches +sidebar_position: 6 +--- + +## Client Side SDK Flag endpoints are public + +The API endpoints that our SDKs make calls to are all public. Your Environment API key should also be considered public. Think of it in the same way you would a Google Analytics key. The key is sent to browsers in plain HTML or Javascript and as a result should not be considered 'secret'. + +Given this fact, it is important to ensure that attackers cannot enumerate or guess Identity keys. If an attacker is able to do this, they can easily overwrite trait values for Identities that are not related to their user. The simplest way to achieve this is to use a computer generated GUID or hash as the Identity key. You might already be doing this (for example if your datastore associates a GUID with a User record). + +If, for example, your database uses an auto-incrementing integer as the user record key, we strongly recommend you either store a GUID alongside that record, or compute a 2-way hash of the user and use that as the Identity key. + +Note that this only relates to _Client Side Keys_. _Server Side Keys_, on the other hand, should be considered secret and stored appropriately. + +You can also prevent client-side SDKS from [setting Traits](/administration-and-security/governance-and-compliance/security#preventing-client-sdks-from-setting-traits). + +### Segment and Targeting rules are not leaked to the client + +If flags are evaluated within the client-side SDKs (Web Browser, Mobile App), the entire set of rules for targeting users based on Segments etc need to be sent to the client. Given these endpoints are public by default, we think this is a leak of potentially sensitive information. We think the best place for your flags to be evaluated is on our server or your server. Not on the client. + +### You can get your flags with a single HTTP GET + +You don't need to run a set of complicated rule evaluations to get your flags. Just hit our endpoint and you get your flags. You won't receive any information on Segments or rollout rules, and this is by design. If you want to run your own HTTP client within your application it's just an HTTP GET and you're good. + +### Build Time Flag Retrieval + +:::tip + +We have a [Flagsmith CLI](https://github.com/Flagsmith/flagsmith-cli) which can be helpful here! + +::: + +A more advanced technique is to grab the Flag defaults from the Flagsmith API at build time and include them on your application build. The steps for this might look something like this: + +1. Push your code to your git repository. +2. An automated build pipeline is triggered. +3. One stage of the pipeline is to grab the current default flag states from the `/flags` endpoint and store the JSON response within your application build. +4. Upon startup of your application, read the JSON file is embedded within your application first to get sane default flags and config. +5. Asynchronously call the Flagsmith API to get the most recent Flag and Config values. + +## Caching Flags Locally + +This approach depends on whether your application has an ability to persist data to the host OS during runtime. Locally caching flags within your application environment ensures that you can subsequently start your application without having to block for a call to the Flagsmith API. A common workflow would then be: + +1. Build your application with sane defaults. +2. Start your app, using the sane defaults, and asynchronously call the Flagsmith API to retrieve up-to-date Flags. +3. Once the up-to-date Flags are retrieved, store them locally. +4. On subsequent app launches, check local storage to see if any flags are available. If they are, load them immediately. +5. Asynchronously call the Flagsmith API to retrieve the up-to-date Flags. + +The official [Javascript Client](/integrating-with-flagsmith/sdks/client-side-sdks/javascript) offers optional caching built in to the SDK. + +## Caching Flags on a Server (Flagsmith Client) + +:::tip + +Note that you can also [evaluate flags locally](/integrating-with-flagsmith/integration-overview) in our Server Side SDKs. + +::: + +When running the Flagsmith SDK within a Server environment, it is difficult for the SDK to ascertain what sort of caching infrastructure is available to it. For this reason, caching flags in a Server Environment needs to be integrated by hand. However, it's pretty simple! + +1. When your server (flagsmith client) starts up, get the Flags from the Flagsmith API. Flagsmith server will now store the Flags in memory within the server runtime. +2. If you have caching infrastructure available (for example, memcache, redis etc), you can then store the flags for that environment within your caching infrastructure. +3. You can set up a [Web Hook](/third-party-integrations/webhook) within Flagsmith that sends flag change events to your server infrastructure. +4. Write an API endpoint within your infrastructure that receives flag change events and stores them in your local cache. +5. You can now rely on your local cache to get up to date flags. diff --git a/docs/docs/best-practices/mobile-app-versioning.md b/docs/docs/best-practices/mobile-app-versioning.md new file mode 100644 index 000000000000..38948feb0441 --- /dev/null +++ b/docs/docs/best-practices/mobile-app-versioning.md @@ -0,0 +1,81 @@ +--- +title: Mobile App Versioning +sidebar_label: Mobile App Versioning +sidebar_position: 7 +--- + +## The Problem + +Feature Flags really come into their own when managing the features of a mobile application. If you ship a bug in your mobile app, there is a significant time delay in getting a fix onto your user's device: + +1. You have to wait for App/Play Store approval (although this time period has got much better in recent years) +2. Once your new app version is live, you then have to wait for your users to upgrade their application, which could take weeks or even months. + +Combined, these two problems can cause real headaches if you ship a bug. Feature flags to the rescue... + +## The Solution + +We're going to create a Segment of affected users, and override (in this case, disable) the affected feature from _just the affected users_ of our application altogether. Here's how we go about it. + +### 1. Put Features behind Flags + +It sounds obvious, but if you don't wrap features in feature flags, you lose the ability to control them remotely. Make it a part of your routine to wrap new features/code in flags so you can start managing them remotely. + +### 2. Start telling Flagsmith about the Device and Application Version + +Using [Identities and Traits](/flagsmith-concepts/identities), make sure you are transmitting data about your device type and version to Flagsmith. We recommend using the following Traits: + +- Platform (iOS or Android) +- Platform Version (e.g. Android 11, iOS 14) +- Your Application Version (this would be the version number you ship your app as - generally the one that shows up in the App/Play Store) + +### 3. Track down your bug + +When you inevitably do ship a bug (don't worry; we've all been there!), and the bug reports start rolling in, try and rapidly isolate exactly what subset of devices are affected: + +- Is it just iOS devices running the just-shipped version? +- Or have all Android devices broken for some reason? +- Did you actually ship the bug 2 versions back but have only just realised now? + +This is generally the hardest part of the process. Work to isolate and define the smallest subset of your user-base that is affected. + +### 4. Segment your Users based on the Bug + +:::tip + +We can make use of [Semver Aware Operators](/flagsmith-concepts/segments/segment-rule-operators) to drive these Segment rules. + +::: + +From your work in #3, create a [Segment](/flagsmith-concepts/segments) in Flagsmith that captures the defined set of users from #3. Let's say we just shipped version `5.4.1`, but we have figured out that the bug actually showed up in version `5.4.0`. Also, this issue is only affecting iOS devices; Android users don't have the problem. So our Segment would contain 2 rules and read something like: + +- Trait `platform` _equals_ `iOS` +- Trait `version` _semver_ {'>='} `5.4.0` **AND** _semver_ {'<='} `5.4.1` + +### 5. Override your Feature with your Segment + +Locate the feature that is causing the problem. Get to the Overrides tab, add the Segment you defined in #4, and set that Feature Override to **_disabled_**. + +And breathe... + +## What just happened? + +We've done the hard work and isolated which precise subset of users are affected by this issue. We want the feature to continue to show and work for all our other users (in this case Android users and iOS users on versions older than `5.4.0`), but we want to disable it for the affected users. + +So we created a Segment that precisely identified the affected users, and then used that Segment to override the feature, **_but just for those users_**. + +## After the Fix: Managing the Rollout + +First things first (and yes, it’s obvious, but let’s say it anyway): ship your fix! Release version 5.4.2 with the bug sorted. As your users update their apps, their `version` trait will update to 5.4.2, meaning they’ll automatically drop out of the affected Segment. Flagsmith will then start sending the Enabled flag for the feature, so your users get access to the newly-fixed functionality. + +It’s a good idea to keep this Segment and the override in place for a while. Not everyone updates their app straight away—some people might not update for ages. That’s fine though; with the Segment in place, those users will never know you shipped a clanger. + +## What’s next? + +Looking to go further? Here are some handy resources and related guides: + +- [Efficient API Usage](/best-practices/efficient-api-usage): Tips for reducing API calls and making your mobile app more efficient. +- [Defensive Coding & Designing for Failure](/best-practices/defensive-coding): Learn how to make your app resilient when things go wrong. +- [Integration Approaches](/best-practices/integration-approaches): Explore different ways to integrate Flagsmith into your mobile projects. +- [When to use Feature Flags](/best-practices/when-to-use-flags): Discover more scenarios where feature flags can save your bacon. +- [Segments](/flagsmith-concepts/segments): Deep dive into how Segments work and how to get the most out of them. diff --git a/docs/docs/best-practices/testing-with-flags.md b/docs/docs/best-practices/testing-with-flags.md new file mode 100644 index 000000000000..6136a63139f5 --- /dev/null +++ b/docs/docs/best-practices/testing-with-flags.md @@ -0,0 +1,36 @@ +--- +title: Testing with Feature Flags +sidebar_label: Testing with Feature Flags +sidebar_position: 3 +--- + +Feature flags are an invaluable tool for testing new functionality in a controlled and safe manner. They allow you to test in production-like environments, release features to specific user groups for beta testing, and run experiments to gather data before a full roll-out. + +## Testing in Production + +One of the most powerful uses of feature flags is the ability to test new code in your production environment before it's released to all users. + +You can deploy a new feature behind a flag that is turned off by default. The code is live in production, but invisible to your users. This allows you, your team, and any other stakeholders to access and test the feature directly in the production environment. This is often called "testing in production". + +This approach gives you the highest level of confidence that the feature will behave as expected when you release it, as it's being tested against live production data and infrastructure. + +## Beta Programmes and User Segments + +Instead of releasing a feature to everyone at once, you can use feature flags to run beta programmes with a select group of users. + +By creating a [Segment](/flagsmith-concepts/segments) of users (e.g., "beta_testers"), you can enable a new feature exclusively for them. This allows you to gather feedback from real users in a controlled way. These users can be internal employees, a dedicated group of QA testers, or a set of customers who have opted into early access. + +This is particularly useful for mobile applications. If a bug is found, you can use a feature flag to remotely disable the feature for the affected app versions, giving you time to release a fix without impacting your entire user base. + +## A/B and Multivariate Testing + +Feature flags are the foundation for running experiments like A/B and multivariate tests. You can present different versions of a feature to different user segments simultaneously and measure which one performs better against your key metrics. + +For example, you could test: +- Different button colours or text to see which drives more clicks. +- Entirely different user flows for a new feature. +- The performance impact of a new algorithm. + +Flagsmith allows you to define different variations for a feature and control the percentage of users that see each one. This, combined with an analytics tool, allows you to make data-driven decisions about your product. + +You can learn more in our guide to [A/B Testing](/experimentation-ab-testing). \ No newline at end of file diff --git a/docs/docs/best-practices/when-to-use-flags.md b/docs/docs/best-practices/when-to-use-flags.md new file mode 100644 index 000000000000..79689ccd7752 --- /dev/null +++ b/docs/docs/best-practices/when-to-use-flags.md @@ -0,0 +1,51 @@ +--- +title: When to use Feature Flags +sidebar_label: When to use Feature Flags +sidebar_position: 2 +--- + +Feature Flags are a powerful tool for modern software development. They allow teams to modify system behaviour without changing code and deploying new versions. But when exactly should you use them? This guide covers the common use cases for feature flags. + +## Decouple Deployment from Release + +The most common reason to use feature flags is to separate the act of deploying code to production from the act of releasing a feature to users. + +### Feature Roll-Outs +You can deploy new features to production behind a feature flag that is turned off. The code is in production, but no users can see it. This allows you to test the feature in a production environment. Once you are confident, you can release the feature by turning the flag on. This gives you full control over the release process, independent of code deployment cycles. + +### Staged Rollouts +Instead of releasing a feature to all users at once, you can do it gradually. With staged rollouts, you can release a feature to a small percentage of your users first (e.g., 1%, 10%, 50%) and monitor its performance and stability. This minimises the risk and impact of any potential issues. If something goes wrong, you can quickly turn the flag off. + +## Experimentation + +Feature flags are essential for running experiments like A/B tests or multivariate tests. + +### A/B Testing +You can present two or more versions of a feature to different segments of users simultaneously to see which one performs better. For example, you can test different button colours, text, or user flows. + +### Multivariate Testing +This is similar to A/B testing but allows you to test multiple variables at once. You can define different variations for a feature and let Flagsmith distribute users among them based on defined weightings. + +## Operational Control + +Feature flags can also be used as operational levers to manage your application in production. + +### Kill Switches +A kill switch is a long-lived flag that allows you to quickly disable a feature or a part of your application in case of an emergency. For example, if a new feature is causing performance issues or has a critical bug, you can use a kill switch to turn it off instantly without having to roll back a deployment. This can also be used to disable parts of the application during maintenance or downtime of a dependency. + +### Mobile App Versioning +For mobile applications, a fix for a bug requires a new release to be approved by app stores and then users have to update their app. This can take time. With feature flags, you can remotely disable a broken feature for affected app versions, giving you time to fix the bug and release an update. + +## Personalisation and Entitlement + +### Feature Management for Different User Segments +You can use feature flags to control which features are available to different groups of users. This is often used for managing features based on subscription plans (e.g., Free vs. Pro). You can create segments of users based on their attributes (like `plan: 'scale-up'`) and then enable or disable features for those segments. + +### Beta Programmes +When you want to release a new feature to a specific group of beta testers, you can create a segment for them and enable the feature only for that segment. + +## Further Reading + +- For a deeper dive into the different types of feature flag lifecycles, check out the [Feature Flags Lifecycles guide](/best-practices/flag-lifecycle). +- Learn more about [A/B and multivariate testing](/experimentation-ab-testing) to see how experimentation can be managed with Flagsmith. +- Explore how [Segments](/flagsmith-concepts/segments) can help you target features to specific groups of users. diff --git a/docs/docs/clients/3rd-party.md b/docs/docs/clients/3rd-party.md deleted file mode 100644 index 1b1dc4efded6..000000000000 --- a/docs/docs/clients/3rd-party.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -description: Flagsmith 3rd Party Client SDKs -sidebar_label: 3rd Party -sidebar_position: 7 ---- - -# 3rd Party SDKs - -Community members have worked on their own SDKs which we have listed below. If you've written an SDK and want it listed -here please get in touch! - -## Vue - -[Flagsmith client for Vue applications](https://github.com/jhoermann/flagsmith-vue). - -## Clojure - -[A Clojure wrapper around the Flagsmith Java SDK](https://github.com/Global-Online-Health/flagsmith-clj). - -## Laravel - -[A PHP/Laravel Framework library](https://github.com/clearlyip/laravel-flagsmith) diff --git a/docs/docs/clients/client-side/_category_.json b/docs/docs/clients/client-side/_category_.json deleted file mode 100644 index 797df12c86a1..000000000000 --- a/docs/docs/clients/client-side/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Client Side", - "position": 3, - "collapsed": false -} diff --git a/docs/docs/clients/rest.md b/docs/docs/clients/rest.md deleted file mode 100644 index 8a8e651207e3..000000000000 --- a/docs/docs/clients/rest.md +++ /dev/null @@ -1,534 +0,0 @@ ---- -description: Manage your Feature Flags and Remote Config in your REST APIs. -sidebar_label: REST -sidebar_position: 2 ---- - -# Direct API Access - -:::tip - -Some API actions require object UUIDs/IDs to be referenced. You can enable the [JSON View](#json-view) from your account -settings page which will help you access these variables. - -::: - -:::info - -Our Admin API has a [Rate Limit](/system-administration/system-limits#admin-api-rate-limit) that you need to be aware -of. - -::: - -## Overview - -Our API is split in two : - -1. The `Public SDK API` that serves Flags to your client applications. This API does not require secret keys and is open - by design. -2. The `Private Admin API` that allows you to do things like programatically create and toggle flags. Interacting with - this API requires a [secret key](#private-admin-api-endpoints). - -## API Explorer - -You can view the API via Swagger at [https://api.flagsmith.com/api/v1/docs/](https://api.flagsmith.com/api/v1/docs/) or -get OpenAPI as [JSON](https://api.flagsmith.com/api/v1/docs/?format=.json) or -[YAML](https://api.flagsmith.com/api/v1/docs/?format=.yaml). - -We have a [Postman Collection](https://www.postman.com/flagsmith/workspace/flagsmith/overview) that you can use to play -around with the API and get a feel for how it works. - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://app.getpostman.com/run-collection/14712118-a638325a-f1f4-4570-8b4d-fd2841218dfa?action=collection%2Ffork&collection-url=entityId%3D14712118-a638325a-f1f4-4570-8b4d-fd2841218dfa%26entityType%3Dcollection%26workspaceId%3D452554eb-f581-4754-b5b8-0deabdce9f4b#?env%5BFlagsmith%20Environment%5D=W3sia2V5IjoiRmxhZ3NtaXRoIEVudmlyb25tZW50IEtleSIsInZhbHVlIjoiOEt6RVRkRGVNWTd4a3FrU2tZM0dzZyIsImVuYWJsZWQiOnRydWV9LHsia2V5IjoiYmFzZVVybCIsInZhbHVlIjoiaHR0cHM6Ly9hcGkuZmxhZ3NtaXRoLmNvbS9hcGkvdjEvIiwiZW5hYmxlZCI6dHJ1ZX0seyJrZXkiOiJJZGVudGl0eSIsInZhbHVlIjoicG9zdG1hbl91c2VyXzEyMyIsImVuYWJsZWQiOnRydWV9XQ==) - -You can also access the API directly with tools like [curl](https://curl.haxx.se/) or [httpie](https://httpie.org/), or -with clients for languages that we do not currently have SDKs for. - -## API Keys - -### Public SDK API Endpoints - -Publicly accessible API calls need to have an environment key supplied with each request. This is provided as an HTTP -header, with the name `X-Environment-Key` and the value of the Environment Key that you can find within the Flagsmith -administrative area. - -For SaaS customers, the URL to hit for this API is [`https://edge.api.flagsmith.com/`](/advanced-use/edge-api). - -Our Edge API specification is detailed [here](/edge-api/overview). - -### Private Admin API Endpoints - -You can also do things like create new flags, environments, toggle flags or indeed anything that is possible from the -administrative front end via the API. - -To authenticate, you can use the API Token associated with your Organisation. This can be found in the `Organisation` -page from the top navigation panel. You need to create a token and then provide it as an HTTP header: - -```bash -Authorization: Api-Key -``` - -For example, to create a new Environment: - -```bash -curl 'https://api.flagsmith.com/api/v1/environments/' \ - -H 'Content-Type: application/json' \ - -H 'Authorization: Api-Key ' \ - --data-binary '{"name":"New Environment","project":""}' -``` - -You can find a complete list of endpoints via the Swagger REST API at -[https://api.flagsmith.com/api/v1/docs/](https://api.flagsmith.com/api/v1/docs/). - -For SaaS customers, the URL to hit for this API is `https://api.flagsmith.com/`. - -## Curl Examples - -These are the two main endpoints that you need to consume the SDK aspect of the API. - -### Get Environment Flags - -```bash -curl 'https://edge.api.flagsmith.com/api/v1/flags/' -H 'X-Environment-Key: ' -``` - -### Send Identity with Traits and receive Flags - -This command will perform the entire SDK Identity workflow in a single call: - -1. Lazily create an Identity -2. Setting Traits for the Identity -3. Receiving the Flags for that Identity - -```bash -curl --request POST 'https://edge.api.flagsmith.com/api/v1/identities/' \ ---header 'X-Environment-Key: ' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "identifier":"identifier_5", - "traits": [ - { - "trait_key": "my_trait_key", - "trait_value": 123.5 - }, - { - "trait_key": "my_other_key", - "trait_value": true - } - ] -}' -``` - -## JSON View {#json} - -You can enable the JSON view in your Account Settings page. This will then give you access to relevant object meta data -in the Flag area of the dashboard. - -## Code Examples - -Below are some examples for achieving certain actions with the REST API, using python. - -### Create a feature - -```python -import os - -from requests import Session - -API_URL = os.environ.get("API_URL", "https://api.flagsmith.com/api/v1") # update this if self-hosting -PROJECT_ID = os.environ["PROJECT_ID"] # obtain this from the URL on your dashboard -TOKEN = os.environ["API_TOKEN"] # obtain this from the account page in your dashboard -FEATURE_NAME = os.environ["FEATURE_NAME"] # name of the feature to create - -session = Session() -session.headers.update({"Authorization": f"Token {TOKEN}"}) - -create_feature_url = f"{API_URL}/projects/{PROJECT_ID}/features/" -data = {"name": FEATURE_NAME} -response = session.post(create_feature_url, json=data) -``` - -### Update the value / state of a feature in an environment - -```python -import json -import os - -import requests - -TOKEN = os.environ.get("API_TOKEN") # obtained from Account section in dashboard -ENV_KEY = os.environ.get("ENV_KEY") # obtained from environment settings in dashboard -BASE_URL = "https://api.flagsmith.com/api/v1" # update this if self hosting -FEATURE_STATES_URL = f"{BASE_URL}/environments/{ENV_KEY}/featurestates" -FEATURE_NAME = os.environ.get("FEATURE_NAME") - -session = requests.Session() -session.headers.update( - {"Authorization": f"Token {TOKEN}", "Content-Type": "application/json"} -) - -# get the existing feature state id based on the feature name -get_feature_states_response = session.get( - f"{FEATURE_STATES_URL}/?feature_name={FEATURE_NAME}" -) -feature_state_id = get_feature_states_response.json()["results"][0]["id"] - -# update the feature state -data = {"enabled": True, "feature_state_value": "new value"} # `feature_state_value` can be str, int or bool -update_feature_state_response = session.patch( - f"{FEATURE_STATES_URL}/{feature_state_id}/", data=json.dumps(data) -) -``` - -### Create a segment and segment override - -```python -import os - -from requests import Session - -API_URL = os.environ.get("API_URL", "https://api.flagsmith.com/api/v1") # update this if self-hosting -SEGMENT_NAME = os.environ["SEGMENT_NAME"] # define the name of the segment here -PROJECT_ID = os.environ["PROJECT_ID"] # obtain this from the URL on your dashboard -TOKEN = os.environ["API_TOKEN"] # obtain this from the account page in your dashboard -FEATURE_ID = os.environ.get("FEATURE_ID") # obtain this from the URL on your dashboard when viewing a feature -IS_FEATURE_SPECIFIC = os.environ.get("IS_FEATURE_SPECIFIC", default=False) == "True" # set this to True to create a feature specific segment -ENVIRONMENT_ID = os.environ["ENVIRONMENT_ID"] # must (currently) be obtained by inspecting the request to /api/v1/environments in the network console - -# set these values to create a segment override for the segment, feature, environment combination -ENABLE_FOR_SEGMENT = os.environ.get("ENABLE_FOR_SEGMENT", default=False) == "True" -VALUE_FOR_SEGMENT = os.environ.get("VALUE_FOR_SEGMENT") - -SEGMENT_DEFINITION = { - "name": SEGMENT_NAME, - "feature": FEATURE_ID if IS_FEATURE_SPECIFIC else None, - "project": PROJECT_ID, - "rules": [ - { - "type": "ALL", - "rules": [ # add extra rules here to build up 'AND' logic - { - "type": "ANY", - "conditions": [ # add extra conditions here to build up 'OR' logic - { - "property": "my_trait", # specify a trait key that you want to match on, e.g. organisationId - "operator": "EQUAL", # specify the operator you want to use (one of EQUAL, NOT_EQUAL, GREATER_THAN, LESS_THAN, GREATER_THAN_INCLUSIVE, LESS_THAN_INCLUSIVE, CONTAINS, NOT_CONTAINS, REGEX, PERCENTAGE_SPLIT, IS_SET, IS_NOT_SET) - "value": "my-value" # the value to match against, e.g. 103 - } - ] - } - ] - } - ] -} - -session = Session() -session.headers.update({"Authorization": f"Token {TOKEN}"}) - -# first let's create the segment -create_segment_url = f"{API_URL}/projects/{PROJECT_ID}/segments/" -create_segment_response = session.post(create_segment_url, json=SEGMENT_DEFINITION) -assert create_segment_response.status_code == 201 -segment_id = create_segment_response.json()["id"] - -if not any(key in os.environ for key in ("ENABLE_FOR_SEGMENT", "VALUE_FOR_SEGMENT")): - print("Segment created! Not creating an override as no state / value defined.") - exit(0) - -# next we need to create a feature segment (a flagsmith internal entity) -create_feature_segment_url = f"{API_URL}/features/feature-segments/" -feature_segment_data = { - "feature": FEATURE_ID, - "segment": segment_id, - "environment": ENVIRONMENT_ID -} -create_feature_segment_response = session.post(create_feature_segment_url, json=feature_segment_data) -assert create_feature_segment_response.status_code == 201 -feature_segment_id = create_feature_segment_response.json()["id"] - -# finally, we can create the segment override -create_segment_override_url = f"{API_URL}/features/featurestates/" -feature_state_data = { - "feature": FEATURE_ID, - "feature_segment": feature_segment_id, - "environment": ENVIRONMENT_ID, - "enabled": ENABLE_FOR_SEGMENT, - "feature_state_value": { - "type": "unicode", - "string_value": VALUE_FOR_SEGMENT - } -} -create_feature_state_response = session.post(create_segment_override_url, json=feature_state_data) -assert create_feature_state_response.status_code == 201 -``` - -### Create identity overrides - -Creating identity overrides varies depending on if you are using Flagsmith SaaS or a different Flagsmith environment. - -
- -Flagsmith SaaS - -Creating an identity override requires the following information: - -* Client-side API key of the environment to create the override in -* Identifier to create the override for -* Name of the feature to create the override for -* Desired feature state - -To create an identity override, use the -[Update Edge Identity Feature State endpoint](https://api.flagsmith.com/api/v1/docs/#/operations-api-api_v1_environments_edge-identities_list). -For example, the following request would enable the feature named `custom_background_colour` with a value of `blue` -for the identity `my_user_id`: - -``` -curl --request PUT \ - --url https://api.flagsmith.com/api/v1/environments/environments/YOUR_ENVIRONMENT_API_KEY/edge-identities-featurestates \ - --header 'Accept: application/json' \ - --header 'Authorization: Token YOUR_ADMIN_API_KEY' \ - --header 'Content-Type: application/json' \ - --data '{"enabled":true,"feature":"custom_background_colour","feature_state_value":"blue", "identifier":"my_user_id"}' -``` - -This will create the override if it doesn't exist, or update it if it does (upsert). - -
- -
- -Self-hosted and private cloud - -Creating an identity override in non-SaaS environments requires additional information compared to Flagsmith SaaS: - -* Internal ID of the feature to override -* Internal ID of the target identity -* ID of the identity feature state to update, if an override already exists - -Make sure you have [JSON View](#json) enabled. - -To obtain the feature's internal ID, open the feature in the Flagsmith dashboard, and expand the "JSON Data: Feature" -section. The feature's internal ID is the `id` field of this JSON object. - -To obtain the internal IDs of the target identity, browse to the target identity from the Identities section in the -Flagsmith dashboard. The identity's internal ID is displayed in the URL, which is `1234` in this example: - -``` -https://flagsmith.example.com/project/5/environment/AbCxYz/users/my_identifier/1234 -``` - -To obtain the ID of the identity feature state, from the same page on the Flagsmith dashboard, expand the "JSON -Data: Identity Feature States" section. If you don't see this section, you can create an identity override for this -feature and identity combination by calling the -[Create Identity Feature State](https://api.flagsmith.com/api/v1/docs/#/api/api_v1_environments_featurestates_create) -endpoint. For example, this request would set the feature with ID `10` to enabled, with a value of `"blue"`: - -``` -curl --request POST 'https://flagsmith.example.com/api/v1/environments/AbCXyZ/identities/1234/featurestates/' \ - --header 'Accept: application/json' \ - --header 'Authorization: Token YOUR_ADMIN_API_KEY' - --data '{"enabled":true,"feature":10,"feature_state_value":"blue"}' -``` - -If you do have an identity feature state already, its ID is the `id` field of the object having the -same internal feature ID you had previously found. In this example, if your internal feature ID was `10`, the -identity feature state ID would be `200`: - -```json -[ - { - // highlight-next-line - "id": 200, - "feature_state_value": null, - "multivariate_feature_state_values": [], - "identity": { - "id": 1234, - "identifier": "my_user_id" - }, - "enabled": true, - "feature": 10 - }, - { - "id": 300, - "feature_state_value": null, - "multivariate_feature_state_values": [], - "identity": { - "id": 1234, - "identifier": "my_user_id" - }, - "enabled": false, - "feature": 20 - } -] -``` - -To update this identity override, call the -[Update Identity Feature State](https://api.flagsmith.com/api/v1/docs/#/api/api_v1_environments_featurestates_update) -endpoint. For example, this request would enable the feature with internal ID `10` for the identity with internal ID -`1234`, assuming there was previously an override with an ID of `200`: - -``` -curl --request PUT 'https://flagsmith.example.com/api/v1/environments/AbCXyZ/identities/1234/featurestates/200' \ - --header 'Accept: application/json' \ - --header 'Authorization: Token YOUR_ADMIN_API_KEY' - --data '{"enabled":true,"feature":10}' -``` - -
- -### Update a segment's rules - -```python -import os - -from requests import Session - -API_URL = os.environ.get("API_URL", "https://api.flagsmith.com/api/v1") # update this if self-hosting -PROJECT_ID = os.environ["PROJECT_ID"] # obtain this from the URL on your dashboard -TOKEN = os.environ["API_TOKEN"] # obtain this from the account page in your dashboard -SEGMENT_ID = os.environ.get("SEGMENT_ID") # obtain this from the URL on your dashboard when viewing a segment - -SEGMENT_RULES_DEFINITION = { - "rules": [ - { - "type": "ALL", - "rules": [ - { - "type": "ANY", - "conditions": [ # add as many conditions here to build up a segment - { - "property": "my_trait", # specify a trait key that you want to match on, e.g. organisationId - "operator": "EQUAL", # specify the operator you want to use (one of EQUAL, NOT_EQUAL, GREATER_THAN, LESS_THAN, GREATER_THAN_INCLUSIVE, LESS_THAN_INCLUSIVE, CONTAINS, NOT_CONTAINS, REGEX, PERCENTAGE_SPLIT, IS_SET, IS_NOT_SET) - "value": "my-value" # the value to match against, e.g. 103 - } - ] - } - ] - } - ] -} - -session = Session() -session.headers.update({"Authorization": f"Token {TOKEN}"}) - -update_segment_url = f"{API_URL}/projects/{PROJECT_ID}/segments/{SEGMENT_ID}/" -session.patch(update_segment_url, json=SEGMENT_RULES_DEFINITION) -``` - -### Iterate over Identities - -Sometimes it can be useful to iterate over your Identities to check things like Segment overrides. - -```python -import os - -import requests - -TOKEN = os.environ.get("API_TOKEN") # obtained from Account section in dashboard -ENV_KEY = os.environ.get("ENV_KEY") # obtained from Environment settings in dashboard -BASE_URL = "https://api.flagsmith.com/api/v1" # update this if self hosting -IDENTITIES_PAGE_URL = f"{BASE_URL}/environments/{ENV_KEY}/edge-identities/?page_size=20" - -session = requests.Session() -session.headers.update( - {"Authorization": f"Token {TOKEN}", "Content-Type": "application/json"} -) - -# get the existing feature state id based on the feature name -page_of_identities = session.get(f"{IDENTITIES_PAGE_URL}") -print(page_of_identities.json()) - -for identity in page_of_identities.json()['results']: - print(str(identity)) - IDENTITY_UUID = identity['identity_uuid'] - IDENTITY_URL = f"{BASE_URL}/environments/{ENV_KEY}/edge-identities/{IDENTITY_UUID}/edge-featurestates/all/" - identity_data = session.get(f"{IDENTITY_URL}") - print(identity_data.json()) -``` - -### Delete an Edge Identity - -```python -import os - -import requests - -TOKEN = os.environ.get("API_TOKEN") # obtained from Account section in dashboard -ENV_KEY = os.environ.get("ENV_KEY") # obtained from Environment settings in dashboard -IDENTITY_UUDI = os.environ["IDENTITY_UUDI"] # must (currently) be obtained by inspecting the request to /api/v1/environments/{ENV_KEY}/edge-identities/{IDENTITY_UUDI} in the network console -BASE_URL = "https://edge.api.flagsmith.com/api/v1" # update this if self hosting - -session = requests.Session() -session.headers.update( - {"Authorization": f"Token {TOKEN}", "Content-Type": "application/json"} -) - -# delete the existing edge identity based on the uuid -delete_edge_identity_url = f"{BASE_URL}/environments/{ENV_KEY}/edge-identities/{IDENTITY_UUDI}/" -delete_edge_identity_response = session.delete(delete_edge_identity_url) -assert delete_edge_identity_response.status_code == 204 - -``` - -### Delete an Identity - -```python -import os - -import requests - -TOKEN = os.environ.get("API_TOKEN") # obtained from Account section in dashboard -ENV_KEY = os.environ.get("ENV_KEY") # obtained from Environment settings in dashboard -IDENTITY_ID = os.environ["IDENTITY_ID"] # obtain this from the URL on your dashboard when viewing an identity -BASE_URL = "https://api.flagsmith.com/api/v1" # update this if self hosting - -session = requests.Session() -session.headers.update( - {"Authorization": f"Token {TOKEN}", "Content-Type": "application/json"} -) - -# delete the existing identity based on the identity id -delete_identity_url = f"{BASE_URL}/environments/{ENV_KEY}/identities/{IDENTITY_ID}/" -delete_identity_response = session.delete(delete_identity_url) -assert delete_identity_response.status_code == 204 -``` - -### Bulk Uploading Identities and Traits - -You can achieve this with a `POST` to the `bulk-identities` endpoint: - -```bash -curl -i -X POST "https://edge.api.flagsmith.com/api/v1/bulk-identities" \ - -H "X-Environment-Key: ${FLAGSMITH_ENVIRONMENT_KEY}" \ - -H 'Content-Type: application/json' \ - -d $'{ - "data": [ - { - "identifier": "my_identifier_1", - "traits": [ - { - "trait_key": "my_key_name", - "trait_value": "set from POST /bulk-identities" - } - ] - }, - { - "identifier": "my_identifier_2", - "traits": [ - { - "trait_key": "some_other_key_name", - "trait_value": "if this identity does not exist, it will be created by this request" - } - ] - }, - { - "identifier": "my_identifier_3", - "traits": [ - { - "trait_key": "this_trait_will_be_deleted", - "trait_value": null - } - ] - } - ] - }' -``` diff --git a/docs/docs/deployment-self-hosting/_category_.json b/docs/docs/deployment-self-hosting/_category_.json new file mode 100644 index 000000000000..bf89feb3f166 --- /dev/null +++ b/docs/docs/deployment-self-hosting/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Deployment and Self-hosting", + "collapsed": true, + "position": 10 +} \ No newline at end of file diff --git a/docs/docs/deployment-self-hosting/administration-and-maintenance/_category_.json b/docs/docs/deployment-self-hosting/administration-and-maintenance/_category_.json new file mode 100644 index 000000000000..495de3e0bac7 --- /dev/null +++ b/docs/docs/deployment-self-hosting/administration-and-maintenance/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Administration and Maintenance", + "position": 7, + "collapsed": true +} diff --git a/docs/docs/deployment-self-hosting/administration-and-maintenance/troubleshooting.md b/docs/docs/deployment-self-hosting/administration-and-maintenance/troubleshooting.md new file mode 100644 index 000000000000..24edd88af33a --- /dev/null +++ b/docs/docs/deployment-self-hosting/administration-and-maintenance/troubleshooting.md @@ -0,0 +1,21 @@ +--- +title: Troubleshooting +sidebar_label: Troubleshooting +sidebar_position: 90 +--- + +Here are some common issues encountered when trying to set up Flagsmith in a self-hosted environment. + +## Health Checks + +If you are using health checks, make sure to use `/health` as the health-check endpoint for both the API and the frontend. + +## API and Database Connectivity + +The most common cause of issues when setting things up in AWS with an RDS database is missing Security Group permissions between the API application and the RDS database. You need to ensure that the attached security groups for ECS/Fargate/EC2 allow access to the RDS database. [AWS provide more detail about this here](https://aws.amazon.com/premiumsupport/knowledge-center/ecs-task-connect-rds-database/) and [here](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.RDSSecurityGroups.html). + +Make sure you have a `DATABASE_URL` environment variable set within the API application. + +## Frontend > API DNS Setup + +If you are running the API and the frontend as separate applications, you need to make sure that the frontend is pointing to the API. Check the [Frontend environment variables](/deployment-self-hosting/core-configuration/environment-variables#frontend-environment-variables), particularly `API_URL`. diff --git a/docs/docs/deployment-self-hosting/administration-and-maintenance/upgrades-and-rollbacks.md b/docs/docs/deployment-self-hosting/administration-and-maintenance/upgrades-and-rollbacks.md new file mode 100644 index 000000000000..e8eeecefcd94 --- /dev/null +++ b/docs/docs/deployment-self-hosting/administration-and-maintenance/upgrades-and-rollbacks.md @@ -0,0 +1,82 @@ +--- +sidebar_label: Upgrades and Rollbacks +title: Upgrades and Rollbacks +sidebar_position: 2 +--- + +# Rolling Back to a Previous Version of Flagsmith + +:::warning + +These steps may result in data loss in the scenario where new models or fields have been added to the database. We recommend taking a full backup of the database before completing the rollback. + +::: + +This page covers the process of rolling back to a previous version of Flagsmith. If you need to roll back to a previous version, you will need to ensure that the database is also rolled back to the correct state. In order to do this, you will need to unapply all the migrations that happened between the version that you want to roll back to and the one that you are rolling back from. The following steps explain how to do that. + +## Steps for v2.151.0 and Later + +1. Identify the date and time that you deployed the version that you want to roll back to. + +:::tip + +If you are unsure about when you completed the previous deployment, you can use the `django_migrations` table as a guide. If you query the table using the following query, you should see the migrations that have been applied (in descending order), grouped in batches corresponding to each deployment. + +```sql +SELECT * +FROM django_migrations +ORDER BY applied DESC +``` + +::: + +2. Run the rollback command inside a Flagsmith API container running the _current_ version of Flagsmith: + +```bash +python manage.py rollbackmigrationsafter "" +``` + +3. Roll back the Flagsmith API to the desired version. + +## Steps for Versions Earlier Than v2.151.0 + +If you are rolling back from a version earlier than v2.151.0, you will need to replace step 2 above with the following two steps. + +### Step 1: Generate Rollback Commands + +Replace the datetime in the query below with a datetime after the deployment of the version you want to roll back to, and before any subsequent deployments. Execute the subsequent query against the Flagsmith database. + +```sql {14} showLineNumbers +select + concat('python manage.py migrate ', + app, + ' ', + case + when substring(name, 1, 4)::integer = 1 then 'zero' + else lpad((substring(name, 1, 4)::integer - 1)::text, 4, '0') + end + ) as "python_commands" +from django_migrations +where id in ( + select min(id) + from django_migrations + where applied >= 'yyyy-MM-dd HH:mm:ss' + group by app +); +``` + +Example output: + +``` + python_commands +----------------------------------------------- + python manage.py migrate multivariate 0007 + python manage.py migrate segment 0004 + python manage.py migrate environments 0034 + python manage.py migrate features 0064 + python manage.py migrate token_blacklist zero +``` + +### Step 2: Execute the Rollback Commands + +Run the generated commands inside a Flagsmith API container running the _current_ version of Flagsmith. diff --git a/docs/docs/deployment-self-hosting/administration-and-maintenance/using-the-django-admin.md b/docs/docs/deployment-self-hosting/administration-and-maintenance/using-the-django-admin.md new file mode 100644 index 000000000000..6c04373ed4db --- /dev/null +++ b/docs/docs/deployment-self-hosting/administration-and-maintenance/using-the-django-admin.md @@ -0,0 +1,55 @@ +--- +sidebar_label: Django Admin +title: Django Admin +sidebar_position: 100 +--- + +# Django Admin + +The Flagsmith API is a Django application. As such, certain administrative tasks can be performed using [Django's built-in admin interface](https://docs.djangoproject.com/en/4.2/ref/contrib/admin/), which we refer to as Django Admin. + +:::danger + +Improper use of Django Admin can cause data loss and make your Flagsmith instance unusable. Make sure to control who has access, and only perform tasks as directed by Flagsmith staff. + +::: + +## Accessing Django Admin + +Django Admin can be accessed from the `/admin/` route on the Flagsmith API. Note that the trailing slash is important. + +Accessing Django Admin requires a user with [`is_staff`](https://docs.djangoproject.com/en/4.2/ref/contrib/auth/#django.contrib.auth.models.User.is_staff) set. This does not grant any additional permissions beyond accessing Django Admin itself. + +A user with [`is_superuser`](https://docs.djangoproject.com/en/4.2/ref/contrib/auth/#django.contrib.auth.models.User.is_superuser) is granted all permissions. Note that superusers still require `is_staff` to access Django Admin. + +You can obtain a user with these permissions using any of these methods: + +* Use the [`createsuperuser` management command](/deployment-self-hosting/core-configuration/initial-setup#local-installation) from a Flagsmith API shell. +* If no users exist yet, [visit the Initialise Config page](/deployment-self-hosting/core-configuration/initial-setup#cloud-environments-eg-heroku-ecs). +* Manually set the `is_staff` and `is_superuser` database fields for your user in the `users_ffadminuser` table. + +## Authentication + +You can log in to Django Admin using the same email and password you use to log in to Flagsmith, or using Google login. + +### Email and Password + +To log in to Django Admin with a password, make sure the Flagsmith API has the `ENABLE_ADMIN_ACCESS_USER_PASS` environment variable set to `true`. + +If your Flagsmith account does not have a password, you can create one using any of these methods: + +* From the Flagsmith login page, click "Forgot password". Make sure your Flagsmith API is [configured to send emails](/deployment-self-hosting/core-configuration/email-setup). +* From a Flagsmith API shell, run `python manage.py changepassword your_email@example.com` and type a password. + +### Google + +Google accounts use OAuth 2.0, which requires TLS. + +To set up Google authentication for Django Admin, create an OAuth client ID and secret from the [Google Developer Console](https://console.developers.google.com/project). The redirect URI should point to `/admin/admin_sso/assignment/end/` on your API domain. + +Set your Google OAuth client ID and secret in the following Flagsmith API environment variables: + +* `OAUTH_CLIENT_ID` +* `OAUTH_CLIENT_SECRET` + +To log in with Google, click "Log in using SSO" from the Django Admin login page. diff --git a/docs/docs/deployment-self-hosting/core-configuration/_category_.json b/docs/docs/deployment-self-hosting/core-configuration/_category_.json new file mode 100644 index 000000000000..dda0067ad9a7 --- /dev/null +++ b/docs/docs/deployment-self-hosting/core-configuration/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Core Configuration", + "position": 4, + "collapsed": true, + "link": { + "type": "generated-index" + } +} \ No newline at end of file diff --git a/docs/docs/deployment-self-hosting/core-configuration/caching-strategies.md b/docs/docs/deployment-self-hosting/core-configuration/caching-strategies.md new file mode 100644 index 000000000000..bfd9d006a50c --- /dev/null +++ b/docs/docs/deployment-self-hosting/core-configuration/caching-strategies.md @@ -0,0 +1,42 @@ +--- +title: "Caching Strategies" +description: "How to configure caching for API endpoints and the environment document." +sidebar_position: 40 +--- + +The application utilises an in-memory cache for a number of different API endpoints to improve performance. The main things that are cached are listed below: + +1. Environment flags - the application utilises an in memory cache for the flags returned when calling /flags. The number of seconds this is cached for is configurable using the environment variable `"CACHE_FLAGS_SECONDS"` +2. Project segments - the application utilises an in memory cache for returning the segments for a given project. The number of seconds this is cached for is configurable using the environment variable `"CACHE_PROJECT_SEGMENTS_SECONDS"`. +3. Flags and identities endpoint caching - the application provides the ability to cache the responses to the GET /flags and GET /identities endpoints. The application exposes the configuration to allow the caching to be handled in a manner chosen by the developer. The configuration options are explained in more detail below. +4. Environment document - when making heavy use of the environment document, it is often wise to utilise caching to reduce the load on the database. Details are provided below. + +## Flags & Identities Endpoint Caching + +To enable caching on the flags and identities endpoints (GET requests only), you must set the following environment variables: + +| Environment Variable | Description | Example value | Default | +| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------ | --------------------------------------------- | +| `FLAG_IDENTITY_CACHE_LOCATION` | The location to cache the flags and identities endpoints. One of `default` or `redis`. | `redis` | | +| `FLAG_IDENTITY_CACHE_TTL_SECONDS` | The number of seconds to cache the flags and identities endpoints for. | `60` | `0` (i.e. don't cache) | + +## Environment Document Caching + +To enable caching on the environment document endpoint, you must set the following environment variables: + +:::caution + +Persistent cache should only be used with cache backends that offer a centralised cache. It should not be used with e.g. LocMemCache. + +::: + +:::info + +When using a persistent cache, a change can take a few seconds to update the cache. This can also be optimised by increasing the performance of your task processor. + +::: + +| Environment Variable | Description | Example value | Default | +|---------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------|-----------------------------------------------| +| `CACHE_ENVIRONMENT_DOCUMENT_MODE` | The caching mode. One of `PERSISTENT` or `EXPIRING`. Note that although the default is `EXPIRING` there is no caching by default due to the default value of `CACHE_ENVIRONMENT_DOCUMENT_SECONDS` | `PERSISTENT` | `EXPIRING` | +| `CACHE_ENVIRONMENT_DOCUMENT_SECONDS` | Number of seconds to cache the environment for (only relevant when `CACHE_ENVIRONMENT_DOCUMENT_MODE=EXPIRING`) | `60` | `0` ( = don't cache) | \ No newline at end of file diff --git a/docs/docs/deployment-self-hosting/core-configuration/email-setup.md b/docs/docs/deployment-self-hosting/core-configuration/email-setup.md new file mode 100644 index 000000000000..d662767c1332 --- /dev/null +++ b/docs/docs/deployment-self-hosting/core-configuration/email-setup.md @@ -0,0 +1,50 @@ +--- +title: "Email Setup" +description: "How to configure Flagsmith to send emails using SMTP or SendGrid." +sidebar_position: 30 +--- + +:::note + +You can self-host Flagsmith without setting up an email server/gateway. You can invite additional users to the platform using invitation links, and the platform will run fine without email. + +::: + +:::tip + +Flagsmith makes use of the `django_site` table to provide the domain name for email template links. You will need to configure the record in this table to point to your domain for email links to work. + +::: + +## Required Environment Variables + +- `SENDER_EMAIL`: Email address from which emails are sent +- `EMAIL_BACKEND`: One of: + - `django.core.mail.backends.smtp.EmailBackend` + - `sgbackend.SendGridBackend` + - `django_ses.SESBackend` + +## SMTP Configuration + +If using `django.core.mail.backends.smtp.EmailBackend`, you will need to configure: + +- `EMAIL_HOST` = env("EMAIL_HOST", default='localhost') +- `EMAIL_HOST_USER` = env("EMAIL_HOST_USER", default=None) +- `EMAIL_HOST_PASSWORD` = env("EMAIL_HOST_PASSWORD", default=None) +- `EMAIL_PORT` = env("EMAIL_PORT", default=587) +- `EMAIL_USE_TLS` = env.bool("EMAIL_USE_TLS", default=True) + +## SendGrid Configuration + +If using `sgbackend.SendGridBackend`, you will need to configure: + +- `SENDGRID_API_KEY`: API key for the SendGrid account + +## AWS SES Configuration + +If using AWS SES, you will need to configure: + +- `AWS_SES_REGION_NAME`: If using Amazon SES as the email provider, specify the region (e.g. eu-central-1) that contains your verified sender email address. Defaults to us-east-1 +- `AWS_SES_REGION_ENDPOINT`: SES region endpoint, e.g. email.eu-central-1.amazonaws.com. Required when using SES. +- `AWS_ACCESS_KEY_ID`: If using Amazon SES, these form part of your SES credentials. +- `AWS_SECRET_ACCESS_KEY`: If using Amazon SES, these form part of your SES credentials. \ No newline at end of file diff --git a/docs/docs/deployment-self-hosting/core-configuration/environment-variables.md b/docs/docs/deployment-self-hosting/core-configuration/environment-variables.md new file mode 100644 index 000000000000..adc0fc7f6d5a --- /dev/null +++ b/docs/docs/deployment-self-hosting/core-configuration/environment-variables.md @@ -0,0 +1,60 @@ +--- +title: "Environment Variables" +description: "A reference page listing all environment variables for the API and Frontend." +sidebar_position: 20 +--- + +This page provides a comprehensive reference for all the environment variables you can use to configure your self-hosted Flagsmith instance. You'll find variables for both the API and the frontend, along with a brief description of what each one does. Use this as a handy guide when setting up or tweaking your deployment, whether you're running locally, in the cloud, or on Kubernetes. If you're not sure what a particular variable does, or whether you need to set it, check the relevant section below for more details. + +## API Environment Variables + +- `DJANGO_ALLOWED_HOSTS`: Comma-separated list of domains that can access the API. Alternatively `*` to allow any. +- `DATABASE_URL`: The URL of your PostgreSQL database. Both `postgres://` and `postgresql://` schemas are supported. +- `REDIS_URL`: The URL of your Redis instance. +- `ENV`: The environment the application is running in, e.g. "prod". +- `SENTRY_DSN`: If you want to send errors to Sentry, specify the DSN here. +- `SENTRY_TRACE_SAMPLE_RATE`: The percentage of transactions to trace in Sentry. See [Sentry's documentation](https://docs.sentry.io/platforms/python/performance/instrumentation/django/#configure) for more info. +- `LOG_LEVEL`: The log level to output at. One of `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`. +- `LOG_FORMAT`: The format to use for logging. One of `generic` or `json`. +- `DJANGO_SECRET_KEY`: A long, random and unique string used for cryptographic signing. +- `ACCESS_LOG_LOCATION`: The location to store web logs generated by Gunicorn if running as a Docker container. If not set, no logs will be stored. If set to `-`, the logs will be sent to `stdout`. +- `DJANGO_SETTINGS_MODULE`: Python path to settings file for the given environment, e.g. "app.settings.develop" +- `ALLOW_ADMIN_INITIATION_VIA_CLI`: Enables the `bootstrap` management command which creates default admin user, organisation, and project. +- `ADMIN_EMAIL`: Email to use for the default superuser creation. +- `ORGANISATION_NAME`: Organisation name to use for the default organisation. +- `PROJECT_NAME`: Project name to use for the default project. +- `ENABLE_GZIP_COMPRESSION`: If Django should gzip compress HTTP responses. Defaults to `False`. +- `GOOGLE_ANALYTICS_KEY`: If Google Analytics is required, add your tracking code. +- `GOOGLE_SERVICE_ACCOUNT`: Service account JSON for accessing the Google API, used for getting usage of an organisation - needs access to analytics.readonly scope. +- `INFLUXDB_TOKEN`: If you want to send API events to InfluxDB, specify this write token. +- `INFLUXDB_URL`: The URL for your InfluxDB database. +- `INFLUXDB_ORG`: The organisation string for your InfluxDB API call. +- `GA_TABLE_ID`: GA table ID (view) to query when looking for organisation usage. +- `USER_CREATE_PERMISSIONS`: Set the permissions for creating new users, using a comma-separated list of djoser or rest_framework permissions. Use this to turn off public user creation for self-hosting. e.g. `'djoser.permissions.CurrentUserOrAdmin'`. Defaults to `'rest_framework.permissions.AllowAny'`. +- `ALLOW_REGISTRATION_WITHOUT_INVITE`: Determines whether users can register without an invite. Defaults to True. Set to False or 0 to disable. Note that if disabled, new users must be invited via email. +- `PREVENT_SIGNUP`: Determines whether to prevent new signups. +- `ENABLE_EMAIL_ACTIVATION`: New user registration will go via email activation flow, default False. + +## Frontend Environment Variables + +- `FLAGSMITH_API_URL`: The API to hit for requests. E.g. `https://edge.api.flagsmith.com/api/v1/` +- `FLAGSMITH_ON_FLAGSMITH_API_KEY`: The Flagsmith environment key we use to manage features - [Flagsmith runs on Flagsmith](/deployment-self-hosting/core-configuration/running-flagsmith-on-flagsmith). +- `FLAGSMITH_ON_FLAGSMITH_API_URL`: The API URL which the Flagsmith client should communicate with. Flagsmith runs on Flagsmith. E.g. `https://edge.api.flagsmith.com/api/v1/`. If you are self-hosting and using your own Flagsmith instance to manage its own features, you would generally point this to the same domain name as your own Flagsmith instance. +- `DISABLE_ANALYTICS_FEATURES`: Disables any in-app analytics-related features: API Usage charts, flag analytics. E.g. `DISABLE_ANALYTICS_FEATURES=1`. +- `ENABLE_FLAG_EVALUATION_ANALYTICS`: Determines if the Flagsmith SDK should send usage analytics. If you want to enable Flag Analytics, set this. E.g. `ENABLE_FLAG_EVALUATION_ANALYTICS=1`. +- `PROXY_API_URL`: Proxies the API via this application. Set this to the hostname of the API being proxied. Proxies `/api/v1/` through to `PROXY_API_URL`. If you are using this, any setting to `FLAGSMITH_API_URL` will be ignored and the browser will use the frontend node server to send API requests. Do not prepend `api/v1/` - it will be added automatically. +- `GOOGLE_ANALYTICS_API_KEY`: Google Analytics key to track API usage. +- `CRISP_WEBSITE_ID`: Crisp Chat widget Website key. +- `FIRST_PROMOTER_ID`: First Promoter ID for checkout affiliates. +- `ALLOW_SIGNUPS`: **DEPRECATED** in favour of `PREVENT_SIGNUP` in the API. Determines whether to prevent manual signups without invites. Set it to any value to allow signups. +- `PREVENT_FORGOT_PASSWORD`: Determines whether to prevent forgot password functionality, useful for LDAP/SAML. Set it to any value to prevent forgot password functionality. +- `PREVENT_EMAIL_PASSWORD`: Disables email address signup, login and change email functionality. +- `ENABLE_MAINTENANCE_MODE`: Puts the site into maintenance mode. Set it to any value to enable maintenance. +- `AMPLITUDE_API_KEY`: The Amplitude key to use for behaviour tracking. +- `REO_API_KEY`: The Reo key to use for behaviour tracking. +- `MIXPANEL_API_KEY`: Mixpanel analytics key to use for behaviour tracking. +- `SENTRY_API_KEY`: Sentry key for error reporting. +- `ALBACROSS_CLIENT_ID`: Albacross client ID key for behaviour tracking. +- `BASE_URL`: Used for specifying a base URL path that's ignored during routing if serving from a subdirectory. +- `USE_SECURE_COOKIES`: Enable/disable the use of secure cookies. If deploying the frontend in a private network without a domain/SSL cert, disable secure cookies to ensure that session token is persisted. Default: true. +- `COOKIE_SAME_SITE`: Define the value of the SameSite attribute for the session token cookie set by the frontend. Further reading on this value is available [here](https://web.dev/articles/samesite-cookies-explained). Default: 'none'. \ No newline at end of file diff --git a/docs/docs/deployment-self-hosting/core-configuration/initial-setup.md b/docs/docs/deployment-self-hosting/core-configuration/initial-setup.md new file mode 100644 index 000000000000..11b4731c61f2 --- /dev/null +++ b/docs/docs/deployment-self-hosting/core-configuration/initial-setup.md @@ -0,0 +1,28 @@ +--- +title: "Initial Setup" +description: "How to initialise the instance and create the first superuser." +sidebar_position: 10 +--- + +The application is built using Django which comes with a handy set of admin pages available at `/admin/`. To access these, you'll need to create a superuser. This user can also be used to access the admin pages or the application itself if you have the frontend application running as well. This user can be created using the instructions below, depending on your installation: + +## Local Installation + +```bash +cd api +python manage.py createsuperuser +``` + +## Cloud Environments (e.g. Heroku, ECS) + +Once the app has been deployed, you can initialise your installation by accessing `/api/v1/users/config/init/`. This will show a page with a basic form to set up some initial data for the platform. Each of the parameters in the form are described below. + +| Parameter name | Description | +| --------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| Username | A unique username to give the installation superuser | +| Email | The email address to give the installation superuser | +| Password | The password to give the installation superuser | +| Site name | A human-readable name for the site, e.g. 'Flagsmith' | +| Site domain[^1] | The domain that the frontend of the site will be running on, e.g. app.flagsmith.com. This will be used for e.g. password reset emails. | + +[^1]: It is important that this is correct as it is used in the password reset emails to construct the reset link. \ No newline at end of file diff --git a/docs/docs/deployment-self-hosting/core-configuration/running-flagsmith-on-flagsmith.md b/docs/docs/deployment-self-hosting/core-configuration/running-flagsmith-on-flagsmith.md new file mode 100644 index 000000000000..d8a06b9d323f --- /dev/null +++ b/docs/docs/deployment-self-hosting/core-configuration/running-flagsmith-on-flagsmith.md @@ -0,0 +1,26 @@ +--- +title: "Running Flagsmith on Flagsmith" +description: "How to configure a self-hosted instance to use its own flags for feature management." +sidebar_position: 50 +--- + +Flagsmith uses Flagsmith to control features on the frontend dashboard. If you are self-hosting the platform, you will sometimes see features greyed out, or you may want to disable specific features, e.g. logging in via Google and GitHub. If you are using your own Flagsmith environment, then you will need to have a replica of our flags in order to control access to those features. + +## Setup Process + +To do this, first create a new project within your self-hosted Flagsmith application. This is the project that we will use to control the features of the self-hosted Flagsmith instance. We will then point the self-hosted frontend dashboard at this Flagsmith project in order to control what features show for your self-hosted Flagsmith instance. + +## Environment Variables + +Once you have created the project, you need to set the following [Frontend](https://github.com/Flagsmith/flagsmith-frontend) environment variables in order to configure this: + +- `FLAGSMITH_ON_FLAGSMITH_API_KEY` + - The Flagsmith Client-side Environment Key we use to manage features - Flagsmith runs on Flagsmith. This will be the API key for the project you created as instructed above. +- `ENABLE_FLAGSMITH_REALTIME` + - Determines whether the Flagsmith on Flagsmith SDK uses Realtime. +- `FLAGSMITH_ON_FLAGSMITH_API_URL` + - The API URL which the Flagsmith frontend dashboard should communicate with. This will most likely be the domain name of the Flagsmith API you are self-hosting: Flagsmith runs on Flagsmith. E.g. For our SaaS hosted platform, the variable is `https://edge.api.flagsmith.com/api/v1/`. For example, if you were running everything locally using the standard [docker-compose setup](https://github.com/Flagsmith/flagsmith-docker), you would use `http://localhost:8000/api/v1/` + +## Verification and Usage + +Once you have set this up, you should see the Flagsmith frontend requesting its own flags from the API (you can look in your browser developer console to see this). You can now start creating flags and overriding the default behaviours of the platform. For example, if you wanted to disable Google OAuth authentication, you would create a flag called `oauth_google` and disable it. \ No newline at end of file diff --git a/docs/docs/deployment-self-hosting/edge-proxy.md b/docs/docs/deployment-self-hosting/edge-proxy.md new file mode 100644 index 000000000000..eea0cfcea546 --- /dev/null +++ b/docs/docs/deployment-self-hosting/edge-proxy.md @@ -0,0 +1,72 @@ +--- +title: Edge Proxy +sidebar_position: 3 +--- + +The Flagsmith Edge Proxy is a service that you host yourself, which allows you to run an instance of the Flagsmith Engine close to your servers. If you are running Flagsmith within a server-side environment and you want to have very low latency flags, you have two options: + +1. Run the Edge Proxy within your own infrastructure and connect to it from your server-side SDKs +2. Run your server-side SDKs in [Local Evaluation Mode](/integrating-with-flagsmith/sdks/server-side#local-evaluation). + +The main benefit to running the Edge Proxy is that you reduce your polling requests against the Flagsmith API itself. + +The main benefit to running server-side SDKs in [Local Evaluation Mode](/integrating-with-flagsmith/sdks/server-side#local-evaluation) is that you get the lowest possible latency. + +## How Does It Work + +:::info + +The Edge Proxy has the same [caveats as running our SDK in Local Evaluation mode.](/integrating-with-flagsmith/sdks/server-side#local-evaluation). + +::: + +You can think of the Edge Proxy as a copy of our Python Server-Side SDK, running in [Local Evaluation Mode](/integrating-with-flagsmith/sdks/server-side#local-evaluation), with an API interface that is compatible with the Flagsmith SDK API. + +The Edge Proxy runs as a lightweight Docker container. It connects to the Flagsmith API (either powered by us at api.flagsmith.com or self-hosted by you) to get environment flags and segment rules. You can then point the Flagsmith SDKs to your Edge Proxy; it implements all the current SDK endpoints. This means you can serve a very large number of requests close to your infrastructure and users, at very low latency. Check out the [architecture below](#architecture). + +The Proxy also acts as a local cache, allowing you to make requests to the Proxy without hitting the core API. + +## Performance + +The Edge Proxy can currently serve ~2,000 requests per second (RPS) at a mean latency of ~7ms on an M1 MacBook Pro with a simple set of flags. Working with more complex environments with many segment rules will bring this RPS number down. + +It is stateless and hence close to perfectly scalable when deployed behind a load balancer. + +## Managing Traits + +There is one caveat with the Edge Proxy. Because it is entirely stateless, it is not able to persist trait data into any sort of data store. This means that you _have_ to provide the full complement of traits when requesting the flags for a particular identity. Our SDKs all provide relevant methods to achieve this. An example using `curl` would read as follows: + +```bash +curl -X "POST" "http://localhost:8000/api/v1/identities/?identifier=do_it_all_in_one_go_identity" \ + -H 'X-Environment-Key: n9fbf9h3v4fFgH3U3ngWhb' \ + -H 'Content-Type: application/json; charset=utf-8' \ + -d $'{ + "traits": [ + { + "trait_value": 123.5, + "trait_key": "my_trait_key" + }, + { + "trait_value": true, + "trait_key": "my_other_key" + } + ], + "identifier": "do_it_all_in_one_go_identity" +}' +``` + +Note that the Edge Proxy will currently _not_ send the trait data back to the Core API. + +## Deployment and Configuration + +Please see the [hosting documentation](/deployment-self-hosting/hosting-guides/docker). + +## Architecture + +The standard Flagsmith architecture: + +![Image](/img/edge-proxy-existing.svg) + +With the proxy added to the mix: + +![Image](/img/edge-proxy-proxy.svg) diff --git a/docs/docs/deployment/configuration/enterprise-edition.md b/docs/docs/deployment-self-hosting/enterprise-edition.md similarity index 55% rename from docs/docs/deployment/configuration/enterprise-edition.md rename to docs/docs/deployment-self-hosting/enterprise-edition.md index d9deb5c11673..e26adf26db1b 100644 --- a/docs/docs/deployment/configuration/enterprise-edition.md +++ b/docs/docs/deployment-self-hosting/enterprise-edition.md @@ -1,21 +1,19 @@ --- -sidebar_position: 4 +sidebar_position: 6 --- # Enterprise Edition -Flagsmith is also provided as an "Enterprise Edition" which has additional features and capabilities over the Open -Source product as detailed [in the Version Comparison page](/version-comparison). +Flagsmith is also provided as an "Enterprise Edition" which has additional features and capabilities over the Open Source product. For more information about the differences between versions, please contact our sales team. ## Deployment Options We currently support the following infrastructure platforms: - Kubernetes -- Redhat OpenShift -- Amazon Web Services (AWS) - via - [Amazon ECS](https://aws.amazon.com/ecs/?whats-new-cards.sort-by=item.additionalFields.postDateTime&whats-new-cards.sort-order=desc) -- Google Cloud Platform (GCP) - via [AppEngine](https://cloud.google.com/appengine) +- Red Hat OpenShift +- Amazon Web Services (AWS) - via [Amazon ECS](https://aws.amazon.com/ecs/?whats-new-cards.sort-by=item.additionalFields.postDateTime&whats-new-cards.sort-order=desc) +- Google Cloud Platform (GCP) - via [App Engine](https://cloud.google.com/appengine) - Azure - via [Container Instances](https://azure.microsoft.com/en-gb/services/container-instances/) If you require additional deployment options, please contact us. @@ -33,12 +31,11 @@ Please contact us for the relevant source code for these projects. ## Docker Image Repository -The Flagsmith API Enterprise Edition is hosted with a private Docker Hub repository. To access the Docker images in this -repository you will need to provide a Docker Hub account. Please get in touch if you need access to these repos. +The Flagsmith API Enterprise Edition is hosted with a private Docker Hub repository. To access the Docker images in this repository, you will need to provide a Docker Hub account. Please get in touch if you need access to these repos. We have 2 different Enterprise Edition Images. You can choose to use either image. -### "SaaS" image (flagsmith-private-cloud) +### "SaaS" Image (flagsmith-private-cloud) This image tracks our SaaS build and includes additional packages: @@ -46,16 +43,15 @@ This image tracks our SaaS build and includes additional packages: - Workflows (Change Requests and Flag Scheduling) - LDAP -This image also bundles the front end into the python application, meaning you don't need to run a separate front end -and back end. +This image also bundles the frontend into the Python application, meaning you don't need to run a separate frontend and backend. -### Enterprise Edition image (flagsmith-api-ee) +### Enterprise Edition Image (flagsmith-api-ee) This image includes additions from the SaaS image above: - Oracle Support - MySQL Support -- Appdynamics Integration +- AppDynamics Integration ## Environment Variables @@ -64,22 +60,20 @@ This image includes additions from the SaaS image above: | Variable | Example Value | Description | | ------------------------ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | **API_URL** | http://localhost:8888/api/v1 | The URL of the API Backend | -| **FLAGSMITH_CLIENT_API** | http://localhost:8888/api/v1 | This is where the features for the front end themselves are pulled from. Create a project within your backend and refer to flag names | +| **FLAGSMITH_CLIENT_API** | http://localhost:8888/api/v1 | This is where the features for the frontend themselves are pulled from. Create a project within your backend and refer to flag names | Env Var: **FLAGSMITH** Value example: 4vfqhypYjcPoGGu8ByrBaj Description: The `environment id` for the `FLAGSMITH_CLIENT_API` project above. ### Version Tags -The versions of all of our enterprise images track the versions of our Open Source version. You can view these tags -here: +The versions of all of our enterprise images track the versions of our Open Source version. You can view these tags here: [https://github.com/Flagsmith/flagsmith/tags](https://github.com/Flagsmith/flagsmith/tags) ## AppDynamics -The application supports the use of AppDynamics for monitoring purposes. In order to set up AppDynamics for your -environment follow the steps below: +The application supports the use of AppDynamics for monitoring purposes. In order to set up AppDynamics for your environment, follow the steps below: :::note @@ -88,15 +82,12 @@ There is a bug in the AppDynamics wizard that sets the value `ssl = (on)` which ::: 1. Set up your application in your AppDynamics dashboard using the "Getting Started Wizard - Python". -2. In the wizard you will need to select the "uWSGI with Emperor: Module Directive" when choosing a deployment method -3. On completing the wizard you will be provided with a configuration file like the one seen here in the - appdynamics.template.cfg provided, except with your application information. Make a copy of this information and - place it in a file. +2. In the wizard, you will need to select the "uWSGI with Emperor: Module Directive" when choosing a deployment method. +3. On completing the wizard, you will be provided with a configuration file like the one seen here in the appdynamics.template.cfg provided, except with your application information. Make a copy of this information and place it in a file. -### Running with docker +### Running with Docker -When running with traditional Docker you can use the code snippet below to inject the required information for running -App Dynamics +When running with traditional Docker, you can use the code snippet below to inject the required information for running AppDynamics: ```shell docker run -t \{image_name\} -v \{config_file_path\}:/etc/appdynamics.cfg -e APP_DYNAMICS=on @@ -104,13 +95,12 @@ docker run -t \{image_name\} -v \{config_file_path\}:/etc/appdynamics.cfg -e APP Replacing the values for: -- **_\{image_name\}_**: the tagged name of the docker image you are using -- **_\{config_file_path\}_**: the absolute path of the appdynamics.cfg file on your system +- **\{image_name\}**: the tagged name of the Docker image you are using +- **\{config_file_path\}**: the absolute path of the appdynamics.cfg file on your system -### Running with docker-compose +### Running with Docker Compose -When running with the `docker-compose.yml` file provided ensure the `APP_DYNAMICS` environment variable is set to `on` -as seen below: +When running with the `docker-compose.yml` file provided, ensure the `APP_DYNAMICS` environment variable is set to `on` as seen below: ```yaml api: @@ -123,57 +113,48 @@ api: - \{config_file_path\}:/etc/appdynamics.cfg ``` -Replacing the value for **_\{config_file_path\}_** with the absolute path of the appdynamics.cfg file on your system. +Replacing the value for **\{config_file_path\}** with the absolute path of the appdynamics.cfg file on your system. -Running the command below will build the docker image with all the AppDynamics config included +Running the command below will build the Docker image with all the AppDynamics config included: ```shell docker-compose -f docker-compose.yml build ``` -This image can then be run locally using the docker-compose `up` command as seen below +This image can then be run locally using the docker-compose `up` command as seen below: ```shell docker-compose -f docker-compose.yml up ``` -### Additional settings +### Additional Settings -If you need additional AppDynamics setup options you can find the other environment variables you can set +If you need additional AppDynamics setup options, you can find the other environment variables you can set [here](https://docs.appdynamics.com/display/PRO21/Python+Agent+Settings). ### Oracle Database -Flagsmith is compatible with the Oracle database engine by configuring a few environment variables correctly. Firstly, -you'll need to ensure that you have a value for `DJANGO_DB_ENGINE` set as `oracle`. Then you can set the remaining -database parameters (`DJANGO_DB_*`) as required. +Flagsmith is compatible with the Oracle database engine by configuring a few environment variables correctly. Firstly, you'll need to ensure that you have a value for `DJANGO_DB_ENGINE` set as `oracle`. Then you can set the remaining database parameters (`DJANGO_DB_*`) as required. #### Local Testing -The following sections detail how to run the application locally using the OracleDB Docker image. If you're looking to -run the application using an instance of OracleDB elsewhere, you just need to setup the environment variables correctly -as per the documentation. +The following sections detail how to run the application locally using the OracleDB Docker image. If you're looking to run the application using an instance of OracleDB elsewhere, you just need to set up the environment variables correctly as per the documentation. :::note **Prerequisites** -You will likely need to install the Oracle client on the machine running the Flagsmith API application. The instructions -to do so are [here](https://cx-oracle.readthedocs.io/en/latest/user_guide/installation.html). +You will likely need to install the Oracle client on the machine running the Flagsmith API application. The instructions to do so are [here](https://cx-oracle.readthedocs.io/en/latest/user_guide/installation.html). ::: -To run the application locally using Oracle (via Docker), you need to go through a registration with your docker hub -account to get access to the images. Go to https://hub.docker.com/_/oracle-database-enterprise-edition and register. -Once you've done that, you can run the Oracle database using docker, and we've created a docker-compose file to simplify -this. +To run the application locally using Oracle (via Docker), you need to go through a registration with your Docker Hub account to get access to the images. Go to https://hub.docker.com/_/oracle-database-enterprise-edition and register. Once you've done that, you can run the Oracle database using Docker, and we've created a docker-compose file to simplify this. ```bash docker-compose -f docker-compose.oracle.yml up db ``` -Once you have a database running, you'll need to set up the database and users correctly. This can be done with the -following processes. +Once you have a database running, you'll need to set up the database and users correctly. This can be done with the following processes. First, connect to the database itself: @@ -187,8 +168,7 @@ OR docker-compose exec db bash -c "source /home/oracle/.bashrc; sqlplus /nolog" ``` -Once connected, you'll need to run the following SQL commands. Note that these commands should be amended if you'd like -a different password / user combination. +Once connected, you'll need to run the following SQL commands. Note that these commands should be amended if you'd like a different password / user combination. ```sql conn sys as sysdba; @@ -200,21 +180,20 @@ GRANT EXECUTE ON SYS.DBMS_LOB TO oracle_user; GRANT EXECUTE ON SYS.DBMS_RANDOM TO oracle_user; ``` -## Load testing +## Load Testing ### JMeter -There are [JMeter](https://jmeter.apache.org/) tests avaiable in our public repo on GitHub: +There are [JMeter](https://jmeter.apache.org/) tests available in our public repo on GitHub: https://github.com/Flagsmith/flagsmith/tree/main/api/jmeter-tests ### wrk -We also recommend using [wrk](https://github.com/wg/wrk) for load testing the core SDK endpoints. Some examples of this -(make sure you update URL and environment keys!) +We also recommend using [wrk](https://github.com/wg/wrk) for load testing the core SDK endpoints. Some examples of this (make sure you update URL and environment keys!): ```bash -# Get flags endpoint +# Get flags endpoint wrk -t6 -c200 -d20s -H 'X-Environment-Key: iyiS5EDNDxMDuiFpHoiwzG' http://127.0.0.1:8000/api/v1/flags/ # Get flags for an identity @@ -223,6 +202,6 @@ wrk -t6 -c200 -d20s -H 'X-Environment-Key: iyiS5EDNDxMDuiFpHoiwzG' "http://127.0 ## Common Installation Issues and Fixes -### Front end build errors with npm ERR! errno 137 +### Frontend Build Errors with npm ERR! errno 137 This is an out of memory error. Start the container with more RAM. diff --git a/docs/docs/deployment-self-hosting/hosting-guides/_category_.json b/docs/docs/deployment-self-hosting/hosting-guides/_category_.json new file mode 100644 index 000000000000..49dc9172439b --- /dev/null +++ b/docs/docs/deployment-self-hosting/hosting-guides/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Hosting Guides", + "collapsed": true, + "position": 1 +} \ No newline at end of file diff --git a/docs/docs/deployment-self-hosting/hosting-guides/cloud-providers/_category_.json b/docs/docs/deployment-self-hosting/hosting-guides/cloud-providers/_category_.json new file mode 100644 index 000000000000..5ee3bb43aff0 --- /dev/null +++ b/docs/docs/deployment-self-hosting/hosting-guides/cloud-providers/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Cloud Providers", + "collapsed": true, + "position": 3 +} \ No newline at end of file diff --git a/docs/docs/deployment/hosting/aptible.md b/docs/docs/deployment-self-hosting/hosting-guides/cloud-providers/aptible.md similarity index 56% rename from docs/docs/deployment/hosting/aptible.md rename to docs/docs/deployment-self-hosting/hosting-guides/cloud-providers/aptible.md index 92c0c135dd52..f4fee0ea91c4 100644 --- a/docs/docs/deployment/hosting/aptible.md +++ b/docs/docs/deployment-self-hosting/hosting-guides/cloud-providers/aptible.md @@ -1,7 +1,10 @@ --- title: Aptible +sidebar_position: 30 --- +Thinking about running Flagsmith on Aptible? This guide will walk you through the steps to get your Flagsmith instance up and running smoothly on the Aptible platform. We'll cover the key configuration tweaks you'll need, how to set up your environment, and some handy tips to make sure everything works as expected. Let's get started! + ## Prerequisites The options and health check routes described in this document are available from Flagsmith 2.130.0. @@ -10,16 +13,12 @@ The options and health check routes described in this document are available fro Running Flagsmith on Aptible requires some configuration tweaks because of how Aptible's application lifecycle works: -- Don't wait for the database to be available before the Flagsmith API starts. You can do this by setting the - `SKIP_WAIT_FOR_DB` environment variable. -- Add `containers` as an allowed host to comply with Aptible's - [strict health checks](https://www.aptible.com/docs/core-concepts/apps/connecting-to-apps/app-endpoints/https-endpoints/health-checks#strict-health-checks). +- Don't wait for the database to be available before the Flagsmith API starts. You can do this by setting the `SKIP_WAIT_FOR_DB` environment variable. +- Add `containers` as an allowed host to comply with Aptible's [strict health checks](https://www.aptible.com/docs/core-concepts/apps/connecting-to-apps/app-endpoints/https-endpoints/health-checks#strict-health-checks). - Use the `before_release` tasks from `.aptible.yml` to run database migrations - Use a Procfile to only start the API and not perform database migrations on startup -This configuration can be applied by adding the Procfile and `.aptible.yml` configuration files to a -[Docker image](https://www.aptible.com/docs/core-concepts/apps/deploying-apps/image/deploying-with-docker-image/overview#how-do-i-deploy-from-docker-image) -that you build starting from a Flagsmith base image: +This configuration can be applied by adding the Procfile and `.aptible.yml` configuration files to a [Docker image](https://www.aptible.com/docs/core-concepts/apps/deploying-apps/image/deploying-with-docker-image/overview#how-do-i-deploy-from-docker-image) that you build starting from a Flagsmith base image: ```text title="Procfile" cmd: serve @@ -48,8 +47,9 @@ ADD .aptible.yml /.aptible/.aptible.yml USER nobody ``` -Before deploying, set the environment variables for your database URL and allowed hosts from the Aptible dashboard, or -using the Aptible CLI: +## Environment Variables + +Before deploying, set the environment variables for your database URL and allowed hosts from the Aptible dashboard, or using the Aptible CLI: ```shell aptible config:set --app flagsmith \ @@ -59,8 +59,7 @@ aptible config:set --app flagsmith \ ## Deployment -After your image is built and pushed to a container registry that Aptible can access, you can deploy it using the -Aptible CLI as you would any other application: +After your image is built and pushed to a container registry that Aptible can access, you can deploy it using the Aptible CLI as you would any other application: ```shell aptible deploy --app flagsmith --docker-image example/my-flagsmith-aptible-image @@ -70,6 +69,4 @@ Once Flagsmith is running in Aptible, make sure to create the first admin user b ## Limitations -The steps described in this document do not deploy the -[asynchronous task processor](/deployment/configuration/task-processor), which may affect performance in production -workloads. +The steps described in this document do not deploy the [asynchronous task processor](/deployment-self-hosting/scaling-and-performance/asynchronous-task-processor), which may affect performance in production workloads. diff --git a/docs/docs/deployment/hosting/aws.md b/docs/docs/deployment-self-hosting/hosting-guides/cloud-providers/aws.md similarity index 52% rename from docs/docs/deployment/hosting/aws.md rename to docs/docs/deployment-self-hosting/hosting-guides/cloud-providers/aws.md index 9487570e94ee..a63c4a677896 100644 --- a/docs/docs/deployment/hosting/aws.md +++ b/docs/docs/deployment-self-hosting/hosting-guides/cloud-providers/aws.md @@ -2,7 +2,7 @@ title: Deploying Flagsmith on AWS description: Getting Started with Flagsmith on AWS sidebar_label: AWS -sidebar_position: 70 +sidebar_position: 10 --- ## Overview @@ -10,11 +10,10 @@ sidebar_position: 70 We recommend running Flagsmith on AWS using the following AWS services: - ECS/Fargate for running the Docker image -- RDS/Aurora/Postgres for the database +- RDS/Aurora/PostgreSQL for the database - Application Load Balancer to distribute traffic -We have [Pulumi](https://www.pulumi.com/) scripts available for AWS deployments. Please get in touch if these are of -interest. +We have [Pulumi](https://www.pulumi.com/) scripts available for AWS deployments. Please get in touch if these are of interest. ## AWS Infrastructure Architecture @@ -22,23 +21,17 @@ interest. ## ECS -Unless you have specific requirements, we recommend running the -[unified Docker image](https://hub.docker.com/repository/docker/flagsmith/flagsmith). +Unless you have specific requirements, we recommend running the [unified Docker image](https://hub.docker.com/repository/docker/flagsmith/flagsmith). -It's best to study our [docker-compose file](https://github.com/Flagsmith/flagsmith/blob/main/docker-compose.yml) in -order to set up the base environment variables. Further environment variables are -[described here](locally-api.md#environment-variables). +It's best to study our [docker-compose file](https://github.com/Flagsmith/flagsmith/blob/main/docker-compose.yml) in order to set up the base environment variables. Further environment variables are [described here](/deployment-self-hosting/core-configuration/environment-variables). -Run a single ECS service with at least two Fargate instances running for failover. For more info on Fargate sizes, see -our [scaling page](/deployment/configuration/sizing-and-scaling). +Run a single ECS service with at least two Fargate instances running for failover. For more info on Fargate sizes, see our [scaling page](/deployment-self-hosting/scaling-and-performance/sizing-and-scaling). -If you are using health-checks, make sure to use `/health` as the health-check endpoint. +If you are using health checks, make sure to use `/health` as the health-check endpoint. ## RDS/Aurora -We run in production on PostgreSQL version `11`; Aurora release `3.x`. When starting for the first time, the application -will create that database schema automatically. Schema upgrades will also happen seamlessly during application server -upgrades. +We run in production on PostgreSQL version `11`; Aurora release `3.x`. When starting for the first time, the application will create that database schema automatically. Schema upgrades will also happen seamlessly during application server upgrades. ## Application Load Balancer diff --git a/docs/docs/deployment-self-hosting/hosting-guides/cloud-providers/google-cloud.md b/docs/docs/deployment-self-hosting/hosting-guides/cloud-providers/google-cloud.md new file mode 100644 index 000000000000..fd1811f07acf --- /dev/null +++ b/docs/docs/deployment-self-hosting/hosting-guides/cloud-providers/google-cloud.md @@ -0,0 +1,26 @@ +--- +title: Deploying Flagsmith on Google Cloud +sidebar_label: Google Cloud +sidebar_position: 20 +--- + +## Overview + +We recommend running Flagsmith on Google Cloud Platform using the following services: + +- [Cloud Run](https://cloud.google.com/run) for the application server +- [Cloud SQL/PostgreSQL](https://cloud.google.com/sql/postgresql) for the database + +## Cloud Run + +Unless you have specific requirements, we recommend running the [unified Docker image](https://hub.docker.com/repository/docker/flagsmith/flagsmith). + +It's best to study our [docker-compose file](https://github.com/Flagsmith/flagsmith/blob/main/docker-compose.yml) in order to set up the base environment variables. Further environment variables are [described here](/deployment-self-hosting/core-configuration/environment-variables). + +Run a single Cloud Run service with at least two container instances running for failover. For more info on sizing, see our [scaling page](/deployment-self-hosting/scaling-and-performance/sizing-and-scaling). We recommend running with at least [2 minimum instances](https://cloud.google.com/run/docs/configuring/min-instances) to avoid cold starts, particularly in order to serve low-latency requests to the SDKs. + +If you are using health checks, make sure to use `/health` as the health-check endpoint for both the API and the frontend. + +## Cloud SQL/PostgreSQL + +We support PostgreSQL versions `11+`. Our SaaS platform runs in production on PostgreSQL version `11`. When starting for the first time, the application will create that database schema automatically. Schema upgrades will also happen seamlessly during application server upgrades. diff --git a/docs/docs/deployment-self-hosting/hosting-guides/docker.md b/docs/docs/deployment-self-hosting/hosting-guides/docker.md new file mode 100644 index 000000000000..4010f66ed928 --- /dev/null +++ b/docs/docs/deployment-self-hosting/hosting-guides/docker.md @@ -0,0 +1,65 @@ +--- +title: Docker +sidebar_label: Docker +description: Getting Started with Flagsmith on Docker +sidebar_position: 1 +--- + +This guide explains how to run a full Flagsmith environment on your local machine using Docker. This is useful for development, testing, or evaluating Flagsmith's features. + +You can use Docker to set up an entire [Flagsmith Feature Flag](https://www.flagsmith.com) environment locally: + +```bash +curl -o docker-compose.yml https://raw.githubusercontent.com/Flagsmith/flagsmith/main/docker-compose.yml +docker-compose -f docker-compose.yml up +``` + +Wait for the images to download and run, then visit `http://localhost:8000/`. As a first step, you will need to create a new account at [http://localhost:8000/signup](http://localhost:8000/signup) + +## Environment Variables + +As well as the Environment Variables specified in the [API](/deployment-self-hosting/core-configuration/environment-variables#api-environment-variables) and [Frontend](/deployment-self-hosting/core-configuration/environment-variables#frontend-environment-variables), you can also specify the following: + +- `GUNICORN_CMD_ARGS`: Gunicorn command line arguments. Overrides Flagsmith's defaults. See [Gunicorn documentation](https://docs.gunicorn.org/en/stable/settings.html) for reference. +- `GUNICORN_WORKERS`: The number of [Gunicorn Workers](https://docs.gunicorn.org/en/stable/settings.html#workers) that are created +- `GUNICORN_THREADS`: The number of [Gunicorn Threads per Worker](https://docs.gunicorn.org/en/stable/settings.html#threads) +- `GUNICORN_TIMEOUT`: The number of seconds before the [Gunicorn times out](https://docs.gunicorn.org/en/stable/settings.html#timeout) +- `ACCESS_LOG_FORMAT`: Message format for Gunicorn's access log. See [variable details](https://docs.gunicorn.org/en/stable/settings.html#access-log-format) to define your own format. +- `ACCESS_LOG_LOCATION`: The location to write access logs to. If set to `-`, the logs will be sent to `stdout` + +## Platform Architectures + +Our Docker images are built against the following CPU architectures: + +- `amd64` +- `linux/arm64` +- `linux/arm/v7` + +## Architecture + +The docker-compose file runs the following containers: + +### Frontend Dashboard and REST API Combined - Port 8000 + +The web user interface allows you to create accounts and manage your flags. The frontend is written in Node.js and React. + +The web user interface communicates via REST to the API that powers the application. The SDK clients also connect to this API. The API is written in Django and the Django REST Framework. + +Once you have created an account and some flags, you can then start using the API with one of the [Flagsmith Client SDKs](https://github.com/Flagsmith?q=client&type=&language=). You will need to override the API endpoint for each SDK to point to [http://localhost:8000/api/v1/](http://localhost:8000/api/v1/). + +You can access the Django Admin console to get CRUD access to some of the core tables within the API. You will need to create a superuser account first with the following command: + +```bash +# Make sure you are in the root directory of this repository +docker-compose run --rm --entrypoint "python manage.py createsuperuser" api +``` + +You can then access the admin dashboard at [http://localhost:8000/admin/](http://localhost:8000/admin/) + +### PostgreSQL Database + +The REST API stores all its data within a PostgreSQL database. Schema changes will be carried out automatically when upgrading using Django Migrations. + +## Access Flagsmith Remotely + +You will need to either open ports into your Docker host or set up a reverse proxy to access the two Flagsmith services (dashboard and API). You will also need to configure the dashboard environment variable `API_URL`, which tells the dashboard where the REST API is located. diff --git a/docs/docs/deployment/hosting/kubernetes.md b/docs/docs/deployment-self-hosting/hosting-guides/kubernetes-openshift.md similarity index 79% rename from docs/docs/deployment/hosting/kubernetes.md rename to docs/docs/deployment-self-hosting/hosting-guides/kubernetes-openshift.md index f8a14de1130d..258a394ddcc8 100644 --- a/docs/docs/deployment/hosting/kubernetes.md +++ b/docs/docs/deployment-self-hosting/hosting-guides/kubernetes-openshift.md @@ -1,14 +1,32 @@ --- -title: Kubernetes -sidebar_position: 50 +title: Kubernetes and OpenShift +description: "Deploying Flagsmith on Kubernetes and OpenShift using our Helm charts." +sidebar_position: 2 --- ## Overview -Check out our [Kubernetes Chart Repository on GitHub](https://github.com/Flagsmith/flagsmith-charts) and our -[published Helm Charts](https://flagsmith.github.io/flagsmith-charts/). +This guide covers deploying Flagsmith on Kubernetes or OpenShift using our Helm charts. -## Quick-start +Check out our [Kubernetes Chart Repository on GitHub](https://github.com/Flagsmith/flagsmith-charts) and our [published Helm Charts](https://flagsmith.github.io/flagsmith-charts/). + +### OpenShift + +:::tip + +Support for our OpenShift Operator is only available with an [Enterprise plan](https://flagsmith.com/pricing/). + +::: + +Flagsmith runs on the OpenShift platform via our Helm charts. The rest of this guide is applicable for both Kubernetes and OpenShift. + +--- + +## Quick Start Deployment + +### Basic Installation + +This is the simplest way to get Flagsmith running for testing or development. ```bash helm repo add flagsmith https://flagsmith.github.io/flagsmith-charts/ @@ -16,31 +34,28 @@ helm install -n flagsmith --create-namespace flagsmith flagsmith/flagsmith kubectl -n flagsmith port-forward svc/flagsmith-frontend 8080:8080 ``` -Then view `http://localhost:8080` in a browser. This will install the chart using default options, in a new namespace -`flagsmith`. +Then view `http://localhost:8080` in a browser. This will install the chart using default options in a new namespace called `flagsmith`. + +### Using Custom Values (Optional) -Refer to the chart's default -[`values.yaml`](https://github.com/Flagsmith/flagsmith-charts/blob/main/charts/flagsmith/values.yaml) file to learn -which values are expected by the chart. You can use it as a reference for building your own values file: +Refer to the chart's default [`values.yaml`](https://github.com/Flagsmith/flagsmith-charts/blob/main/charts/flagsmith/values.yaml) file to learn which values are expected by the chart. You can use it as a reference for building your own values file: ```bash wget https://raw.githubusercontent.com/Flagsmith/flagsmith-charts/main/charts/flagsmith/values.yaml helm install -n flagsmith --create-namespace flagsmith flagsmith/flagsmith -f values.yaml ``` -We would suggest only doing this when running the platform locally, and recommend reading the Helm docs for -[installation](https://helm.sh/docs/intro/using_helm/#helm-install-installing-a-package), -[upgrading](https://helm.sh/docs/helm/helm_upgrade/) and -[values](https://helm.sh/docs/chart_template_guide/values_files/) for further information. +:::note -## Configuration +We would suggest only doing this when running the platform locally, and recommend reading the Helm docs for [installation](https://helm.sh/docs/intro/using_helm/#helm-install-installing-a-package), [upgrading](https://helm.sh/docs/helm/helm_upgrade/) and [values](https://helm.sh/docs/chart_template_guide/values_files/) for further information. -### Ingress configuration +::: -The above is a quick way of gaining access to Flagsmith, but in many cases you will need to configure ingress to work -with an ingress controller. +--- -#### Port forwarding +The above is a quick way of gaining access to Flagsmith, but in many cases you will need to configure ingress to work with an ingress controller. + +### Port forwarding In a terminal, run: @@ -50,15 +65,13 @@ kubectl -n [flagsmith-namespace] port-forward svc/[flagsmith-release-name]-front Then access `http://localhost:8080` in a browser. -#### In a cluster that has an ingress controller, using the frontend proxy +### In a cluster that has an ingress controller, using the frontend proxy -In this configuration, api requests are proxied by the frontend. This is simpler to configure, but introduces some -latency. +In this configuration, API requests are proxied by the frontend. This is simpler to configure but introduces some latency. -Set the following values for flagsmith, with changes as needed to accommodate your ingress controller, and any -associated DNS changes. +Set the following values for Flagsmith, with changes as needed to accommodate your ingress controller and any associated DNS changes. -Eg in the `charts/flagsmith/values.yaml` file: +For example, in the `charts/flagsmith/values.yaml` file: ```yaml ingress: @@ -70,13 +83,11 @@ ingress: Then, once any out-of-cluster DNS or CDN changes have been applied, access `https://flagsmith.[MYDOMAIN]` in a browser. -#### In a cluster that has an ingress controller, using separate ingresses for frontend and api +### In a cluster that has an ingress controller, using separate ingresses for frontend and API -Set the following values for flagsmith, with changes as needed to accommodate your ingress controller, and any -associated DNS changes. Also, set the `FLAGSMITH_API_URL` env-var such that the URL is reachable from a browser -accessing the frontend. +Set the following values for Flagsmith, with changes as needed to accommodate your ingress controller and any associated DNS changes. Also, set the `FLAGSMITH_API_URL` environment variable such that the URL is reachable from a browser accessing the frontend. -Eg in the `charts/flagsmith/values.yaml` file: +For example, in the `charts/flagsmith/values.yaml` file: ```yaml ingress: @@ -102,13 +113,13 @@ frontend: Then, once any out-of-cluster DNS or CDN changes have been applied, access `https://flagsmith.[MYDOMAIN]` in a browser. -#### Minikube ingress +### Minikube ingress (See [https://kubernetes.io/docs/tasks/access-application-cluster/ingress-minikube/] for more details.) If using minikube, enable ingress with `minikube addons enable ingress`. -Then set the following values for flagsmith in the `charts/flagsmith/values.yaml` file: +Then set the following values for Flagsmith in the `charts/flagsmith/values.yaml` file: ```yaml ingress: @@ -120,7 +131,7 @@ ingress: and apply. This will create two ingress resources. -Run `minikube ip`. Set this ip and `flagsmith.local` in your `/etc/hosts`, eg: +Run `minikube ip`. Set this IP and `flagsmith.local` in your `/etc/hosts`, for example: ```txt 192.168.99.99 flagsmith.local @@ -130,19 +141,15 @@ Then access `http://flagsmith.local` in a browser. ### Provided Database configuration -By default, the chart creates its own PostgreSQL server within the cluster, referencing -[https://github.com/helm/charts/tree/master/stable/postgresql](https://github.com/helm/charts/tree/master/stable/postgresql) -for the service. +By default, the chart creates its own PostgreSQL server within the cluster, referencing [https://github.com/helm/charts/tree/master/stable/postgresql](https://github.com/helm/charts/tree/master/stable/postgresql) for the service. :::caution -We recommend running an externally managed database in production, either by deploying your own Postgres instance in -your cluster, or using a service like [AWS RDS](https://aws.amazon.com/rds/postgresql/). +We recommend running an externally managed database in production, either by deploying your own PostgreSQL instance in your cluster or using a service like [AWS RDS](https://aws.amazon.com/rds/postgresql/). ::: -You can provide configuration options to the postgres database by modifying the values, for example the below changes -the max_connections in the `charts/flagsmith/values.yaml` file: +You can provide configuration options to the PostgreSQL database by modifying the values. For example, the below changes the max_connections in the `charts/flagsmith/values.yaml` file: ```yaml postgresql: @@ -154,12 +161,11 @@ postgresql: ### External Database configuration -To connect the Flagsmith API to an external PostgreSQL server set the values under `databaseExternal`, eg in the -`charts/flagsmith/values.yaml` file: +To connect the Flagsmith API to an external PostgreSQL server, set the values under `databaseExternal`. For example, in the `charts/flagsmith/values.yaml` file: ```yaml postgresql: - enabled: false # turn off the chart-managed postgres + enabled: false # turn off the chart-managed PostgreSQL databaseExternal: enabled: true @@ -181,10 +187,9 @@ databaseExternal: :::caution -Due to a [bug](https://bugs.python.org/issue33342) in python's urllib module, passwords containing `[`, `]` or `#` chars -must be urlencoded. +Due to a [bug](https://bugs.python.org/issue33342) in Python's urllib module, passwords containing `[`, `]` or `#` characters must be URL-encoded. -e.g. +For example: `postgres://user:pass[word@localhost/flagsmith` @@ -198,18 +203,13 @@ should be provided as: :::caution -It's important to define a [`secretKey`](https://docs.djangoproject.com/en/4.2/ref/settings/#std-setting-SECRET_KEY) -value in your helm chart when running in Kubernetes. Use a password manager to generate a random hash and set this so -that all the API nodes are running with an identical `DJANGO_SECRET_KEY`. +It's important to define a [`secretKey`](https://docs.djangoproject.com/en/4.2/ref/settings/#std-setting-SECRET_KEY) value in your Helm chart when running in Kubernetes. Use a password manager to generate a random hash and set this so that all the API nodes are running with an identical `DJANGO_SECRET_KEY`. -If you are using our Helm charts and don't provide a `secretKey`, one will be generated for you and shared across the -running pods, but this will change upon redeployment, which you probably don't want to happen. +If you are using our Helm charts and don't provide a `secretKey`, one will be generated for you and shared across the running pods, but this will change upon redeployment, which you probably don't want to happen. ::: -The chart handles most environment variables required, but see the -[API readme](/deployment/hosting/locally-api#environment-variables) for all available configuration options. These can -be set using `api.extraEnv`, eg in the `charts/flagsmith/values.yaml` file: +The chart handles most environment variables required, but see the [API environment variables](/deployment-self-hosting/core-configuration/environment-variables#api-environment-variables) for all available configuration options. These can be set using `api.extraEnv`. For example, in the `charts/flagsmith/values.yaml` file: ```yaml api: @@ -248,7 +248,7 @@ For each of the deployments, you can set `deploymentStrategy`. By default this i Kubernetes behaviour, but you can set this to an object to adjust this. See [https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy]. -Eg in the `charts/flagsmith/values.yaml` file: +For example, in the `charts/flagsmith/values.yaml` file: ```yaml api: @@ -261,13 +261,11 @@ api: ### PgBouncer -By default, Flagsmith connects directly to the database - either in-cluster, or external. Can enable PgBouncer with -`pgbouncer.enabled: true` to have Flagsmith connect to PgBouncer, and PgBouncer connect to the database. +By default, Flagsmith connects directly to the database - either in-cluster or external. You can enable PgBouncer with `pgbouncer.enabled: true` to have Flagsmith connect to PgBouncer, and zPgBouncer connect to the database. ### All-in-one Docker image -The Docker image at [https://hub.docker.com/r/flagsmith/flagsmith/] contains both the API and the frontend. To make use -of this, set the following values: +The Docker image at [https://hub.docker.com/r/flagsmith/flagsmith/] contains both the API and the frontend. To make use of this, set the following values: ```yaml api: @@ -277,22 +275,20 @@ api: separateApiAndFrontend: false ``` -This switches off the Kubernetes deployment for the frontend. However, the ingress and service are retained, but all -requests are handled by the API deployment. +This switches off the Kubernetes deployment for the frontend. However, the ingress and service are retained, but all requests are handled by the API deployment. ### InfluxDB -By default, Flagsmith uses Postgres to store time series data. You can alternatively use Influx to track: +By default, Flagsmith uses PostgreSQL to store time series data. You can alternatively use InfluxDB to track: - SDK API traffic - SDK Flag Evaluations -[You need to perform some additional steps to configure InfluxDB.](/deployment#time-series-data-via-influxdb). +[You need to perform some additional steps to configure InfluxDB.](/deployment-self-hosting/scaling-and-performance/using-influxdb-for-analytics). ### Task Processor -The task processor itself is documented [here](/deployment/configuration/task-processor). See the table below for the -values to set to configure the task processor using the helm chart. +The task processor itself is documented [here](/deployment-self-hosting/scaling-and-performance/asynchronous-task-processor). See the table below for the values to set to configure the task processor using the Helm chart. ## Chart Values @@ -300,16 +296,16 @@ The following table lists the configurable parameters of the chart and their def | Parameter | Description | Default | | -------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------ | -| `api.image.repository` | docker image repository for flagsmith api | `flagsmith/flagsmith-api` | -| `api.image.tag` | docker image tag for flagsmith api | appVersion | +| `api.image.repository` | Docker image repository for Flagsmith API | `flagsmith/flagsmith-api` | +| `api.image.tag` | Docker image tag for Flagsmith API | appVersion | | `api.image.imagePullPolicy` | | `IfNotPresent` | | `api.image.imagePullSecrets` | | `[]` | -| `api.separateApiAndFrontend` | Set to false if using flagsmith/flagsmith image for the api | `true` | -| `api.replicacount` | number of replicas for the flagsmith api, `null` to unset | 1 | +| `api.separateApiAndFrontend` | Set to false if using flagsmith/flagsmith image for the API | `true` | +| `api.replicacount` | Number of replicas for the Flagsmith API, `null` to unset | 1 | | `api.deploymentStrategy` | See "Deployment strategy" above | | -| `api.resources` | resources per pod for the flagsmith api | `{}` | -| `api.podLabels` | additional labels to apply to pods for the flagsmith api | `{}` | -| `api.extraEnv` | extra environment variables to set for the flagsmith api | `{}` | +| `api.resources` | Resources per pod for the Flagsmith API | `{}` | +| `api.podLabels` | Additional labels to apply to pods for the Flagsmith API | `{}` | +| `api.extraEnv` | Extra environment variables to set for the Flagsmith API | `{}` | | `api.secretKey` | See `secretKey` docs above | `null` | | `api.secretKeyFromExistingSecret.enabled` | Set to true to use a secret key stored in an existing k8s secret | `false` | | `api.secretKeyFromExistingSecret.name` | The name of the secret key k8s secret | `null` | @@ -318,7 +314,7 @@ The following table lists the configurable parameters of the chart and their def | `api.tolerations` | | `[]` | | `api.affinity` | | `{}` | | `api.podSecurityContext` | | `{}` | -| `api.defaultPodSecurityContext.enabled` | whether to use the default security context | `true` | +| `api.defaultPodSecurityContext.enabled` | Whether to use the default security context | `true` | | `api.livenessProbe.failureThreshold` | | 5 | | `api.livenessProbe.initialDelaySeconds` | | 10 | | `api.livenessProbe.periodSeconds` | | 10 | @@ -334,21 +330,21 @@ The following table lists the configurable parameters of the chart and their def | `api.dbWaiter.image.tag` | | `latest` | | `api.dbWaiter.image.imagePullPolicy` | | `IfNotPresent` | | `api.dbWaiter.timeoutSeconds` | Time before init container will retry | 30 | -| `frontend.enabled` | Whether the flagsmith frontend is enabled | `true` | -| `frontend.image.repository` | docker image repository for flagsmith frontend | `flagsmith/flagsmith-frontend` | -| `frontend.image.tag` | docker image tag for flagsmith frontend | appVersion | +| `frontend.enabled` | Whether the Flagsmith frontend is enabled | `true` | +| `frontend.image.repository` | Docker image repository for Flagsmith frontend | `flagsmith/flagsmith-frontend` | +| `frontend.image.tag` | Docker image tag for Flagsmith frontend | appVersion | | `frontend.image.imagePullPolicy` | | `IfNotPresent` | | `frontend.image.imagePullSecrets` | | `[]` | -| `frontend.replicacount` | number of replicas for the flagsmith frontend, `null` to unset | 1 | +| `frontend.replicacount` | Number of replicas for the Flagsmith frontend, `null` to unset | 1 | | `frontend.deploymentStrategy` | See "Deployment strategy" above | | -| `frontend.resources` | resources per pod for the flagsmith frontend | `{}` | -| `frontend.apiProxy.enabled` | proxy API requests to the API service within the cluster | `true` | -| `frontend.extraEnv` | extra environment variables to set for the flagsmith frontend | `{}` | +| `frontend.resources` | Resources per pod for the Flagsmith frontend | `{}` | +| `frontend.apiProxy.enabled` | Proxy API requests to the API service within the cluster | `true` | +| `frontend.extraEnv` | Extra environment variables to set for the Flagsmith frontend | `{}` | | `frontend.nodeSelector` | | `{}` | | `frontend.tolerations` | | `[]` | | `frontend.affinity` | | `{}` | | `api.podSecurityContext` | | `{}` | -| `api.defaultPodSecurityContext.enabled` | whether to use the default security context | `true` | +| `api.defaultPodSecurityContext.enabled` | Whether to use the default security context | `true` | | `frontend.livenessProbe.failureThreshold` | | 20 | | `frontend.livenessProbe.initialDelaySeconds` | | 20 | | `frontend.livenessProbe.periodSeconds` | | 10 | @@ -386,27 +382,27 @@ The following table lists the configurable parameters of the chart and their def | `taskProcessor.tolerations` | | `[]` | | `taskProcessor.affinity` | | `{}` | | `taskProcessor.podSecurityContext` | | `{}` | -| `taskProcessor.defaultPodSecurityContext.enabled` | whether to use the default security context | `true` | -| `devPostgresql.enabled` | if `true`, creates in-cluster PostgreSQL database | `true` | -| `postgresql.serviceAccount.enabled` | creates a serviceaccount for the postgres pod | `true` | +| `taskProcessor.defaultPodSecurityContext.enabled` | Whether to use the default security context | `true` | +| `devPostgresql.enabled` | If `true`, creates in-cluster PostgreSQL database | `true` | +| `postgresql.serviceAccount.enabled` | Creates a service account for the PostgreSQL pod | `true` | | `nameOverride` | | `flagsmith-postgres` | | `postgresqlDatabase` | | `flagsmith` | | `postgresqlUsername` | | `postgres` | | `postgresqlPassword` | | `flagsmith` | -| `databaseExternal.enabled` | use an external database. Specify database URL, or all parts. | `false` | -| `databaseExternal.url` | See [schema](https://github.com/jazzband/dj-database-url#url-schema). | | -| `databaseExternal.type` | Note: Only Postgres is fully supported by default images. [Other databases](https://github.com/jazzband/dj-database-url#supported-databases) not guaranteed to work. | `postgres` | +| `databaseExternal.enabled` | Use an external database. Specify database URL, or all parts. | `false` | +| `databaseExternal.url` | See [schema](https://github.com/kennethreitz/dj-database-url#url-schema). | | +| `databaseExternal.type` | Note: Only PostgreSQL supported by default images. | `postgres` | | `databaseExternal.port` | | 5432 | | `databaseExternal.database` | Name of the database within the server | | | `databaseExternal.username` | | | | `databaseExternal.password` | | | | `databaseExternal.urlFromExistingSecret.enabled` | Reference an existing secret containing the database URL. | | | `databaseExternal.urlFromExistingSecret.name` | Name of referenced secret | | -| `databaseExternal.urlFromExistingSecret.key` | Key within the referenced secrt to use | | +| `databaseExternal.urlFromExistingSecret.key` | Key within the referenced secret to use | | | `influxdb2.enabled` | | `true` | | `influxdb2.nameOverride` | | `influxdb` | -| `influxdb2.image.repository` | docker image repository for influxdb | `quay.io/influxdb/influxdb` | -| `influxdb2.image.tag` | docker image tag for influxdb | `v2.0.2` | +| `influxdb2.image.repository` | Docker image repository for InfluxDB | `quay.io/influxdb/influxdb` | +| `influxdb2.image.tag` | Docker image tag for InfluxDB | `v2.0.2` | | `influxdb2.image.imagePullPolicy` | | `IfNotPresent` | | `influxdb2.image.imagePullSecrets` | | `[]` | | `influxdb2.adminUser.organization` | | `influxdata` | @@ -415,7 +411,7 @@ The following table lists the configurable parameters of the chart and their def | `influxdb2.adminUser.password` | | randomly generated | | `influxdb2.adminUser.token` | | randomly generated | | `influxdb2.persistence.enabled` | | `false` | -| `influxdb.resources` | resources per pod for the influxdb | `{}` | +| `influxdb.resources` | Resources per pod for the InfluxDB | `{}` | | `influxdb.nodeSelector` | | `{}` | | `influxdb.tolerations` | | `[]` | | `influxdb.affinity` | | `{}` | @@ -432,7 +428,7 @@ The following table lists the configurable parameters of the chart and their def | `pgbouncer.image.tag` | | `1.16.0` | | `pgbouncer.image.imagePullPolicy` | | `IfNotPresent` | | `pgbouncer.image.imagePullSecrets` | | `[]` | -| `pgbouncer.replicaCount` | number of replicas for pgbouncer, `null` to unset | 1 | +| `pgbouncer.replicaCount` | Number of replicas for PgBouncer, `null` to unset | 1 | | `pgbouncer.deploymentStrategy` | See "Deployment strategy" above | | | `pgbouncer.podAnnotations` | | `{}` | | `pgbouncer.resources` | | `{}` | @@ -475,29 +471,27 @@ The following table lists the configurable parameters of the chart and their def | `api.statsd.host` | Host URL to receive statsd metrics | `null` | | `api.statsd.hostFromNodeIp` | Set as true to use the node IP as the statsd host instead | `false` | | `api.statsd.port` | Host port to receive statsd metrics | `8125` | -| `api.statsd.prefix` | Prefix to add to metric ids | `flagsmith.api` | +| `api.statsd.prefix` | Prefix to add to metric IDs | `flagsmith.api` | | `common.labels` | Labels to add to all resources | `{}` | | `common.annotations` | Annotations to add to all resources | `{}` | --- -## Key upgrade notes +## Key Upgrade Notes + +- [0.37.0](https://artifacthub.io/packages/helm/flagsmith/flagsmith/0.37.0): upgrades the bundled in-cluster PostgreSQL. This makes no effort to preserve data in the bundled in-cluster PostgreSQL if it is in use. This also renames the bundled in-cluster PostgreSQL to have `dev-postgresql` in the name, to signify that it exists such that the chart can be deployed self-contained, but that this PostgreSQL instance is treated as disposable. All Flagsmith installations for which the data is not disposable [should use an externally managed database](#provided-database-configuration). -- [0.37.0](https://artifacthub.io/packages/helm/flagsmith/flagsmith/0.37.0): upgrades the bundled in-cluster Postgres. - This makes no effort to preserve data in the bundled in-cluster Postgres if it is in use. This also renames the - bundled in-cluster Postgres to have `dev-postgresql` in the name, to signify that it exists such that the chart can be - deployed self-contained, but that this Postgres instance is treated as disposable. All Flagsmith installations for - which the data is not disposable [should use an externally managed database](#provided-database-configuration). +--- -## Development and contributing +## Development and Contributing ### Requirements -helm version > 3.0.2 +Helm version > 3.0.2 -### To run locally +### Running Locally -You can test and run the application locally on OSX using `minikube` like this: +You can test and run the application locally on macOS using `minikube` like this: ```bash # Install Docker for Desktop and then: @@ -508,7 +502,7 @@ helm install flagsmith --debug ./flagsmith minikube dashboard ``` -### Test chart installation +### Test Chart Installation Install Chart without building a package: @@ -516,16 +510,16 @@ Install Chart without building a package: helm install flagsmith --debug ./flagsmith ``` -Run template and check kubernetes resouces are made: +Run template and check Kubernetes resources are made: ```bash helm template flagsmith flagsmith --debug -f flagsmith/values.yaml ``` -### Build chart package +### Build Chart Package To build chart package run: ```bash helm package ./flagsmith -``` +``` \ No newline at end of file diff --git a/docs/docs/deployment-self-hosting/hosting-guides/manual-installation.md b/docs/docs/deployment-self-hosting/hosting-guides/manual-installation.md new file mode 100644 index 000000000000..04585a30e017 --- /dev/null +++ b/docs/docs/deployment-self-hosting/hosting-guides/manual-installation.md @@ -0,0 +1,15 @@ +--- +title: "Manual Installation" +description: "How to manually install both the Frontend and the API for a more configurable environment." +sidebar_position: 5 +--- + +If you want a more configurable environment, you can manually install both the Frontend and the API. + +## Server-Side API + +The source code and installation instructions can be found at [the GitHub project](https://github.com/Flagsmith/flagsmith/tree/main/api). The API is written in Python and is based on Django and the Django REST Framework. The server-side API relies on a PostgreSQL installation to store its data, and a Redis installation as a cache. + +## Frontend Website + +The source code and installation instructions can be found at [the GitHub project](https://github.com/Flagsmith/flagsmith/tree/main/frontend). The Frontend Website is written in React/JavaScript and requires Node.js. \ No newline at end of file diff --git a/docs/docs/deployment-self-hosting/hosting-guides/one-click-installers.md b/docs/docs/deployment-self-hosting/hosting-guides/one-click-installers.md new file mode 100644 index 000000000000..57677d5f10b8 --- /dev/null +++ b/docs/docs/deployment-self-hosting/hosting-guides/one-click-installers.md @@ -0,0 +1,40 @@ +--- +title: "One-Click Installers" +description: "A dedicated page for the Deploy to options (DigitalOcean, Render, Railway) and guides for Fly.io and Caprover." +sidebar_position: 4 +--- + +This guide provides instructions for deploying Flagsmith using one-click installers for various cloud platforms. These options offer a quick and straightforward way to get your Flagsmith instance up and running. + +## Deploy to DigitalOcean + +[![Deploy to DigitalOcean](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/flagsmith/flagsmith/tree/main) + +## Deploy to Render + +[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/flagsmith/flagsmith/tree/main) + +## Deploy on Railway + +[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/36mGw8?referralCode=DGxv1S) + +## Deploy to Fly.io + +![Fly.io](/img/logos/fly.io.svg) + +We're big fans of [Fly.io](https://fly.io)! You can deploy to Fly.io really easily: + +```bash +git clone git@github.com:Flagsmith/flagsmith.git +cd flagsmith +flyctl postgres create --name flagsmith-flyio-db +flyctl apps create flagsmith-flyio +flyctl postgres attach --postgres-app flagsmith-flyio-db +flyctl deploy +``` + +Fly.io has a global application namespace, and so you may need to change the name of the application defined in [`fly.toml`](https://github.com/Flagsmith/flagsmith/blob/main/fly.toml) as well as the commands above. + +## Deploy to Caprover + +You can also deploy to a [Caprover Server](https://caprover.com/) with [One Click Apps](https://caprover.com/docs/one-click-apps.html). \ No newline at end of file diff --git a/docs/docs/deployment-self-hosting/index.md b/docs/docs/deployment-self-hosting/index.md new file mode 100644 index 000000000000..952db1dd7eea --- /dev/null +++ b/docs/docs/deployment-self-hosting/index.md @@ -0,0 +1,85 @@ +--- +title: "Deployment and Self-hosting Overview" +description: "Learn how to deploy and self-host Flagsmith in your own infrastructure." +sidebar_position: 1 +--- + +# Deployment and Self-hosting + +Welcome to the Flagsmith deployment and self-hosting guide. This section provides comprehensive information on how to deploy and manage your own Flagsmith instance in your infrastructure. + +## What is Self-hosting? + +Self-hosting Flagsmith allows you to run the complete Flagsmith platform within your own infrastructure, giving you full control over your data, security policies, and deployment environment. This is ideal for organisations that require: + +- **Data sovereignty**: Keep your feature flag data within your own infrastructure +- **Custom integrations**: Integrate with your existing tooling and workflows +- **Enhanced security**: Implement your own security policies and compliance requirements +- **Offline capabilities**: Deploy in environments with limited or no internet access + +## Deployment Options + +Flagsmith supports multiple deployment methods to suit different infrastructure requirements: + +### Quick Start Options +- **[One-click installers](/deployment-self-hosting/hosting-guides/one-click-installers)**: Get up and running quickly with pre-configured deployment options +- **[Docker deployment](/deployment-self-hosting/hosting-guides/docker)**: Containerised deployment using Docker and Docker Compose +- **[Manual installation](/deployment-self-hosting/hosting-guides/manual-installation)**: Step-by-step installation guide for custom deployments + +### Cloud Platform Support +- **[AWS deployment](/deployment-self-hosting/hosting-guides/cloud-providers/aws)**: Deploy on Amazon Web Services +- **[Google Cloud Platform](/deployment-self-hosting/hosting-guides/cloud-providers/google-cloud)**: Deploy on Google Cloud Platform +- **[Aptible](/deployment-self-hosting/hosting-guides/cloud-providers/aptible)**: Deploy on Aptible platform + +### Enterprise Orchestration +- **[Kubernetes and OpenShift](/deployment-self-hosting/hosting-guides/kubernetes-openshift)**: Full Kubernetes deployment with Helm charts and operators +- **[Enterprise Edition](/deployment-self-hosting/enterprise-edition)**: Additional features and capabilities for enterprise deployments + +## Core Configuration + +Once deployed, you'll need to configure your Flagsmith instance: + +- **[Initial Setup](/deployment-self-hosting/core-configuration/initial-setup)**: Create your first superuser and initialise the platform +- **[Environment Variables](/deployment-self-hosting/core-configuration/environment-variables)**: Configure application settings and connections +- **[Email Setup](/deployment-self-hosting/core-configuration/email-setup)**: Configure email notifications and password resets +- **[Caching Strategies](/deployment-self-hosting/core-configuration/caching-strategies)**: Optimise performance with Redis and other caching options +- **[Running Flagsmith on Flagsmith](/deployment-self-hosting/core-configuration/running-flagsmith-on-flagsmith)**: Use Flagsmith to manage Flagsmith itself + +## Scaling and Performance + +As your usage grows, you'll need to consider scaling and performance: + +- **[Sizing and Scaling](/deployment-self-hosting/scaling-and-performance/sizing-and-scaling)**: Understand resource requirements and scaling strategies +- **[Monitoring](/deployment-self-hosting/scaling-and-performance/monitoring)**: Set up monitoring and alerting for your deployment +- **[Asynchronous Task Processor](/deployment-self-hosting/scaling-and-performance/asynchronous-task-processor)**: Configure background task processing +- **[Using InfluxDB for Analytics](/deployment-self-hosting/scaling-and-performance/using-influxdb-for-analytics)**: Set up analytics storage +- **[Load Testing](/deployment-self-hosting/scaling-and-performance/load-testing)**: Test your deployment under load + +## Administration and Maintenance + +Ongoing administration tasks to keep your deployment running smoothly: + +- **[Using the Django Admin](/deployment-self-hosting/administration-and-maintenance/using-the-django-admin)**: Access and manage your deployment through the admin interface +- **[Upgrades and Rollbacks](/deployment-self-hosting/administration-and-maintenance/upgrades-and-rollbacks)**: Safely upgrade your deployment and roll back if needed +- **[Troubleshooting](/deployment-self-hosting/administration-and-maintenance/troubleshooting)**: Common issues and their solutions + +## Edge Proxy + +For high-performance deployments: + +- **[Edge Proxy](/deployment-self-hosting/edge-proxy)**: Deploy edge proxies for improved performance and reduced latency + +## Getting Started + +To get started with self-hosting Flagsmith: + +1. **Choose your deployment method**: Start with [Docker](/deployment-self-hosting/hosting-guides/docker) for a quick setup, or use [one-click installers](/deployment-self-hosting/hosting-guides/one-click-installers) for cloud platforms +2. **Configure your environment**: Set up [environment variables](/deployment-self-hosting/core-configuration/environment-variables) and [email configuration](/deployment-self-hosting/core-configuration/email-setup) +3. **Initialise your instance**: Create your first superuser using the [initial setup guide](/deployment-self-hosting/core-configuration/initial-setup) +4. **Scale as needed**: Monitor your deployment and scale using the [scaling guides](/deployment-self-hosting/scaling-and-performance/sizing-and-scaling) + +## Support + +If you encounter issues during deployment or need assistance with enterprise features, please refer to our [troubleshooting guide](/deployment-self-hosting/administration-and-maintenance/troubleshooting) or contact our support team. + +For enterprise deployments with additional features and support, consider the [Enterprise Edition](/deployment-self-hosting/enterprise-edition). \ No newline at end of file diff --git a/docs/docs/deployment-self-hosting/scaling-and-performance/_category_.json b/docs/docs/deployment-self-hosting/scaling-and-performance/_category_.json new file mode 100644 index 000000000000..34138adfe2f7 --- /dev/null +++ b/docs/docs/deployment-self-hosting/scaling-and-performance/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Scaling & Performance", + "collapsed": true, + "position": 5 +} + diff --git a/docs/docs/deployment/configuration/task-processor.md b/docs/docs/deployment-self-hosting/scaling-and-performance/asynchronous-task-processor.md similarity index 64% rename from docs/docs/deployment/configuration/task-processor.md rename to docs/docs/deployment-self-hosting/scaling-and-performance/asynchronous-task-processor.md index a074af057708..d3da0ea97d2a 100644 --- a/docs/docs/deployment/configuration/task-processor.md +++ b/docs/docs/deployment-self-hosting/scaling-and-performance/asynchronous-task-processor.md @@ -1,16 +1,12 @@ # Asynchronous Task Processor -Flagsmith has the ability to consume asynchronous tasks using a separate task processor service. If flagsmith is run -without the asynchronous processor, the flagsmith API will run any asynchronous tasks in a separate, unmanaged thread. +Flagsmith has the ability to consume asynchronous tasks using a separate task processor service. If Flagsmith is run without the asynchronous processor, the Flagsmith API will run any asynchronous tasks in a separate, unmanaged thread. ## Running the Processor -The task processor can be run using the `flagsmith/flagsmith-api` image with a slightly different entrypoint. It can be -pointed to the same database that the API container is using, or instead, [it can use a separate -database](#running-the-processor-with-a-separate-database) as broker and result storage. +The task processor can be run using the `flagsmith/flagsmith-api` image with a slightly different entrypoint. It can be pointed to the same database that the API container is using, or instead, [it can use a separate database](#running-the-processor-with-a-separate-database) as broker and result storage. -To enable the API sending tasks to the processor, you must set the `TASK_RUN_METHOD` environment variable to -`"TASK_PROCESSOR"` in the `flagsmith` container running the Flagsmith application. +To enable the API sending tasks to the processor, you must set the `TASK_RUN_METHOD` environment variable to `"TASK_PROCESSOR"` in the `flagsmith` container running the Flagsmith application. A basic docker-compose setup might look like: @@ -47,9 +43,7 @@ flagsmith_processor: ## Configuring the Processor -The processor exposes a number of configuration options to tune the processor to your needs / setup. These configuration -options are via command line arguments when starting the processor. The default Flagsmith container entrypoint expects -these options as `TASK_PROCESSOR_`-prefixed environment variables. +The processor exposes a number of configuration options to tune the processor to your needs/setup. These configuration options are via command line arguments when starting the processor. The default Flagsmith container entrypoint expects these options as `TASK_PROCESSOR_`-prefixed environment variables. | Environment variable | Argument | Description | Default | | ---------------------------------- | ------------------- | -------------------------------------------------------------------------- | ------- | @@ -60,14 +54,10 @@ these options as `TASK_PROCESSOR_`-prefixed environment variables. ## Running the Processor with a Separate Database -The task processor can be run with a separate database for the task queue and results, which can be useful to separate -infrastructure concerns. To do this, you need point the application — i.e. both the API and task processor services — -to a different database than the primary database used by the API. This can be done in **one of the following ways:** +The task processor can be run with a separate database for the task queue and results, which can be useful to separate infrastructure concerns. To do this, you need to point the application — i.e. both the API and task processor services — to a different database than the primary database used by the API. This can be done in **one of the following ways:** - Set the `TASK_PROCESSOR_DATABASE_URL` environment variable pointing to the task processor database. -- Set the old-style environment variables `TASK_PROCESSOR_DATABASE_HOST`, `TASK_PROCESSOR_DATABASE_PORT`, - `TASK_PROCESSOR_DATABASE_USER`, `TASK_PROCESSOR_DATABASE_PASSWORD`, `TASK_PROCESSOR_DATABASE_NAME` to point to the - task processor database. +- Set the old-style environment variables `TASK_PROCESSOR_DATABASE_HOST`, `TASK_PROCESSOR_DATABASE_PORT`, `TASK_PROCESSOR_DATABASE_USER`, `TASK_PROCESSOR_DATABASE_PASSWORD`, `TASK_PROCESSOR_DATABASE_NAME` to point to the task processor database. A basic docker-compose setup with a separate database might look like: @@ -115,32 +105,25 @@ flagsmith_processor: ### Migrating from the Default Database (or back) -As soon as the application starts with a separate task processor database, by default it will consume tasks from **both -databases**, as to ensure that any remaining tasks in the default database are exhausted. This comes with a slight -performance cost that **may impact your deployment**. +As soon as the application starts with a separate task processor database, by default it will consume tasks from **both databases**, as to ensure that any remaining tasks in the default database are exhausted. This comes with a slight performance cost that **may impact your deployment**. -This _default behavior_ is intended to help migrating from a _single database_ to a _separate database_ setup, and is -managed by the following setting: +This _default behaviour_ is intended to help migrating from a _single database_ to a _separate database_ setup, and is managed by the following setting: ```yaml # First, fetch any remaining tasks from the default database, then move onto the task processor database. -TASK_PROCESSOR_DATABASES: default,task_processor # Default behavior when a separate database is used +TASK_PROCESSOR_DATABASES: default,task_processor # Default behaviour when a separate database is used ``` -Once all tasks from the default database are consumed, it is recommended to update the `TASK_PROCESSOR_DATABASES` -environment variable to only consume tasks from the task processor separate database: +Once all tasks from the default database are consumed, it is recommended to update the `TASK_PROCESSOR_DATABASES` environment variable to only consume tasks from the task processor separate database: ```yaml # Only consume tasks from the task processor database. TASK_PROCESSOR_DATABASES: task_processor ``` -The above setting is also recommended for new deployments starting with a separate database layout, since there will be -no pending tasks in the default database to consume. +The above setting is also recommended for new deployments starting with a separate database layout, since there will be no pending tasks in the default database to consume. -Optionally, or if you have any reason to move historical task processor data from one database to another, you can do -so using Django's `loaddata` and `dumpdata` commands. This is a manual process, and you should ensure that the task -processor is **not running while you do this**, as it may interfere with the data and/or tasks. +Optionally, or if you have any reason to move historical task processor data from one database to another, you can do so using Django's `loaddata` and `dumpdata` commands. This is a manual process, and you should ensure that the task processor is **not running while you do this**, as it may interfere with the data and/or tasks. Assuming the above Docker Compose setup as example: @@ -154,16 +137,13 @@ docker-compose run flagsmith python manage.py loaddata task_processor task_proce There are a number of options for monitoring the task processor's health. -### Health checks +### Health Checks -A task processor container exposes `/health/readiness` and `/health/liveness` endpoints for readiness and liveness -probes. The endpoints run simple availability checks. To include a test that enqueues a task and makes sure it's run -to your readiness probe, set `ENABLE_TASK_PROCESSOR_HEALTH_CHECK` environment variable to `True`. +A task processor container exposes `/health/readiness` and `/health/liveness` endpoints for readiness and liveness probes. The endpoints run simple availability checks. To include a test that enqueues a task and makes sure it's run to your readiness probe, set `ENABLE_TASK_PROCESSOR_HEALTH_CHECK` environment variable to `True`. -### Task statistics +### Task Statistics -Both API and task processor expose an endpoint which returns task processor statistics in JSON format. -This endpoint is available at `GET /processor/monitoring`. See an example response below: +Both API and task processor expose an endpoint which returns task processor statistics in JSON format. This endpoint is available at `GET /processor/monitoring`. See an example response below: ```json { diff --git a/docs/docs/deployment-self-hosting/scaling-and-performance/load-testing.md b/docs/docs/deployment-self-hosting/scaling-and-performance/load-testing.md new file mode 100644 index 000000000000..85fd3c3e284c --- /dev/null +++ b/docs/docs/deployment-self-hosting/scaling-and-performance/load-testing.md @@ -0,0 +1,22 @@ +--- +title: Load Testing +description: Instructions for load testing using JMeter and wrk. +--- + +## JMeter + +There are [JMeter](https://jmeter.apache.org/) tests available in our public repo on GitHub: + +https://github.com/Flagsmith/flagsmith/tree/main/api/jmeter-tests + +## wrk + +We also recommend using [wrk](https://github.com/wg/wrk) for load testing the core SDK endpoints. Some examples of this (make sure you update URL and environment keys!): + +```bash +# Get flags endpoint +wrk -t6 -c200 -d20s -H 'X-Environment-Key: iyiS5EDNDxMDuiFpHoiwzG' http://127.0.0.1:8000/api/v1/flags/ + +# Get flags for an identity +wrk -t6 -c200 -d20s -H 'X-Environment-Key: iyiS5EDNDxMDuiFpHoiwzG' "http://127.0.0.1:8000/api/v1/identities/?identifier=mrflags@flagsmith.com" +``` diff --git a/docs/docs/deployment-self-hosting/scaling-and-performance/monitoring.md b/docs/docs/deployment-self-hosting/scaling-and-performance/monitoring.md new file mode 100644 index 000000000000..216d7eb26851 --- /dev/null +++ b/docs/docs/deployment-self-hosting/scaling-and-performance/monitoring.md @@ -0,0 +1,113 @@ +--- +title: Monitoring +description: Setting up monitoring integrations like AppDynamics and StatsD. +--- + +Monitoring your Flagsmith instance is crucial for keeping things running smoothly, spotting issues early, and making sure your platform is performing as expected. Whether you're running a small self-hosted setup or a large-scale deployment, having the right monitoring tools in place will help you keep tabs on system health, performance, and usage. + +This page covers how to integrate Flagsmith with popular monitoring solutions like AppDynamics and StatsD. We'll walk you through the setup steps and share some tips for getting the most out of your monitoring stack. + +## AppDynamics + +:::info + +AppDynamics is an Enterprise only feature. + +::: + +The application supports the use of AppDynamics for monitoring purposes. In order to set up AppDynamics for your environment, follow the steps below: + +:::note + +There is a bug in the AppDynamics wizard that sets the value `ssl = (on)` which needs to be changed to `ssl = on` + +::: + +1. Set up your application in your AppDynamics dashboard using the "Getting Started Wizard - Python". +2. In the wizard, you will need to select the "uWSGI with Emperor: Module Directive" when choosing a deployment method. +3. On completing the wizard, you will be provided with a configuration file like the one seen here in the appdynamics.template.cfg provided, except with your application information. Make a copy of this information and place it in a file. + +### Running with Docker + +When running with traditional Docker, you can use the code snippet below to inject the required information for running AppDynamics: + +```shell +docker run -t \{image_name\} -v \{config_file_path\}:/etc/appdynamics.cfg -e APP_DYNAMICS=on +``` + +Replacing the values for: + +- **\{image_name\}**: the tagged name of the Docker image you are using +- **\{config_file_path\}**: the absolute path of the appdynamics.cfg file on your system + +### Running with Docker Compose + +When running with the `docker-compose.yml` file provided, ensure the `APP_DYNAMICS` environment variable is set to `on` as seen below: + +```yaml +api: + build: + context: . + dockerfile: docker/Dockerfile + env: + APP_DYNAMICS: 'on' + volumes: + - \{config_file_path\}:/etc/appdynamics.cfg +``` + +Replacing the value for **\{config_file_path\}** with the absolute path of the appdynamics.cfg file on your system. + +Running the command below will build the Docker image with all the AppDynamics config included: + +```shell +docker-compose -f docker-compose.yml build +``` + +This image can then be run locally using the docker-compose `up` command as seen below: + +```shell +docker-compose -f docker-compose.yml up +``` + +### Additional Settings + +If you need additional AppDynamics setup options, you can find the other environment variables you can set [here](https://docs.appdynamics.com/display/PRO21/Python+Agent+Settings). + +## Prometheus + +To enable the Prometheus `/metrics` endpoint, set the `PROMETHEUS_ENABLED` environment variable to `true`. + +The metrics provided by Flagsmith are described below. + +| Metric Name | Type | Description | Labels | +|-------------|------|-------------|--------| +| `flagsmith_build_info` | Gauge | Flagsmith version and build information | `ci_commit_sha`, `version` | +| `flagsmith_environment_document_cache_queries` | Counter | Results of cache retrieval for environment document. `result` label is either `hit` or `miss` | `result` | +| `flagsmith_http_server_request_duration_seconds` | Histogram | HTTP request duration in seconds | `route`, `method`, `response_status` | +| `flagsmith_http_server_requests` | Counter | Total number of HTTP requests | `route`, `method`, `response_status` | +| `flagsmith_http_server_response_size_bytes` | Histogram | HTTP response size in bytes | `route`, `method`, `response_status` | +| `flagsmith_task_processor_enqueued_tasks` | Counter | Total number of enqueued tasks | `task_identifier` | +| `flagsmith_task_processor_finished_tasks` | Counter | Total number of finished tasks. Only collected by Task Processor. `task_type` label is either `recurring` or `standard` | `task_identifier`, `task_type`, `result` | +| `flagsmith_task_processor_task_duration_seconds` | Histogram | Task processor task duration in seconds. Only collected by Task Processor. `task_type` label is either `recurring` or `standard` | `task_identifier`, `task_type`, `result` | + +## StatsD + +There is currently no specific documentation for setting up StatsD. + +## Task Processor Monitoring + +There are a number of options for monitoring the task processor's health. + +### Health Checks + +A task processor container exposes `/health/readiness` and `/health/liveness` endpoints for readiness and liveness probes. The endpoints run simple availability checks. To include a test that enqueues a task and makes sure it's run to your readiness probe, set `ENABLE_TASK_PROCESSOR_HEALTH_CHECK` environment variable to `True`. + +### Task Statistics + +Both API and task processor expose an endpoint which returns task processor statistics in JSON format. This endpoint is available at `GET /processor/monitoring`. See an example response below: + +```json +{ + "waiting": 1 // The number of tasks waiting in the queue. +} +``` diff --git a/docs/docs/deployment-self-hosting/scaling-and-performance/sizing-and-scaling.md b/docs/docs/deployment-self-hosting/scaling-and-performance/sizing-and-scaling.md new file mode 100644 index 000000000000..13795a502254 --- /dev/null +++ b/docs/docs/deployment-self-hosting/scaling-and-performance/sizing-and-scaling.md @@ -0,0 +1,50 @@ +--- +title: Sizing and Scaling +description: Sizing and Scaling Flagsmith +sidebar_position: 80 +--- + +Flagsmith has a very simple architecture, making it well understood when it comes to serving high loads. + +## Frontend Dashboard + +Generally, this component is not put under any sort of significant load. It can be load balanced if required. It does not require sticky sessions. + +## API + +The API is completely stateless. This means it can scale out behind a load balancer almost perfectly. As an example, when running on AWS ECS/Fargate, we run with: + +- `cpu=1024` +- `memory=2048` + +In terms of auto-scaling, we recommend basing the auto-scaling off the `ECSServiceAverageCPUUtilization` metric, with a `target_value` of `50` and a 30-second cool-down timeout. + +## Database + +Our recommendation is to first scale the database up with a more powerful single server. + +### Replication + +Once the database connections have been saturated by the API cluster, adding read replicas to the database solves the next bottleneck of database connections. + +Flagsmith can be set up to handle as many read replicas as needed. To add replicas, you'll need to set the `REPLICA_DATABASE_URLS` environment variable with a comma-separated list of database URLs. + +Example: + +``` +REPLICA_DATABASE_URLS: postgres://user:password@replica1.database.host:5432/flagsmith,postgres://user:password@replica2.database.host:5432/flagsmith +``` + +:::tip + +Use the `REPLICA_DATABASE_URLS_DELIMITER` environment variable if you are using any `,` characters in your passwords. + +::: + +In addition to typical read replicas, which usually exist locally in the same data centre to the application, there is also support for replicas across regions via the `CROSS_REGION_REPLICA_DATABASE_URLS` environment variable, which is set in the same way as the `REPLICA_DATABASE_URLS` with cross-region replicas having their own matching `CROSS_REGION_REPLICA_DATABASE_URLS_DELIMITER`, which also defaults to `,` as above. + +Cross-region replicas are only used once all typical replicas have gone offline, since the performance characteristics wouldn't be favourable to spread replica load at longer latencies. Both `REPLICA_DATABASE_URLS` and `CROSS_REGION_REPLICA_DATABASE_URLS` can be used alone or simultaneously. + +To support different configurations, there are two different replication strategies available. By setting `REPLICA_READ_STRATEGY` to `DISTRIBUTED` (the default option), the load to the replicas is distributed evenly. If your use case, on the other hand, is to utilise fallback replicas (primary, secondary, etc.), the `REPLICA_READ_STRATEGY` should be set to `SEQUENTIAL` so a replica is only used if all the other replicas preceding it have gone offline. This strategy is applicable to both typical replicas as well as to cross-region replicas. + +We would also recommend testing [pgBouncer](https://www.pgbouncer.org/) in your environment as it generally optimises database connections and reduces the load on the database. diff --git a/docs/docs/deployment-self-hosting/scaling-and-performance/using-influxdb-for-analytics.md b/docs/docs/deployment-self-hosting/scaling-and-performance/using-influxdb-for-analytics.md new file mode 100644 index 000000000000..9e7e719d4da4 --- /dev/null +++ b/docs/docs/deployment-self-hosting/scaling-and-performance/using-influxdb-for-analytics.md @@ -0,0 +1,133 @@ +--- +title: Using InfluxDB for Analytics +description: How to offload time-series data to InfluxDB instead of PostgreSQL. +--- + +Flagsmith has a soft dependency on InfluxDB to store time-series data. You don't need to configure InfluxDB to run the platform; by default, this data will be stored in PostgreSQL. If you are running very high traffic loads, you might be interested in deploying InfluxDB. + +## Docker + +1. Create a user account in InfluxDB. You can visit [http://localhost:8086/] +2. Go into Data > Buckets and create three new buckets called `default`, `default_downsampled_15m` and + `default_downsampled_1h` +3. Go into Data > Tokens and grab your access token. +4. Edit the `docker-compose.yml` file and add the following `environment` variables in the API service to connect the API to InfluxDB: + - `INFLUXDB_TOKEN`: The token from the step above + - `INFLUXDB_URL`: `http://influxdb` + - `INFLUXDB_ORG`: The organisation ID - you can find it + [here](https://docs.influxdata.com/influxdb/v2.0/organizations/view-orgs/) + - `INFLUXDB_BUCKET`: `default` +5. Restart `docker-compose` +6. Create a new task with the following query. This will downsample your per-millisecond API request data down to 15-minute blocks for faster queries. Set it to run every 15 minutes. + +```text +option task = {name: "Downsample (API Requests)", every: 15m} + +data = from(bucket: "default") + |> range(start: -duration(v: int(v: task.every) * 2)) + |> filter(fn: (r) => + (r._measurement == "api_call")) + +data + |> aggregateWindow(fn: sum, every: 15m) + |> filter(fn: (r) => + (exists r._value)) + |> to(bucket: "default_downsampled_15m") +``` + +Once this task has run, you will see data coming into the Organisation API Usage area. + +7. Create another new task with the following query. This will downsample your per-millisecond flag evaluation data down to 15-minute blocks for faster queries. Set it to run every 15 minutes. + +```text +option task = {name: "Downsample (Flag Evaluations)", every: 15m} + +data = from(bucket: "default") + |> range(start: -duration(v: int(v: task.every) * 2)) + |> filter(fn: (r) => + (r._measurement == "feature_evaluation")) + +data + |> aggregateWindow(fn: sum, every: 15m) + |> filter(fn: (r) => + (exists r._value)) + |> to(bucket: "default_downsampled_15m") +``` + +Once this task has run, and you have made some flag evaluations with analytics enabled (see documentation [here](/managing-flags/flag-analytics) for information on this), you should see data in the 'Analytics' tab against each feature in your dashboard. + +8. Create another new task with the following query. This will downsample your per-millisecond API request data down to 1-hour blocks for faster queries. Set it to run every 1 hour. + +```text +option task = {name: "Downsample API 1h", every: 1h} + +data = from(bucket: "default") + |> range(start: -duration(v: int(v: task.every) * 2)) + |> filter(fn: (r) => + (r._measurement == "api_call")) + +data + |> aggregateWindow(fn: sum, every: 1h) + |> filter(fn: (r) => + (exists r._value)) + |> to(bucket: "default_downsampled_1h") +``` + +9. Create another new task with the following query. This will downsample your per-millisecond flag evaluation data down to 1-hour blocks for faster queries. Set it to run every 1 hour. + +```text +option task = {name: "Downsample API 1h - Flag Analytics", every: 1h} + +data = from(bucket: "default") + |> range(start: -duration(v: int(v: task.every) * 2)) + |> filter(fn: (r) => + (r._measurement == "feature_evaluation")) + |> filter(fn: (r) => + (r._field == "request_count")) + |> group(columns: ["feature_id", "environment_id"]) + +data + |> aggregateWindow(fn: sum, every: 1h) + |> filter(fn: (r) => + (exists r._value)) + |> set(key: "_measurement", value: "feature_evaluation") + |> set(key: "_field", value: "request_count") + |> to(bucket: "default_downsampled_1h") +``` + +## Kubernetes (via Helm) + +By default, Flagsmith uses PostgreSQL to store time-series data. You can alternatively use InfluxDB to track: + +- SDK API traffic +- SDK Flag Evaluations + +You will need to perform the steps above to configure InfluxDB itself. You can then configure the Helm chart with the following values: + +| Parameter | Description | Default | +| -------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------ | +| `influxdb2.enabled` | | `true` | +| `influxdb2.nameOverride` | | `influxdb` | +| `influxdb2.image.repository` | Docker image repository for InfluxDB | `quay.io/influxdb/influxdb` | +| `influxdb2.image.tag` | Docker image tag for InfluxDB | `v2.0.2` | +| `influxdb2.image.imagePullPolicy` | | `IfNotPresent` | +| `influxdb2.image.imagePullSecrets` | | `[]` | +| `influxdb2.adminUser.organization` | | `influxdata` | +| `influxdb2.adminUser.bucket` | | `default` | +| `inflxdb2.adminUser.user` | | `admin` | +| `influxdb2.adminUser.password` | | randomly generated | +| `influxdb2.adminUser.token` | | randomly generated | +| `influxdb2.persistence.enabled` | | `false` | +| `influxdb.resources` | resources per pod for the InfluxDB | `{}` | +| `influxdb.nodeSelector` | | `{}` | +| `influxdb.tolerations` | | `[]` | +| `influxdb.affinity` | | `{}` | +| `influxdbExternal.enabled` | Use an InfluxDB not managed by this chart | `false` | +| `influxdbExternal.url` | | | +| `influxdbExternal.bucket` | | | +| `influxdbExternal.organization` | | | +| `influxdbExternal.token` | | | +| `influxdbExternal.tokenFromExistingSecret.enabled` | Use reference to a k8s secret not managed by this chart | `false` | +| `influxdbExternal.tokenFromExistingSecret.name` | Referenced secret name | | +| `influxdbExternal.tokenFromExistingSecret.key` | Key within the referenced secret to use | | + diff --git a/docs/docs/deployment/_category_.json b/docs/docs/deployment/_category_.json deleted file mode 100644 index 7115eab6a057..000000000000 --- a/docs/docs/deployment/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Deployment", - "position": 80, - "collapsed": true -} diff --git a/docs/docs/deployment/configuration/_category_.json b/docs/docs/deployment/configuration/_category_.json deleted file mode 100644 index c3e8b5409a92..000000000000 --- a/docs/docs/deployment/configuration/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Configuration", - "position": 20, - "collapsed": false -} diff --git a/docs/docs/deployment/configuration/authentication/_category_.json b/docs/docs/deployment/configuration/authentication/_category_.json deleted file mode 100644 index 275a3b098f2c..000000000000 --- a/docs/docs/deployment/configuration/authentication/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Authentication", - "collapsed": false -} diff --git a/docs/docs/deployment/configuration/django-admin.md b/docs/docs/deployment/configuration/django-admin.md deleted file mode 100644 index 02815f5fa161..000000000000 --- a/docs/docs/deployment/configuration/django-admin.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -sidebar_label: Django Admin -title: Django Admin -sidebar_position: 100 ---- - -# Django Admin - -The Flagsmith API is a Django application. As such, certain administrative tasks can be performed using -[Django's built-in admin interface](https://docs.djangoproject.com/en/4.2/ref/contrib/admin/), which we refer to as -Django Admin. - -:::danger - -Improper use of Django Admin can cause data loss and make your Flagsmith instance unusable. Make sure to control who -has access, and only perform tasks as directed by Flagsmith staff. - -::: - -## Accessing Django Admin - -Django Admin can be accessed from the `/admin/` route on the Flagsmith API. Note that the trailing slash is important. - -Accessing Django Admin requires a user with -[`is_staff`](https://docs.djangoproject.com/en/4.2/ref/contrib/auth/#django.contrib.auth.models.User.is_staff) set. -This does not grant any additional permissions beyond accessing Django Admin itself. - -A user with -[`is_superuser`](https://docs.djangoproject.com/en/4.2/ref/contrib/auth/#django.contrib.auth.models.User.is_superuser) -is granted all permissions. Note that superusers still require `is_staff` to access Django Admin. - -You can obtain a user with these permissions using any of these methods: - -* Use the [`createsuperuser` management command](/deployment/hosting/locally-api#locally) from a Flagsmith API shell. -* If no users exist yet, - [visit the Initialise Config page](/deployment/hosting/locally-api#environments-with-no-direct-console-access-eg-heroku-ecs). -* Manually set the `is_staff` and `is_superuser` database fields for your user in the `users_ffadminuser` table. - -## Authentication - -You can log in to Django Admin using the same email and password you use to log in to Flagsmith, or using Google login. - -### Email and password - -To log in to Django Admin with a password, make sure the Flagsmith API has the `ENABLE_ADMIN_ACCESS_USER_PASS` -environment variable set to `true`. - -If your Flagsmith account does not have a password, you can create one using any of these methods: - -* From the Flagsmith login page, click "Forgot password". Make sure your Flagsmith API is - [configured to send emails](/deployment/hosting/locally-api#email). -* From a Flagsmith API shell, run `python manage.py changepassword your_email@example.com` and type a password. - -### Google - -Google accounts uses OAuth 2.0, which requires TLS. - -To set up Google authentication for Django Admin, create an OAuth client ID and secret from -[Google Developer Console](https://console.developers.google.com/project). The redirect URI should point to -`/admin/admin_sso/assignment/end/` on your API domain. - -Set your Google OAuth client ID and secret in the following Flagsmith API environment variables: - -* `OAUTH_CLIENT_ID` -* `OAUTH_CLIENT_SECRET` - -To log in with Google, click "Log in using SSO" from the Django Admin login page. diff --git a/docs/docs/deployment/configuration/sizing-and-scaling.md b/docs/docs/deployment/configuration/sizing-and-scaling.md deleted file mode 100644 index 78e37571e5f3..000000000000 --- a/docs/docs/deployment/configuration/sizing-and-scaling.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: Sizing and Scaling -description: Sizing and Scaling Flagsmith -sidebar_position: 80 ---- - -## Overview - -Flagsmith has a very simple architecture, making it well understood when it comes to serving high loads. - -## Frontend Dashboard - -Generally this component is not put under any sort of significant load. It can be load balanced if required. It does not -require sticky-sessions. - -## API - -The API is completely stateless. This means it can scale out behind a load balancer almost perfectly. As an example, -when running on AWS ECS/Fargate we run with: - -- `cpu=1024` -- `memory=2048` - -In terms of auto scaling, we recommend basing the autoscaling off the `ECSServiceAverageCPUUtilization` metric, with a -`target_value` of `50` and a 30 second cool-down timeout. - -## Database - -Our recommendation is to first scale the database up with a more powerful single server. - -### Replication - -Once the database connections have been saturated by the API cluster, adding read replicas to the database solves the -next bottleneck of database connections. - -Flagsmith can be set up to handle as many read replicas as needed. To add replicas, you'll need to set the -`REPLICA_DATABASE_URLS` environment variable with a comma separated list of database urls. - -Example: - -``` -REPLICA_DATABASE_URLS: postgres://user:password@replica1.database.host:5432/flagsmith,postgres://user:password@replica2.database.host:5432/flagsmith -``` - -:::tip - -Use the `REPLICA_DATABASE_URLS_DELIMITER` environment variable if you are using any `,` characters in your passwords. - -::: - -In addition to typical read replicas, which usually exist locally in the same data centre to the application. There is -also support for replicas across regions via the `CROSS_REGION_REPLICA_DATABASE_URLS` environment variable which is set -in the same way as the `REPLICA_DATABASE_URLS` with cross region replicas having their own matching -CROSS_REGION_REPLICA_DATABASE_URLS_DELIMITER which also defaults to `,` as above. - -Cross region replicas are only used once all typical replicas have gone offline, since the performance characteristics -wouldn't be favorable to spread replica load at longer latencies. Both `REPLICA_DATABASE_URLS` and -`CROSS_REGION_REPLICA_DATABASE_URLS` can be used alone or simultaneously. - -To support different configurations there are two different replication strategies available. By setting -`REPLICA_READ_STRATEGY` to `DISTRIBUTED` (the default option) the load to the replicas is distributed evenly. If your -use-case, on the otherhand, is to utilize fallback replicas (primary, secondary, etc) the `REPLICA_READ_STRATEGY` should -be set to `SEQUENTIAL` so a replica is only used if all the other replica's preceding it have gone offline. This -strategy is applicable to both typical replicas as well as to cross region replicas. - -We would also recommend testing [pgBouncer](https://www.pgbouncer.org/) in your environment as it generally optimises -database connections and reduces the load on the database. diff --git a/docs/docs/deployment/configuration/troubleshooting.md b/docs/docs/deployment/configuration/troubleshooting.md deleted file mode 100644 index 8df53392b388..000000000000 --- a/docs/docs/deployment/configuration/troubleshooting.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: Troubleshooting -sidebar_label: Troubleshooting -sidebar_position: 90 ---- - -Here are some common issues encountered when trying to set up Flagsmith in a self hosted environment. - -## Health Checks - -If you are using health-checks, make sure to use `/health` as the health-check endpoint for both the API and the Front -end. - -## API and Database connectivity - -The most common cause of issue when setting things up in AWS with an RDS database is missing Security Group permissions -between the API application and the RDS database. You need to ensure that the attached security groups for -ECS/Fargate/EC2 allow access to the RDS database. -[AWS provide more detail about this here](https://aws.amazon.com/premiumsupport/knowledge-center/ecs-task-connect-rds-database/) -and [here](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.RDSSecurityGroups.html). - -Make sure you have a `DATABASE_URL` environment variable set within the API application. - -## Frontend > API DNS Setup - -If you are running the API and the Front end as separate applications, you need to make sure that the Front end is -pointing to the API. Check the -[Front end environment variables](/deployment/hosting/locally-frontend#environment-variables), particularly `API_URL`. diff --git a/docs/docs/deployment/configuration/usage-report-example.png b/docs/docs/deployment/configuration/usage-report-example.png deleted file mode 100644 index 8a6721f6e1515832f9631660eb49ab67004ab203..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73922 zcmZsCbyVBk(r(euyR<-Xmm)=q6}JGTPzdf&G&qH#!ToaH zbIy0ayVjk5erqLb+sy2}XU{xO>{|^*Tr6^|7cXAmDl5rpy?B8FdGP{S2m}537qMvP z;ukMMUMR~+zxOpc$g|4S8A!i6v)btuJBIT492xnjhpZ9wAg`#0p(|r(gaPo6{w*sL zt%ivVeKa-m^M>6`nt7|8ce#SL?V;axZ>DoOwsrg;9I6d2`iS0z1zgPH9|v9P|I(DW zu^dwLIR@TckLBS@w0rCto?V%uZ>;?%j-B1y{tCGar8&%RA!aMx_l91#`QEsbX8dgZfBH+bI%&-)fLOhf ze)PT>(rGjIU7dAs8II3s*;=`B9=?kL`~<8=O4nq*S?=_2nYg14a6MRAYDweH@AiE+ zX59KEn0`HMBRTwkiER4MO;c%&bi&`lTK$2_W2PrHvJCK!hc4(-PxQcAwDeec&-dZ5 zW9Zdtb&Jb#q6gtiEV`x$D$4&QN6huB;Q&|H^#;HN4h17` zkgCy;Poh=fUm;>;HG0G8 zA;j}8%c;+r$qV6%8r+OGZ1FSh_F|Xcs-UHh6%`hi1C%5U-oYm20RNU~$DYYP3wOlK z-uJyCS&hDMX2{x3wa?aEwU0lW8#M6l5}(Vs%^f#dxCd5D%lEG?N>JGYdj^+!{w=f} zKf;=)Wp>_M;)9{?7uiNUPd_LMTKyjUezAY3ePkzfvxlh_;o;#8hW7Iae-sn`w^#~s zTj36I;E5nD$O1#x6Q0_geK(0d)R-2e5>{Ji6|vHLxwkm zX7zOSoEDa>EwayMl#rLasz9*I@SM}sHyyDG;|^)wnQuC#L!IxHnbfntr5K+W|HoPW z2>Wfs&_>%>I%GgBotj*%)VGguyHsVq9$N*D)>`dO_%J^HIK^?G)nDFbiKD4f`(c`o zNmm>J*)8Va9bl$ZzB)B~qa>NIb$YOH#_P53xwlx|fkO_G`^&Qn`?{b8+2WBe zsi3Xyqj_<#TMvNH_4$#PrjC~aM8vz4U|IG@44^;IBp{wSF{eQlirdyH7gffM6>ywca~TAog%hhR)a0mLA-lHPE#`Wb5&g70Q?kMSp`xYA-RBv z^FuKytk=G}`N5B9S>iT9r5IbMDa_Lk@7myN7K-_Yic^ox)YE_@E-~hlC$pe>?9_sQ z-|ZNy0A>w8WX$03D@RIcT1E^CKp>6pcz;rs#P&cP(dx&Up1=CBce4q0X>hntJ<%4e zu&(EQF6{QyqgJUhfvTP$mX>eyMM4BDDY+z3fs?tXU_fc&Zf``0sRE#Dl6rB`&ChTu zrf+y*$e76bcH59KDQ9UV?OL2!TOE#nds#kdQKB?ir1izDhuhr3!fK`cLiFkR*p$i^ za^V=wZqnj+TGU$f`9mk>bCe+J1L*Ns@;OKqtcI)9!EpBH99W+1pVa3$5(^;$%jy2Y zI$B(sw|a=eovQSqUij{o(!9`mN;IR7bJbufIO>gHxk>|-dY?;K8$HS(!cMnJuIWNG ztRhzc>QhrFN|ZL(=(RSC<$%U`f`PBY=?;>0;RGimj}HJH>_M#YkkD|;+qkS-HSQWk zw7mz6>mIS37RR)qw8~-V4R({{l8&;3%H1V(BDui6lK9!j^7j?(Bl+T7y*h&*m-mPMU(s`6>a6nuddw7D>h3i==Sc1k5{4SP$-Qvc-dl2=t-x zN7E&3>}hT`M+C5bWFd(0$)|i9#&A!==X{ z`Ly6-Ik~~8RldfD*vYA;+xZ(5$`7}nmqu)JlvwF^`c>Z}NV?Oq_;#0lrd005{VtPO zwM!M_b0l~je`5^hu@~ImXo9yigGV@S&WLpzEEi|yyr!94hQ@<#9l(fj&EfIjTME17 z@-|BG{Upf^1u58_r}WLC%OgYg-C@D`lhGz{oPkm~wLdJl#OZ#Vhsx z=JTKBq%3cy@!87Rx6}5ADSHIXN3T=eR`1aep9`M)G0@nj#fig&-S$)CC`A-DLq`Au z#}#}tn#|z+!7}>fVF1!pi4$ULtQIU1cv)pqTq~~Kn91+5sx_d0(wU3ge`$Cqmi3q~ z3F<~IaD$OcwO)>P(ET;J(VgqOjIXNQ`?yjGA8J0L9-K^3wE9e|Y`6pm(`*VFb6au+ z9V;*VKEYn#`O|Dqy6ID(#f!PVEivDm3355Sc3a%}8f8>pMkqtd5L+BZ1ve5X}wsO|D15jm6>OMLWT3k`M1p0_u`eSS+_aT65MRSRAbmJ z9gL#HIOh?75>|f@-b*h7TzF4@;5sR_i{C|!8vinmGbWVuxuv-XJe%A zj7CagzQ(X%8#TF=P&Dh6ep{>px_(b6&G-80qv}hoHpFnf{l$LyxFi+^R>59q@<4pz zzz6!o0Xbt_hu_~Tl84*9RuhZlKPj(*B1fVvtlo!h0H`q|Mu<5K9DtL4wERn+q>7z> z7fF)$kF1BXacO)CQclaQS)emDOpX*rfn|F2C)408@K^!!k*tAgTHGWYV{^s-Tu`p& zdo~XV2Ljq?KP#vP1Q$Tr1=~{F&PnZeXS`wUn;KI$0Z5qd)o&tu^`$8iRAo zZD;BW<9iJ~?!J{!Wh7RVt<$m)l%N2;PvWL3 zpI)Os)MWv3XHD zEgFSQ8m4JN1KA3k-l=YP4Mu$c*$vFX~~t?!X2?K)o90GlLsa=5jE_RmWp3x?hLetZLAy!+v?cT@l3|o?7G^A zW@&5!C7ydE(+rRiDt27yfbBE`Ey0>FGF2{SS$)3X?pu2&dc*2jo`n)+?7fwa;QBVP zmp}l*mIK}Ya=%*cbib*kch2on;%Qg(A~M-xbkS>}oW@TMW}(oC#Q)Y{QAT2<=c*_) z3JO0(gKJ`Y)<1_&s@;z$mgkvu2GpLs4(Viax|JQYPLpPNla!!Wwc)5pypG}=3nL)`OgI5nBre9RJ{^aPMwOZ~WUZr%sex(>kSzXGS+5L)3 zS3w|rkFft*33R9C2HuSC|7QH=T~XXjmHr1wC5+Nc3SySG?=eSJxpY#Kiu=n&?2e3X zbcCz*>ON(ARv9%d668nmddyLt(N?mKOMaLl=p|M`<^YGGa+%b>(OBqOK(As`o zp<5l*g;LnCMc9*;rgJ2I<%k*Rd;~>X$FE#9-CyClyvNpBA^@uKiqkI8uMw=i{H01k zr!D_{B7fPFr-Kk!#pt7G=WBg6pM6KT6}lVi^0)ag>bnrn4GXJV-FB+$g10jNPi`40 zos{mEj4JGPirC|NJoOuKlo-&qsbCLOM@=o0+YA;}JAa9+e8HQFQDh?CMW;gG*KI7_m=e|!0? zqmsZojYSvQ=)?i|(~V;ZOGv9IgX9o zEDfUBby{qk_oIKv@4U5zU%{p^_3PZz2Ja|TPAYW*b(TK;1%>M-+}cj>aDaQxf9Vv8 z9xpaU_kS2G5ed3yn4L02D1isEAJw~jvIQ+ezi98)7-$#{jOMf?yKOSt2N$}SUmBFE z>2sLYnmn9SeB5QxS%557T*7h7M$ToBk#cXYmp zb#(UsaH-~Rm_R8~dh8xJ-bYdIuq76i8c4Tv2T#9VVYa$T{aZg+(iC}}ZZZR9?4wS} zlU<#@#;=yN*|>X;xl+1Q@J*J25t7##aGdWdJH52HM=$anAyXfiXp8Hk*_H3?F>6`A zZ|6X}%&KJfVkD2Ju8uW^{)gO4eeeFb1w>I+Ft;IcMLzx-qiYUOB5No5j2ZPV-GvPAn3;)Zb(U(R2KZ-ct zo~4w{Os(ZJ&L^oEQVUq3flmEkWrL8%Yp_(4^r=}iu^DgEl%!(2g+hO^mJ zA{*OvGLabq!2SeA!hc0>UPO{IZ}ztR*b1am@l48_`u=Bq>6ni^ZYzm7$F$XSJ`j`d+!s5_ps1=8aofv(N?iZtNA~E7{B2^S#=`L}tw(j(KO<7f^|_s`O&Ni}}XGJ+VHALHY}Ta;X!|L?e;q zwoiAU*4-tU_M`KP-w>BtN0Y<+yTxXw+HKpZR&b^f`bSJ0%WPk64{M~*NZcV+Dw#jD zXWfsp)J^%^^Nc1KM2C{*eJlbx{e3C-Px~3ciK1TQwyOhiRJE2}QjbNYPc5_ZvDEih znlo~_H=oclo*vf688nZO?M00`yv1({Z*Y~vQm4zx@o&Wqyp`w}y1f3j$Y2wSZ4FB9 zPD)JuN*q&pU702N6h1Z?cflJqks`5e@rLH$J2MFfPheFnna!Im?#p)1y~0D~F8xLp z{rb4(O^4dqNxxN+%;zY6i$`{vkzC*4nf6{ENlX0Ilrp3cjz zzk0NG$EW~h$)zR1Tfp&}3Phgc;bqbRd7!Ew|Fw)V!oDw0GYRZYDFTj$XBw(VkOz6>?2^Tw%LnFTUbn_9)T;O#)Hk)Wm}y zkr@*S6n0cKxXfy;6C328lcWm-1=IvoxjpZz4OTEP4`=qU<4JKdnbdM5=&O`5`h99? zG=@UB6E>4Ou>A3($1Ztb09Rig$l>Z6=4#UEH;R*_ zIHF5yu?3Onv1dl+?E8HAcd4Zyxjj6(YGGgGHR@zQB6U#l-hEH-({Bv;N-8R?aFLtT ziB&C?%s~WZ$}G?5y6?E>%XmztBJXZ`17%cYe#b2|nfZ1dF{+${99fj1K9zf0g$W~C zSaR9^tjTmIn1Fgk*8LI>`USLwmZFDxtoos*ga(4qOEA+=iZcTU zdz=Pi*&AQ%FMNmvkaHj+y|H9Yb|rVxdoeMm%8p-Dci>V8{Vmb-Uu#4U&1+}VOAO4d z7NAbdcCuD0B0PhI?Vi8bm6sGm7CWVBeOgS!q}+Y0n2IFbT}i92s~sL9)%m!e(LL=+ zBSr9%GI8xqKNgv75zFvLdbzjbD;-xV#EKAL;K&8sA-|$Gfhj)>?eqSf1#WEE`!~k> z)ll=FID5>vjfyGEnzK*ZoI_cXriALBMaGjA_bW}GLRG$GedaXnar~3XX(GkKJt$vk zFqP=ASilpm;6IiN&K~g9?oJ&K^Zwd3K{q{4Q@O9dl)85@Pq8O(d4Ix*4NNrKV1N+R zW|&l!ZwGZs(}NciyHTP>8j?Q~r$wUm<1_t~yqz4^Nm0?)$nJ__<-+DjG=|Yz+Wm}B z&K3+2mNB|IT5*}mE4}EyJ~=ATu3jI1FNIOCN4bK{z^RXg zC?BIQ^UYD43!V<<+keXjdJA4ev$iZ9U#}HzxsB;SEXiKBwyjXjVmRAh#2%Q2V%o~Q z1oKDW*ofL5|HcL%1VAbE{FIe}5pfSu1d9A%C}2XXhV>~UaWh+P;rAbpg}Db=NJ;h` zU_SrDE@*kl97c~ysBXEjfI3T4Q~g*?xfq}uoFaHy9JrMguP*Tc;d_2g7#L-PUvrsN z?R}#($h@c#^LL%YrZLxl*JSd%>AoxhCMfI9*AAaVo@#UH*>(+>ljKi)-)TOt@5gdpAXhsfh$3pb7M*^X0!&n@5YTtDx- z%GsrU^_&rCkZ5F;-ylfwL57mn{jZSYhAURi%b9ox85Vl<-V~RlzWg%?r;&r zvufMe!uJ*$TCf`DJX7k>PT8y@f|#@;j3O*h%||$5!*{u}WP2l(4SdIqY>n!Dus8_& zXxC$GkYoA@N9R`q0q10lkm{v9F^tU2URrJs2>+y7q4YwNf31D}`J?`^L?DEJ5U|I4 z>T@t5Z{MY>sP05)MjNO$BHf@9;lUqvFiDr!BO6trn}SK{p&oJQN?xW}d4fM_=&&Du zH7WD32Wgw(_nIlMW4lfP>|-H2j(R@al#KbJYu%PfZqI>anrXQ*Q;2zQmZ0EDtObnY z!_|G#gz#>Hmk3;*=V;r;w*a_(6_N7bN1dWJgbChydK4D*IjZTy3zG?ABc=P$J413M z=(0*DuO_)Jv1rL(Jrg}DR6;?dx=(`JV2>03CCplL(BkVi+IT%8kK_4obw8muvmYGn zsHG`XO?Z;zrVYQZHGHGZ<+JIcP|~nH2Pm+V^r<@u~3`3T?*? z-G&LG0}iRRl(i$RUK4V^fG6d|=j(0{12c2HD?o-F@XzWptjR>mI@1>{oxE zV!8M?%jT0qpyNur@zN*HdvfRf*m~(dZW}#M`j#?x%7)rkPZv**AJ7bjG$eQZ?9Z}g z=V;V`rZ>9H7xz+4u1f%D8M%xSmE4=w-FNEm?mBC&3lqgJRqI_`;TyyCH9PZBC3zS8 z{4<PQk*7Zf<(PwtK+}It7;`(U4rE+Uc`{|_AqkU0%r%~86i8zV@b!B8fP#! zVD?TVzW^k}g_L)v?TjR_^@~Lmk2Q%^miD#e8BVu$3K=DMgz|dFf+ME(W!Go`e%9yv zalK06EmjR2Gm~NsV1!JBpQ3lsp_9Ru%T>NO8@qn8?^uB0)xlDk1m2V^WlF2jP8%$c z7V?Ve_j*xi0(pQW2K)I}08aASowOx?SDZISKESu=VEL0ET$rBn_u~#OF@=3}-#k6(QAbs`#4OosvO#>KQ zz4;?q5rMq-^E@6xM=cgiWu_YqrotwJhP!^!XE^06yc@L02cezoi`6I-O)H=VF+!r= zUmvFsA1BqkPJ1f37+M>jy!k(iWl9pQ;6qrpGtntw7yq9-BtIpf~TNbCC6A zhSA!rxaj4+%0v#e4-g^%V45HpJ#ssvQ%L<%Yc`oV<~v1}5YP!Fbxi>UEhU{yK6l|c zzn2Lm6u`><{zRXV2m{-8MX!y?`G;N~zsH{m!bSAV6D?v2fEU^gr_r^DllIUj65Y$7 zmy+Z{FuF1D?`aGk|D%PCcCMG|Ja^c%q6B_1Z|u+p=})D8SE%yOBvOS`urb8n7IJ+_ z`&sgQ84iWEv4HzOb7v2Sq5yINaHrh01oMAcd}oBDq9Omus;-#}kPSF3|2#+Hp>tVZ zZz`}}u9&-bX1np*=7wlG{UGODY@}{gmjhg)&H=Wp(sNh3TN5BZQ$u~Vai3R_^8iSO z70(lQPs;{x(zS{GrBn@Z-!F?1w+x2%~aU{gsR*fv12@F|8)-f4= zll`o!0+2BCPjsTsY(Tr}OYfJ|Ojsb7 zged*^XD=dBf`*^;9i@iVA7e6Z6^DPyV0@N8C~PKojRr)&vaXDZVZHeoZflMZhuoVs zh0cP`CRQw*EB08k6=9n_v%9kn>|@^V8*Dd1PNeHE$J+OQ#b3J5qAh$>Vlz-0d4Ht_ zUNXW@8t6K%^m)$+A#(BCI(-`havm23FVAh$k1yHtBW&}Ry0^5>J*c#0(~d1=AGkWt zfYZWw5pMkUGq!v!#7Xxz*b(oTm=pxeMkML!mmxe?A^_e+AxUy@BesaiVgd*~G){#K zkmW+wM=(kU$8bm(3XBmCUKh!|hyk;kVH@aXS0k$$h4b>HQ#^`8Uf?p!@k);o#=GK+_ZX*0k-tD0@L zsEH+xJna!vo4{Bv=`rDIWXx=#^(?}bu0&0uq5g>YJ;Y}yZ_#5Sm49}`%tChFsL1?Y zN}fJaYA6uZawuYf=5Bg|UnZ#5=tAusjo2hVLyQhzcxIH3mVX)<3K%tKRqhX z{+6b3dxH{N0*8=`>jv5GqkW{_MYo0RlOcMb_5xzU{-M`z6`G0|?Hhz}OH!=)(j7MMRk`O-a10kc6tA|g>g=1rJ{mANGI1@0a#-o}P6 z0fs=gi1sTFeHh7&fzyy`-tW^m3x-AFGSCcWw%GoFVf9Mo9y2}UwRpqqa~)!TRauGI z0)Axk3zU32pf0KbY*mzCA@X;qE;Cit3TcrEHb7&>7~f!26~MiGC|rs3)gsAka4+y~ zb3e?+dM`}+lT{O1@pMyKl{#%;JK^$&>pX?0G`Lg&SiQhz;8zPT&`ur)nvZ~ae118; z=5-iAbCULVUT)P_SFOOmMe-V;9F;-c$(2SZ-HQ1f&b3EfClQ~n+doP^9Y2*Xupy&= zCOT05DzR65t_h>FI|0GN3bGK=?{YfrKV*bU5A{3u*E>(OmE zD)0(Enr@VQTQeL5vE3vN^*7%K6x_=Bd}DUw1xU-9HBk{5*BU5YpA$d|=y2|RthNTj z79g9@BwheSPia#x0h5S%FW3L_0DSQ}tP~+FJQ9ls`HXg>uaHO&E0(uU3G$20mp6#u zDwgBwJYB)gsy{~1ZuL^fvsf|PqD06s75cL}_(_x7O1WnDe#1q^L`i9{`_9geqrl&S z!M`BHLXC423DT`O$UC!*geiTePKZlZd=i0tBYTH9^_Rsc`)m1WkKre4wlqIA5c3cH z)3Ic5IXOA8x)S6;({^+szwou3Y$!k_hVbsjwZsB*^*z0#M?q(3YHU!*Tc~Sc**j_2 zIlp{$YRLDemuHetL7BIwahTH5vPxZqQj^W)LaIj@l_kic#~*KF&?C;>E}yFc(49_= zy!j?m^l@+mw%O>Kq3- z5L8ywDbuTxZznr~L;IOP>Cz4ne2HWb8fHVd49*aa`FzXlj6jOrg}qw*B)SF~qrRVq zG(ivCEXLu~k0+_wOa`OuC0ORDb+_YB*VK$0jd|j`g!R$NjDcU2)_0o%Mzg^T_yCS& z#Y$yk5Bzuma+gbH1q!hdzD8_IOlHMA9NMrK(Bi~kt8M|u7dbPjw1!M-y5m&4OKHZ9j2~b_GaoT(*Zq%r7`m4mbdxYW%r1xrHCr5?rVV zoc%Zk^WirJb(#BN6RrsDe#>xqcIml@xsn!}{iVT6mLl2}zDB@SS7mE=sSkTC?TX^)POJdd6U`4mZfLX*D>Q< zZ+>6%%1};Iz#r(eJ|>Z-l%elqZ@jBsbWk{?Pm}Z z3<=MXl$jt#Bd@(&Q7J-N!a9+qkqM(^x^(pIC>HV^EXhJgX+Vu&S_Pph5ko>3QKl{N z=(N=MAq=&SK_UkEXSYB+qbWwN?Zd%MfUri7zH$=XcoxJ-WY)|z&;8_scyIRI!8!x} z+#Y7-ayUBf#h56+ow>|FP`mRE*BJ@}q_K*afTK*@DBsu38x6@EPJ=9BnICA7nuMTF zOtD!U^2nYx9i`(zI)|3l%(cEat%o0@$-2`r*dm+y)g{|2DSy7_G;G$o!b3>hZ0li1 zcy*BeFjW)gDUjccxtb>{tamSfPFNGXP~r(00SddaECayA9Ml6`1ORf*pry8CQJMk* z^`#F{xD*wACs{?mE+n!+FqVtC@pJ6UA5)4nRkniq>pK)7B=0U6U)Ii=>sA`bF2!~x z%uu_%l@wFUOLW=j@!(vQbSl8OG{znZi{AY0O*#8lY3edNk*8(=cq`RU=V~Gy-L6POU$uPmXP8eyM*EYC#3Lu zC_owJ`erkww3D$67r3>~T%%eAt2AHSN?nOM^Yl5w4=U@MmZJTbN`Ui8X1669UZ@=0MjS?CcT>DM&-ymvx7V@LloVyJ|?pJ=~I5psud^o}1MO%*O<@?o0$v2LhaWP(nXe#K^bIlJZQUy-f(-N;a_`o4UKMgO59}ri(j>Bl zkQH`+IF|71BIAr%kewgC51#qM!zN_|Hw*XBfEN)&jqyE@&OmDOpQgb;N2rl(>x-V|7|Q6D zXgnfaepsj9)pUR>myruC?;cn92kW6VLfgmM=+)>WhmLEd@@kiTe8k>t_54nA&CTDZ zZb^938f1F7%x1%WlQA%d+8;irFP!K4m@=hif5$LNdivYP$$#=Al{?PX3N#X z-h$xS69#f0XV?%boqdkoey?+(jQ?$&X&0?nR2V6(qx&8>LQC;NKO8l3rObQ5HVTcT<>{rw*Zd?jhl>2 zoE3U%Z}E9@$OjOvo(`yIx#l-qrn!KQD|G_0Vc&puTcX+87vITa!~~??YKiRTWJDOyu8lOOykoyvj5->DDwu01im6S_{w1i{0T=Kf9kMNRK_nnnJQDHHRNBxfIC ziOp03`sy!j&S=+7s+ZM<3}&SRXF=-%m7!*ztN|&4izJ0S2Dhmw(kTyKwoU|0lT%-K zeikbDp$^)*m0dkP{0vuN7ATo5gIO5doDWDZ)%hd_`s&`12RsJRRtH$2>l5zwBKPCr z73K$bl_XTGJ>22mj)ow~{tE$C{eVVfaap!u$*R?TPNda%3hiwxmv%o$oF)tvrs_q# z{Fa@);g!ZmHYsYJpS`h=i@~27MF7uPW{4+_CA-*e|x)6*A(-8)bTeVc(5~L_L`rzSZg)$})-{VP~mG{=te*fOx;ZIoFo}B zcgTrf_1Ji@v7iVyZkAB$`RHEOiZBC&a+9tD2XNc?&pvJ>y*e&IyA{LU5hl!)u&^e@ zCUF}pZh>vC6ufTj7B=?a?jrb&ZrMU`-ScxKy?V*3%IbUxD+z0e8^ZfWTIH|7<$72c zy(lj>g<|5XQj$F;$RH68abHglE&Y!G?3Cd(h6k1qd~E|6ixd5)ff0N5@NrR)uVo1b zl{@ha{{<2kO;Ua~Q?%Mf*cCnP6y@3p2F)bBDGi?-`@oM6cpSr%{_NOr!R|IFoKjr; zN*l$3v&n_|YD$dr;rL4^_7FGTLhPL)Fx(BGY|dYz45VkyOilFpUGa=oR|vVm+M4_? zmYz8*GVja3fa*U?hruHL_%RB=X;r%7%_TJPMmcP^ml}{d0?tYFTE8u;1||&)EAtKniauOY6Rm>%Rp?$h}1f@Re@+ zMa#UFyP?KJ7z?^h0wQO{fl3v~dof-iBmI6>j>ix7jmcu+Pq80_CU58eGs$)7UKs5u zCT9CQFH5cP)~j5d+%A9~vVEITqSW)yy<0pS8>H6NHiPFPD-BkmFAmn$62W>zx*)ub zuQUJC2NI-4Pj*kgj&SQ$bT0DZZe#vn4q2{*2NNn}Jk*wfmpn912}qx$U{zU&>V$NF z)C+|6ehJ6$i+6vf1z?Kly4H;%{e529KZ1|NtdZ|o@1HO*XekUtW47(ZI631R-maqK zCXbC%0cz7HB=&0)D4&FmxWZ%ANO$Kpw zk3_jr1)2gSe76Yxs4Rz=H;ZAv8%l(+pRMqJWj%e>-ws8)LQ z0)y%Y7tenj3Ffv&@vG&V#Z}ksp-RSxUTee_goCwBw2LMfsAKR5>Njf)r`Eg(kVfco zR(PcqJyKF0A;nE<)otU9QylB3qhnYy99r-q{>+O;2_)EPI7FZoa8$5pRW*i(_o7Ix z@0uoVSeYd2W2X<^49{;Dixt33pDum=VIxFSVt45TG+1*P)xf#Cta^?ONypRDWnK>} z9VxpdxA;`Os?t%wnsZf(njcc?!sAKy_se}%uC4G1L{SK&dY(u6W;|~IL|XuaJXi-z zYJzB%lzK&gofn#oReq)U^i4i5J^Xc{2}<;EWTntn$8k3aiIHm$8i|oF>Cda39Iw+M z+&yvIh@jyxxR7K%kk|(HuId%5!6Ll^9_zb480rP?U_K>t(>EC;O5|4M=ZKg&RvLY96=6t_6~(d-g?rhz>>b;DC=LZcv8tKf}4wDgwHU zD}|gjPmQrrsx3rhV@gS7UTH597B!mfvyYbxGbYLB$GXCM)`3XRlqtr;;JB(lHECe= zToRx3^-{S$`=BZRg%16*x7g51ZHy6AqDOn7|NTK3mV>b5=g1Xpd-7!T#o zWhvo70RO*9K361)7u12Y@_mH3iHu70<2vA`W`2iRjSaT(W9VH${2XfXBb$Uh<8kABv*yroLKtl_>&c)y5nw_(%mqfRcNK%#hShyU)!A z`N8*_)LIQDvV+Da`m=IcH=Q=%(xsu!>*glrM2qv^@6ASGPSHuspf-a3h4Gv3*zR( zRp#D)^`wr1fNu=$V@2Eb&Swd07FU#GCany`%%iDSlJ_jW?39^s<>21AydFbNOKCG; zj;KxH!LmQM@88gfafv4x1+`2jiWa}mD_1W!AixaiVkX!{&M~UX%KXVy_meEAG1ySz z{*T7>)ib4W`~GXC`+9xojsL=Z^VPB9^P>!dm0jNeYle#+EY`2IT2rS|g*A|N8e@5K z1Hes*M#i;Z7;hD3J$EPL>h7`OeW3_E#zn(<_vTE?3f%b&{hes*XarX_uzBUP5)#p+ zecftmcY%@iO78R9-cRVgAm-djTD?m$g@6P{gu#tApjA!8wRO&=XwowDB7xs|)qtGe znX^_Xh>z@Br|vvKxp}?+aZsT5OlvGFtat?<6RT89`MueRNR57V!IQo~1&0A$<2<)A z5vj)XRsKo}>2k{6uXjdg6jkh^jN6rW3fg&6lQqqDaQ$K5S+$w*^iOB27fAys(Hm)? zWac6I@myYs(|p$_g=(?k->eN=V=J0r5F1s!4!fMjKfh%q zo;HS`O@~C=X)!L%yM(dN30nPlI^;D~V<-u}x&$B~!H?R0IFz(0He>h_-s2zPg{XFq zx4(YsfSvcR{2tN3g^p-@bCm_q$;Mye9?$j3qy}I^8px#8Q@>U^ z2E@#(9Ky2weEraY83Nh1wm;Y_pXs{T6?f@XWcMF3ho>*6Rlk+L!V|<52k`jUnG1lw z>ISl!?EE?rE4gzV)LVi4hF%zIH7TroSFVUZ{;D#2A+AJ|SE>BD`I~wi$CWz^oRL4l z4>w17xS2|Pdd4P2iS1aY9PthMzODT+fe%c3NMEeGeMO zvzu;$1e|Vv=rMeUvQ_5*FXVL@99rWb0XCdGpHJ!nQm8P)0@`9L1LDLMlvBm{i51n= zE;sh)-TQb7=~jgoT3GhqGyb!MLqOhpVsaz|_3TnzkBIp=efI;&0J*`%v=*0$TAs2= zNW&s(&VBvwddJcJljD~5tBfq{^r31W7xoDWPUB8#1B&E@ig}tgx4)|xmbxheWq>|5 zp5Y}%-A&f_&j}(I=NgV@dt3J_uV+pv?-UUzJ1k;__3?6?eYledKJ>WEmozzAXr+ZX zS)~3Z4wxnA`bn5S_~Kuc^)-AelPZRkve864K5NyL`JD1M2?6l0iZ1$4&q&Y1$q$2c zy#7WvY;5y}A`*`Kn$&s!mgVcKRCSLl266tDHY zCHN^E|L6EhoBs!NJTlsj%L6`{{fw#GYK?5XzFLo~)n{h54!zQoalym>-`ZKrS4{$Y zJ3)eKw6$CN8uIyr}R$Ilm!>3GMFCK6Ra%^=wOZWUUCA*0;m(yDu zJCdvj(~Ab8&|yve6?Dl^=Gz&ZhX)XKT|NS+2NT5atY&XH>{V{JKPW#WocPc$=XSX2 z3py_`<}@FtpJmjP|FGy?_?+Q2OBvQaOfW;`kGC%tk#h`XUh+OnYszUJuG6+j`GpI1 zobRj+IGG%9KiDGEZ?Vgv6xi&AABXoO4@3^@4wn@K*6dX;eAjuYnLAe}*XlGB-po=y z86oHKKy+r(c+!?-f!EJ$Z|u0##1(lWC&FWK|7TOIG#6mG+a;?qNA{Q*^p0tN@0svE ze~#Bjo#fJe$Ei;#Xa@A=UN3K+-}4}8N}##R{CL|7f4uZjCrN>Eh+Sra&%c!_o8gHB z9);x!IfXQ5p&_g7mHQwYw2kgiEpjC9JhR-79Yk1N?k9=bg~W2i3M0<=7pQE5}hpGjkuM~JEI|44+D|+*oG#fXarWKxV3i?2Y0$SusA;+X%MabXd z$KHX+L3$@Y81!m#z{d+(CAhcH)d##LDjOflj$djtt})T1dnvfgd#LziU~`K)9z?yr zB>0x=9Bn_oi_i#%PBInZz0!1S#;cxz`zSn?-<|5Hric}1x0o)WVJiM{amEFGTv#Gt zEAtY*Sq4SOH@MSOdyv$^qBu+nN+G|ca<}r_4)C8D_g|Uv&jb5kZMkL9iu$Peo!<#i zw2~3G)Bxr&PPq{M~GM|DUyryzTK+5`i zLUHiQ&^py1R`x?R$>N&9@hdp<|DozD1L9hib#VrVKyU`PKyYVp4<6jz-GaLZ87#QF z1ql+|-5r8kaChg;-shhC-upB2W3BG4u3lYLUHw(nIqe%}BS5BGtU*f>7*nai(^$Wo{;>pdqGV;%hU>v)XdZl$lNfi4FzeuAP57-*xobmG(Af)z zo)Gb9BXBLZuQ_a^lxt7WZhp^*TWMz6LbLAusGZK!LindH+5)Ik`h{SgW$%=L;V~oK zVkCUb{IjR7`H;yeevFWV`}J7HUYaw(Vl>}XDz9+OCynYd?SZ?j*3sF}91JbWWR6TK z1+RM|%OMj|YP}+ZN;>&<+XB$SXG^|W zmVQhzP}^SWP;pP=_0EpQ=T!J;L06f7Sy>-P3BmJ7ecFK-rDa?!(Qyc@(m)qH56N*Y ztR8^-c%lD=V_PPVo3=mpfI?>Zd2lUuaY(fR8#bp?yZcJU>giyv$zt}EDg-?q?-e3L z`LWLP_?svH4OaMxTBWv_yp&=NZ^atr$~%h;Ac?=(er0{VuE-|0T_j$toy-;z~hEx*L2JQH_4Ym`qGuu1K4ww*Hwu z-SY+i)PYg3=rCn*fX_KjXt~|!J%~xX z;(AC)IiUC>Rsiu!wFqUA6A?!N``-))N^GW?6IR0}R008+CsZIci8mStK2?m4doq0; zZYj&RX@Gj_u{~E%vk;?JgTgw8=flNLQV<7pj|4Ei@wnV=y2ZueXG)fLyYEu;@D@!q z;3wwNLa}b5`Iv^$x0SQkC;b(B--c|yTVUn$E(>hyYj=2HQd`ZvVUTr@lwvmhsXWmQ z5Tu=+##3q{jTq=#5%*fW$2)>n`u^93+p5LzU`b}C{oOI~Uim4-r+_op&kAqd%Hk?C z@?siW#)YQf9}nC)x}f~Dd)%^jj_ykLk`m|WP#+FZHhSs zlPbyNCNyb&tf~z>a3S6^1T4mxckk!|h|=4U>`x@dllF;RSw$6PC`W(C(FJ68+p~?J;M}i!oRSIv)4<+dG#r;n(xp0=zP;q$^&tMKqfxyi>0l52jQ`kvz$j zeq)xII;FN`ar#^F#2Okxo80x7JrO&(9kTaD&rh%!J*{2@_1a(mv^Lr%W-)GiD^k>J z&Hkz*tLuXMbLs_+g5IOaBoa}UR9u-<4doE_hWSiv`E(nZD(M$03A@&{q~0v>1ol-Yi7Q$XczSM_YPOtl zweBZ$_3|S-u4uPb+-g`rP$64ebAeqsR8A{~tZ;1L9<`5I(s`VjLGdReL-Stp4_XaR zf0T}+__iKqkHVAT`MoF5ndiHIi$i5GEV-|jQi5cnhqEFBL+JL1bf9DyF%x>f-Oz~& zL)$${hq~l2h~| zf9(gxf_JF_N$lYbABiXiWnxyZ%=DK0hQ8Gw0`U<0Ey;*-uvE07xdDrBkKljE-ef zU&hZBo0m4(mRXC5c=}oMVIp~LaXEH@q4?cPNR}JfXC?vHNz!Zy0arB_#Z*_vNlJ8A z1auNBK{$Wq>dL>LG&HtlOvck&g>(4rkj36K)r7^Qmujq{4gs}a)-@$~G9AUMv8UFZ zUc2&opS%;vV-)dxCUm_euj4#ULabS)zKH58X7d$H`WnLNyLp&YwW~gUr2Yn%>6^<+ zWsu}Pn|$g;GIO6aEX06qbeeB2fzmiMBsYcY>yZE&r^E8wFZftI zSO7#+QU~e7`WghBaWqkyAaA8s31OGsYk;+7QwN)w1SN`AUr5vwdCdo~W3%%s?!2L} zGkFDI6uWqQx?O2*c8hXQ?ZVn(J8I7o{uJ4B;1I?-fS{Ac4zv$IQplrIpJmIFEA&puuqd-4)=l?MuW` z5w2IP$BRO`p@sYU1Z^gaEt=Zu5sKVnsa%?&v&SFcIL^tTX_;O|8Nn}lEd<%uOn^Vz zACGrkuKkB#2PJ1pBy zl}27=W2Fk5f~9H>@fPxi8O1Q^*+qp2B_rON=h_R|OlI?L)16Y5H z1v+1j81z;HOIg&XjPpdO6>r!FP^kcRye4rS7k?~(R%yRYe=sE}eY+bIVbdxMIJr2} z3B$=9He0XroV3$9ZzE3Aqe zh9Y6`m@qrh7Gyn^u7|TX(0c|~!sWO}pVgp?`y)rxG4R4d) zgqPM-;^9kKL+H56>LNT57JYlbE*!ZWBBP=DBxx-Hhyu-&KTl^O)?9LhbREua&M^x2j*dr-&CE zmgI7JkrZoZn9C*Op+rK*S9T<(q-T&$*70S5tR~dN-xcFDXAO4~AFRj--{G%Q)FAOV ztnq&Zw#Q5&))W3#-0-a%k!RoW6<5M>BwGRwGySdr%Jovtx^1o!-%~tgh&Iky5FsG^ zgl}NV%U>hHOC2umsil0Ni>Kh!#uDRP7Eprh>Vc3$cvX!llh2>V$InJ z*GSNbv45cVNFT&1f4t1N^1WX(P#q21lnVe+28c!j_L=pSV-gIm_b>n{05?n8*R7ET zqNnVSaS$sjFQm(XFx-ml7Hf`$Nw-#VM=l2R^&OLihz!G08511|;_s4&_-F+Hf^IUx z>@$m)w4Dn8QKaEJgA5Lrjs(o0HE)UK##5_()V@JguR?08BgbtN4@_a_vVqd2Zk z8M~ySM7Afk9Z^+N(0#fNwH7)-!gqsZgODRUj{j(eR5R6TzFDJb3^Sa2(V;HbkBD;#dV$+hfbna)ufpJ8_xygbnX_o~HZzS*GuFXK~5 z-xQ8V6y*Z2@!JpugHx`^H8Y*^M!cNHDbV`rFEI(jGvt_XJ{dE#ddTU(C)MkgR8}yPBMd<0==tzTsc$ zAz>NxX0>MNg5Fj72EUD5cm%GN^X(AZ6}(ap%)>xHc~F)0x7Bm&w$LaU|5+p?h%3u& z_mZ_6qQ8hRm8PvpxL@%TX#lVcFE_pnjCE|Wmv@r4{6R>-N!KF* zX?U_KY)nQ@1yW_L8ruePnAfi-IX8%Isk7A0y~JzPRx?uASoCCF5M-{EHcNCGhsrQk z50$8E22;CRCuZP73;q`OaxK{jQOM9Q{~^-UI?Ned##`JdN%_oTecRU{sy)5b2~n>X zqfRU;tcU#g!Pju`r*4n)*Fs-qz;kN3QJA!FoPjB5{7f+#iTx8&&8#R+#ie5 zT$>FMlrI?%!<3I9h9a3`J(m)qrTihvuIvu$=DW2uZ#>8o z4m(j-XiLA|Cno*uwO{J%AKEY$o8x$abW3=|SmNqSV~X#Nw&30&I?tDx`oBjpI%?PA zJ~!SG3vtuf4HGOPRDv8qZvk9H<814+=YHGq+;^XzVEa(m7t^lAcBe6^vafBX$Sa~r zj+Pd<$u?4L#IlhrImkKMz=yoc)b;mpSHo|LN11KL#uI+rxOxCg13CRNhWhxzUV{kI z;@nGeJC?%1sDkPQ3%`;j6ntc#&I4M@%^<$#N7;5Kbk=Zu5uc#vkl?^ZV?V<-*2aU9 zRFs{>fH?iDh{LA&n`L4D>!Ee&SE1!qzutk6`k-DnBUBW?8LPhQR{vZ)34eE&rV%*; z3S4KuDDqX)c`hFv${B;C&RAv?E;s-;&1=5}EgAHyGdYyw=|O)M@g=ZB?2UFZv!wIT zBF+NPhBn3?&0h(m_@7OUTb4?BLSbay6HEsFlK;iy62}sg(vyX`-_=!Ji(Yp@7sZO! zWjr=fcXnq^SM{CgW=slY4y^pOSjsy3?oP=#{d@6;7^74WdW4#a#+^3B$+f~dP+xoD zg)@^m=Q^{dH0)+XVwaS2 zO(`B!5}ckiqj1`HCMs|gp6KWutv?g;&_t>n7rOVtATGZr_Vn?I z)4G!OH}`ot&Jj@t`}!ZNkp(E7@j7b0clwPge6+{Jg-VwW(GLNQwRj+qqvX9##hd(^ z0sM)}PRx7liXWmZ1_Q(abkjk%jYs|i2~rHwh{008@G?w%R0zUQsqs03m0J9ufXNHL z8<^N=xi$7E`)Ldzl~RAky$KWgm{nMs+*cfihK?YoRdsw@cb1=H(|9MDDvNMS zVUK>dTQLI^e|dZWS3D96OeIKlT6|fCfdD2PxBC!`a1$cSsBdEpF?fSgYe+wIXZVQ? z0Xz#qH%8lo(HTuk*GP|qrbG15#xgmtj8n1Y{p#a8t=3#$bi_7a0zq|n3um77yVKQ& zlGptdy_?F|y|-}q-g7B#&f)<>BH1-Kn+I@5J=OhE1?XGG%2M|h=`E%?Y!iM%3@Gq7 zBpC?rgVFf2azs-}v|tm>C7d-kC$rtCxyqFZ!ea{84H5A^KS8;i;wBNen6%t+U8~qTH)}7S zjc><>=7uPjrLv8x3xK^_v@Q^DHYJIt52i3y@ZEXnua6DP+SqCwzL?#RQ+xR^{rxnL z_CM)F+0Dm)yak7adSaf}lGu;br?vRcC<0%-(Pk;Ye?N%<@k+y0g*^Mp4(P<>hOwP) zN#YVfqCWcq3cIWY>*~{(zZT;ikH)kFieVK?J!nqne*V_)e4OC_($$jog;KZEPi&Yf zMl916{nOJK?RxpA-D$qVN0;@UuLqA`kFXkSCOg7XPcbsOat|qeWq~>k#}8dY1p@oi z3n=$bJXa4(|M(T5Kg^1ObP!;JxO-58D2r87skeM*NHIS-wm=VefskXe42GoQ_S@$; zz;Q@-yKv*;tq%~6y&JJdse!gDpEn)Xj$^%&&i;c9$h+`Hl3*$M z%q9l3g5?xKKXs#2l9Z0;)al=iYfl%c6T3NnUg>8qD{ zH`@6c_tECp(C_VmpZ5uArY76c-eRg`8gX*(&%2B4X>v++@&MuqvzIM?mB}{5;TRmm zs1JQux*&2T#gmW4b}!lAr^9Xu*hVz`4=KTnzciVPxGLA$Ny}5=uYA!xujLy6wi6jW*G|*;xxV0I6*+M>de@^SETWjyKDKiaEHRj7| z*Z2sy`*Rb*NC|G!m~P`Psrw}jMNwPi3*dJ3#8Ii85#T< zPqS8nI9N_VUtIAd=Vw4XJw&-zh9;0W=W}WT`Hh+mo?Jp`{MmLgNR z#QZc1&1NX0Wi$LlsB&gj_JIPdNxn`0aaNUhBX?+9EB303;nU>9J;*!+bmd9g0)$0x*omdjmslSqZNH=u5Qd$ri=Jb@T^IqW4IHnvw>O$zYVMcd^(Id4-q6 z0KsxbM~gLTgEeBfaCu{KQV<&s9US4J3J_Bw<(_xZbiNUvdCO#$6A;eVaXlymSQ8SU|L8DsHM;J$o%$tDT1 zLNwM2NMK($ZyGfiFldnOHGoa)G%Z*!VE3#Jeq;08gNcc1)a)=<`3?`|-xY}1!&=3zp7qmv`*4LqfNJl$rGvpIzcEOp7_oD4Z8-h!R?{oG06e zlrCL-0A4|_lvz%J00k08Wn36;vH6+*-g2WS?Slzs5CZvNBzC&!6CjHB+c_f&*)F_8 z@5!vG!->)?t=5>KeUsk~38x)(b@?d;x(aRtyZ0*vd?rr(z(bi!z3pz+Wskl!0Lv)n z(Y`1gG&4zd#j}xjUKbY{_R)^AFuBpNZ3Z_^Ce~C$Wr)WRMB|=1D?cotkpB-{<~YX3 zIQ>g1$fe_KL-5;GrI#J8;1kFsRrq;Y!6TyRt$MAgEJ_${-InES0V^>XXRMXiy;&>)GVv<0{70sC+Y4SnMpxdwOJ%^x9P{96yMo+ z;H5$g)mO@Fn9G!k!t`ksu24OxUYDGI$Y+}mFCirsNoH}oR-3c5-)uuRYQcblDvaCK zHuWo-Om8Hrb|17@*y!)PcG|J? zARpKIk~6c-6LE#pshxv_V=;L+&tCKSZtZ5>k$y_R<7dRg0rk>+C%&tLO)2wXN@#M} zJa{9bJXo@TUxOq5`Xn$Y43_{7fFFP~4jrWU!O>C#)HVtfUG1T4BE!)#`WR?8bTCY> z+b3p^SsqE$jRBqZ(ZxHPpxB>>04YK0PN}GIAM5%vfI)#ZA`yfwWFCHEc7+&W-J49J zVhKk950cid4(khtLWkZZmW2rd4t|GRBz^Ax1iFVL_CX@6xaIlyE7mSQUW!8x&?#~S zDBJcxfsum#bC>r<&o9b97~*$2RzfbPE5d^?XQ%%JH0#iS{~7? zr2bnxnCuZ+9^_D+=&f;V*$*G~+1sD7r zsOA5Ii|$Xh_PLm}{&ZPUfF{u$Ko_#n0ju>D&2EUnu3lIu_tgq<2z4_RZK#&o5|ej` z@)qJ=V-D+6CQSx8>jDT{l&$ZU#Rfn8HFN5Yg}>^-Vx0?QYDs;mZQ60ScVpQ~Wx`Z8 zs3(}L5f1p6a2Ca3667AV8-FP+@PX$!o-4y)WWGbne~o>@7`HGcEak@LDtI9& zl=lj4ns%yl%1~u+H{4HB%gNCk_7$~mjm)!f!&%*-unQ!sXZ&X4@2#KKdgvYPHWOrv z-Psw-;%UEBuCnAK)J{G+RN%W9*9$V)R((bk3GO?5-|weCtXr1OTs2I1N+J(F%KCU- z!j8sTsUnG#wrAx071WIT<3|Z%KQ*@w34X;hS?_KXpUfs55{(@BWw1h7n|K zaO;6Gbm)#3Vc)+wA4E()WmAKBHjokq{Uq%NcR4|RB-ctfhIo*3Ma(_oaIxG-i!^8rC0CKT}32=FYaP8tP-Rmu#Hao z=)bJja5h$aGr-@PurL2jqwJ9x_1Qq{T6%R<46m$b}Ud<1o(_WkavGaD%V^xyEWYhakjvS@`&PBYy%2kgk~NiEy=W+ zEgFDE3?4&VL4*vGl>r-%yV#uL;$+(bFbq#TrI#ogT!xb2!?@naTK#t@{MnwB`=T2N zXC~IT9KrR09ArAHQk$JmTxuvnsC-|`di zBI*S`4IvC+iWbgmBfv^o=00AYGZ*?K^*dNgW$1Z6w=4)bk^88;GYVp{G@}7w-Y>Hh zli;3KLU^!PUKcS{SzU@xr=K41pvXFRHx%s)LR)DJZkU&OrIRU(q7EruyZxb>H+0hj zE+)3a>vKEoT8+Mq@W&4j1#(Om|ANA#pPZxhmYhs$lRRpz=Z<2>L$y3M zB=c`1Rar0;q6yh)BIboio44y$OKOxZm!0a6ig>X2=2hTxL;8g0wrOBU8HZrfBecV& zKQ*)B!W|q~KU9~c^C(ZEJ&N<@OzKI8&(hrSc;>B45-WkC^#urRTA743cicxO)9b_0 zN$wqL5WU&82Y)V&E+@|$9N!+hWs!XgAo0eVpKCBCxE;~1Nq4sm->cIl_y-Dmjvprs z^!P0-f3D|?IQjFWJA`iFhq@gN;RK6vUpJV%zMKFpnf@aL&h2n<$Dsao+{A`*_P6D% z{_wU-(Mb8X1Ph)&(4LtB2}2i_aDSEu1a{qVgfm|wxIVaOZ0}k|BZnD2$x!(74sStF zk2k2phWaP$(J@!douAwc92hx|JMvxvj=6xx)VQCmNP20d5vzLnzmXOWs8C4WE2~#1 zYh2^SSQLd4ivd0HPKgu@aX;%-6_-vUhZ5o?Pt=|Axy_2!Sj1mzq5~D;h`1ewC9Xxn zop$B{`#g}+8Y+0LeZzH_pVi@6(Cij#E6M}^w0dk8{)SmC7SlL|f^xfPB;o)25*l?? zm4=TUOLN5g-NT)F!}UAwHJ+6p1Wb-oh4^NyGvj1=++Y@#j-%7TwUec z*}9GN9@lJq`2pv;e}NTByWau_jOWgYG)GQb6yGXvHt#b1A)~=h_B@9B)YYt1=+ncjpH`#Lnzm3;E$mqToJ-G>5uad_&$zp zvzhQG58PO2Vcg$cEa4&12`iiz(rw&R-l*JxNq-^nmjr=9P9gG`JA0NhLlExtRT?q` zt?v%$ZD`98{q208>SaTJNAP<=e^~1c36Hon5fdzrKCFDMHKDxEn|qLz)>lHbCwn?Y zvYBfxKZ&H9mrJ{eGjhX*r28hdia^3A3#ik51w&GKmon~-OS;s-p+ zpz6n;wBd(cVXr|6Nbgg zl<6JX;g*<;zoT(w$6F5t%UD59n%lV1_aXuH!H-UkP_ZC$LBsS=8E>!H%q9b9G?G34 z)SaUJ%)U%uX>W9TS@UMk4n9k$SsUEFsL&aNisz{J$#ty&8Mne?q1O8|NEsl5=2jvM z@+NN>ICpnEOXH5g-qK4Z;IWvUO6;(?z62U8omOjgrcTfqA%XJv@0uvyQlX%2AEC(RPJ%f-5=vX0{Y(X5CspE zfV99V{ooO;g#!9+vH}A(b_&qxEtqY*XqWzn;7h%SQ^-@^f1hp&IOYroZi-%z^I)|B z>~TDBqISwIgIle88y)co@gl`3;^{h&JO7CY0^G>PRQWc5Sazig_P>Zfp>uPUZgujg7!Qp3+^MBA=b#~RON7e=>Ol@-<6^QDQJ#+j{7rczVC>J64B6;0|8BR>}K7Wm%J zY8WV0`|z2?9JxyWH`cgL+jn%JfQ$u`e$gymP@i$FCS$DofWsW~(QoVZ1U1zHeUmq| zy7#J3R;XW*Fhk0r{T-kdmR`3oF8H6dc`)E@P3P!tlX>l1x>r12Pb0Zn%@co<=Cl(} zWaTpC`7+%f+NzDeG#PQD`Y$IRVtO1t`qf>mn7s?2ENWeU-Fxw*H~Hg}RNLmvTYD%M z`jc-YVNGf+vY*xT0@%c?ebHny30!vHggO-ppx42e%JzM>y3h8lx48!{H`{QZ2DqQ) zpYng3;qrJ!Uv9B$9n@*;S?M!&;%K+JR$qpcQ~A%p>ks3<-eV<>#z!*x1Y=ri7gIEe ztHiWW5pn`&yKFYOE{J&0L5Nkb8-drU_c-@~@9X$L_p^0*Zin^462+XE0x9spRxqOL z`|o%2m)j=4BhUlmu*-isND60rt2f*l_I@txtQ>7!nW*z`poAVzVXN2e zCw@A*p;3)0U9A7@&e)0Z&xB$6IQ}QJ+aBLBBr%@fiL-D0hAJPzvt*z>?=V>n=rkpBe7cgJD`X>1DlI1Zz) z|M2^N*#7o=*dqN82ZyyI9ArVeyjfwuzdts2cw7cq&B00Yi(4NDJ^pHj{hzo0`HRgy z9Wz_r<-jJCcZAyyyzlaD#d$wfA@&fy++_Zag25mN%ICQ(-R|GWM+P$+beIxK4A6+D zlUel?u9@fkcU(wM#C47AsU*0ZEa_8#V93x1XM}bhZ7%mrkXjSpt#BH}3LBXsm`ic~ zmsaCie_IRo#1%QqxoE_7*!$DvcB$?iXn1fZod>9L^D-i6a03SfRJrf~IeYYxjc#rl z7Ar4>NMlp^LKpq}k^N^HI|JX;XHiSi6xe|t=Vxo)ko84vbmbwIE2tr%IAyU~*4O-h zIiGzhXWo-fLdd!E&t#{=2cPeU4_(<}j>b;O2!Q|JlAhUGaPiOn!^h1I{6MLc(S8nO zN*BB5u-@SRcQKzQze%ziDeFMSXO`T&gkY3!uGClr`~wLz45rv;C#N`Xc?BAt=?4VA z_irC8`_=w03zTb-B0B<~rSfCipo1?7) z5C6MnYu|Wo%%BRax+oVyqXj|6S%Yyy1tes?f6(KMYahl*=7|FS%N^U_Bp-YU^wjep z)K}YA2-=2AK^Dp-_DF*Hzu%0Mc|blhJ4nz6pZ((D(~=0YeuweOmNFymU^ZpT(0|u= z`v8AU&FH|$wyS`7g{aNAmdBj&g6@C%h()qdx-skQDS&)UBvRk<{&#xQZqeO4S*`bQ z5XHdrs|)h{-xfTLcHKJ#seyxr;fb{4wcNJ-kX@?12^sU>mc$tgUJrC(TdZs=L>-Wk zyzXd010fWnvv18jeoIWx`wrHvCpm+~r@tW4zRw8)F+BD!Ex-H?AkF=mW6NLA!T*r= z%7@n`eN*gpc?F-v zOG<@w3bVoGZnOWp$gP&v8!D4lF^pcD>z}1^I>CeOep%IKZTT-5A_)O;&H6= z17snVq7n5QV*fTlzwJT+t;&Ox-taQepNJZ81zZzro}inwJ;7?1=3xuO!XSDyN% z(lSS@4HnUJU^*{8zZbX-m4}C?CfYyeifR>$#iV|=@~{Z#d7pKDPtTSpo@#v;r{_i4Sw=(iDP-}T4!iUYjH9AH z$obp%6w#f90<>eaZ=0a2z?6i$3>rThX^UL;CuVE53M`i*rcdGv{PK?%t5*^s5rXG} zU$Q^7??3Et`bEFweO_r)Nx-oZSP9dpL~T`!N4kh8W!!|l4fZNm>5T6(0Zl2cN+A{Af&;hoqUcnK<&YIqhIgpiAXK{639C2lVGB`$=`d{|+B(x8U6;4g#v^fRgd zKs*sXngf)v2W0mB#Xj98BOCqg>EH+!cHn$0Kyr`~f^{h3Cbm8{F88CebF7Q2yl?r&+O zNb@0bJMy0`4CSudWzy3}?-3M<$1EyxAy0LIujzGnLI975xUz94y{Wv(DmaV|&Y_Z% zluh|rLyJ;yjmM-EIx-K^YcLY;>#)Dek_*8>f$%00M)u#&nZIhY)-saq=HU|C``2PZ z*Uk#HT!3k~)Jtgy?Z33+fta-0=Ca31VzYQe(mEd!&J`b+-Uc@ot2b(BKT4?JUu$+4 zt1)}@RsM$9r+!}`QUu-c9k_Kl5JOaNzlM;`=7xYq!j*u4Mxc=BpIuJ)UeScIoIyu- z@ad;^o%&z)7z9+pF9n0qJJQQ*t?mL=`fVQF+!AMD-g8$wgWwq5o}=zy#8ndeX1+9U)XHHLcj0$V6HJ$D4=Y#5mIE4ITq*pcBYq zBE2^!Zz8s=1=iL|0s+i3F{)8nj8vWptLAz6ZtWHgF~D|Yu8Y6hc(i#OegI=-l}^BC zVncvV+b(c=l$89yj+=vNljRN*+Qs{!X(8oz5i!fv5SedCfdm~euMdb7D5hJo}6dQ3Cc|Fraefr;ZBB98(rCkqq+N46DNf{1<1a6 ziThW!zX^2jG+*x@4yxN7M4dD@2L?tmPBjG!y-?LY3DHX-5^`D%bba^;-PLk?Fzy+) zml#6p^C%uD94Oi^N|SDsh9W!9MJMU@77cZ?Fb_aARlY5k4TS#7>G?a$O7vU`#$g{fMx zf30eRCtxIll1;zSdSFKjYN;U2WRpkGC;fnSK3Vtot1vz?ZpS9XSPNG*x#PVhVoCk= zcj!3HCww%@B!47_Qp3+a4zUxt6UEN-JA;R~Nz>&bICAStU$w0`xXB+Wthn-_&nymZg9P4oD61%Om)xLh^ zS~P9SLm@XB$pp$1iu^=qrq^aMH0vu+Rvm{~EtbeYx`L?s;k}M(;mvELoKW?U%}-&} zw~{pifiipOm=kjv>UgP{==NldnaW|qY`{XZ{_QD-2o=UMqQD4W-wSf9b@n&|sWf=d zj`7nCSS!SXjsXPiPGY9b)EyqZ{@Sk=8f0Z{ef_hA#Zv1z%4L1GhT$XU^8-(kz>$f| zVfuSPw?lqYz+h-pilYhs;3{ZCNvOq}Y9#O#R&}ep8&Zbpe1`-eVI|BxldJS3hkB1WBEeqxH4B)xM%F0#^$+fwSN1y3+l$;z!#Or0OdRN4~z#nyf33N73;!m+fk_ z{aT0JWEiL6CZ^!KGKMrboHJq%qP~R2noS4?Q9Ku`7RgrI8klsOq}9sjw@(d;I%3&R zcyW)kMRo}Wr&5Y1Gyn1;#u>z}K0fBDAiMb1F= za<^kVy^JStBAPvc_|NM+V|R$lE01;#XG`=wMSh>J<{2Lma!eZR{+!fUdRp;bP}+?= z_`$s8V>8u6pTm4-VhKd?JtM28k?;JANK9yXq1Of0Yf^MOn^{A_z2Pk*1%-$fx#?-H zNRKGraVxuSVeOo`>g6(O&cNzPpK5m+VHZg!l8?P2-G^-?WMcmB zs;(?zc-ju3gF!3SJTZx-pFUkg(5}tf%-;spDq191vbrv+OfTDXHR9NEcC1|rzB?{b{SbNg)hMp5r>_n-94`9Txe z>|mlcJ_9`sn}wemF^!|dB3&^JiPqA`AXlwpZp&>>nkjT}i*l%>-U9PA3qF%hZNgyT zeA(g@!&CS>v`wjs4=LIP*D2pjkA$q{WrEpUo{Gj4~}D$LCm#?)^F#-F}}OtHg?mIqj_N|EjH7M z0h2C$jkge~wt-sC$*4)yKmNA!>aPdD?Sr-Xibczv;?F+%;aojGP1r2X+NRLLO5eM> z_2a_BX+2SJw$6N4zMT}dd*9ElKBAOC!Ws1U;q`Z*hkoRec_;e-sikqAl*G<4gke{7 z3MVqOT=u?3CEM`?eRrl1sv9-(xM;GzB^1=J5uC7^^;aO_`<4gU>&^wR_rVKx{1?GE zzCdz;+~+Qlb3_JZW!KHi?f%guc|DUB;se$I`|bO4Z+Ger&ttB?%&KFs+Do_{Ds|rk zfcrxxPj{#LJ3&>MJPP`5oOHH&X%~P3zsI*Eo}w2087JP|Ool$B zCb_3Quk|7Y#i;pwpU+B~8kZ|fYMtk;mm7T%4P54}=Pvo&Z+tOL+)mlHrYjMXP}<3~ z8?8#tG+sS$)i}7wSNTiiGA2yYpwnBb;uRym{`DZ;8dfQG68H}BHIhTzhCE#)coz|* zo*b&If@sGDS$w{H#yNcQ7L!>jXm$>Ck;ow9D|q2Jd)NF+cmUgC)ErEzkzBmJG`V8e z@Fn)T2{Pn-KZM4XRbo$Uv`ygMk&1*FbF^DF>pgRP-u2BxT;mIdX@yr$s*K#rwyb5` zamg7A;kZUs2-lHsH{}c-llFIo3NLPWGRZ}4Z&-9QVQv+uP(4ekVihQ>1+gbNgQ@Y1 z^+Xkj@F>O%oQ{##yMGWtQta5odj2PK58tUtKDxNM(|X!-M9V;M@Zt}lEi7zwv!Tp+ zmC1nAZzTNBnM()Vy@;yCy3~q(2SKlDy)^n((;snsY*nnnvhouhH=x2bqi(_ zYKc3PwK9O{xjF2bKv@BTSzjq3e1V6Yp}Zzm5JARlq@bl~vo7ACa9BtXzwAMN>@{Xr zF_;y&y+mp)PGfKZ*!(Vca20v!5pi^=$!F#Y1r-1#BQB!)3KQL6w?RMl+9*{m2#gr~ zreqG4^@u5beD#+>waQp+5|$|wqv`Er@fQiuh!{`26jFBn;0JEcQvWv+K5q%+c%bmN z=f?Y5*s1h=n3=7n6>kCIrgyTVifW9)L3h>;ToXI_P(+A)Ma1Y`XOPQNV5QMWK^Y{% zp!>&mO#0V$Y~(gKmZbsp1Tb@4U_M5U9kQDhF{_CU2%6}tZaTdMNvsOQ6H!L0e7(zj zJebJ3u=uE3XP-r@xF%euh!a%uI;3)ra#!I!DX4FC@?`ZQUM-ozREGq%+|(%lN?E^E zO&GA@x6w1yud`USu}K&A=;~j>${I=nj7O+}Ke(a}5IuDM*iLvuCoAqa-!A2#hy(Ko zJLj*$?XRL7I(SfdU3xw7B7qJZ#mjbN9N$4{vc9?HuJ$E)zy?C1A?3?vv=fI9I7T%> zNOsCOUG8)zz>|+u>KYJ_5iX6n2nYIYZXicKlKhm~Qiw*E!p2*0+f3z3&GhjWU_`UQ z>l84Ul+VCKqr}#^j{;n*)Y^~ybm-gjFd4z|RN@507kxXJ@p-zj^8N+*ivk=}OoeD2 zzTif#{EpN|m*3W?$J8x)hP31Y`01x%2gnEfkoOgO8L&mb%q!{T7}GA z?|e_jftZ}#9|I7v`EvK1O0WCucFcYAJ;0b0^K!oDwQOA@M!@sfh9lnv(R!t$V1K>( zS|Vg73Bsoizu*Bu(zqq+W6*c;Kq5fpdum*%cL%GF1jeM#7TGXH*;6Z_1N<%FX=I2Q z7ZE0bfXK7-V#<1a1A^=+ONkrYX~ZSs4fAj*`>DlJ5~JWsht9z4vnD!HC`b|UY$ZR} zJPvU8gkEd=%mWH=x>N#=nxg&dzK_=Y6i0!5g!WP^jTDW4&kh9HPT+Urx6Nlg-_p(( z+d*k{upBj1AxP_}kTFPX@&{9XkTcMX$8lU7=Ld?qnYbz2O`hae>y@fR4MJBf4pIs# zc_3??b%XWn#BqmVxRfq7#7%S!g%YbX%=VE&KtE!^FJ2&JpKsuvRzBNU5jXGTBH|tE zu$QqTb(Ln$^C1r&7@>u&>?t)>V>k9@v-pIz&{E7tGGVqx%}!y&SX6~Uj16*R1Lu$c zl{*dmue0KV9gNbl3kTjP!005cUV@#Dm-!lsupo~}II8@U zcM(y<%jW-9R3b}EsHuEP5z4v3l99jezqD@majl+3E2fH;BVtPP|B(0AUs0}a+$bR- zjdTkrAV>-l4j~L8(nv{nN_Tfi4Jb$tDc#M`jr7nR(miy;dE9%iv%c$`f8br~{goJ) zXYRPJPhGmrE;@U_Mi8akRKyhpl6KesL@u$E)CM{D)G`-ujJt#siXthWm zY$1U)5Q;I?5cVh&U$Wwk@ax+!la}axVf2_a7Nsur`n*DH7VbO=vXyfK(=d)56zw%| z{_mp&Zln*S46hP*n?D?QEFdELCatD6HW>ETjF+C!5(c-6A18LSx-KJ6luD2tQffBZ zpiU|l_C-0ZXH$Iy?wmRx3otu9E4!V?WY@_Edvze3H|Qr>Z6{-odG4o#Z-Yrv6}U93 zT~~ZgzfqgOwTUnqTIABSMwdF~(`P4dR|!`r37_aMu)@TEnKq>>aP^7tgz}((Lzl>d zjXqVs={8TAYa-fALdIS$^F+UZy{Bv1*ko+oL?{Md*`q_tW>p!V4KvCLou0;7Oz8P* zo6*DmHv_DZy#GQ=vnp-7|as{uQyN5U+f6Zn9?-LGTB#a(D|k1r`f6hU zlVVpEBKtJ30Tp$4s7l$q@GKd~kgmcPilIgSxOZZ~i1lq&j8TZ0K#8ySo#Ahqwq$S7 zreYB(+b7L5U&(Z)Idz-Xe@~0dvu62_acG};(Z&w)tj>ugnBi8Bn+~3&=JkLMxfo_Px!B`dZa)=F+h9VJ=$W@J=YIV7pIb z-jWcF4+)d^d$ysT4!a%(t?T}bwDT_C%CWflex`p=4^Dqjg{&KX6#g4n_JCyvV~@pb z?`+aq0K<2hYW;U0@i(T!tJWD>i5wVXC@jvi6*|0ae{l<3m0Pf|;$WN6wVyvw@)>`K zeOk4;>ut<3Lh}Yu@1Qy{j0)ToQq+M*DUW?CrqJws1zcW%76H=4MdDt;tluFSRc0ef z!06y>P)FCtE0NNtsjieUiX2YxsjYVaLm*rKU07;+Fl+l_q0!dYNM;rL98g!&uQe_q z!Z_xc>UWJyls5dEPtx-HvY1+MAP|IHa8yO4l?9ouZ{Sy zQKJaHMAg}X-%`8>;G7c>_hLD>YBBaQbz>fe^FQY1+pchSbqQLgZJyKa9LZSWwz1^1 zQxyeQFWhRg;ouLXooz-J+6ErUe-Xi0mhv;Tc1E#p+CLYEW2Sd~BV#M_qGYn)UtPKH zCt%OJUk%W;YYaGDji;8E%|N^roofjVCsOWmIRQ zoWjgcxIb&aB$h+V)5G4TRA~UrJGhmunGdn;pea^+#gE~vbChE$qch=J1w@EABOp0%$om;Z@1y4OFuS~Ovt}~QEZLBX3LtPF?+|Ei8 z{iK{Yr*5OXxV{XADV4R73&Rq#2yHmnjwN0gnG;F^vvGwLlmkCF&f$D{tuIUdmi5|y zP63QOv_~yEdKGQ+wNoIyJPwt?!raYucPt7WpF*C9q&REUwlv!;R4SEj(^wHHp^u4U z3|2O*?$pTJJ{~0X+SC6q6$Vw>v`u;Z2tb)DHnoHlMC@SNXzfNOQKfVm8)!hS6~xc^t4#|){?hAGf})VbUr1KH8{3K9%GfAj4;2Y zdcqn^`YBt!Mkn7Swse?+=;jCjwX8H8t+*L{byJ!CXi|@NU5fTg(EX<-JFrc*FSF}V z02y1Jtsu+t6aMCGpQ=Zh`_1NM^=2D|{kcAt6GzozFR!g<=ZSalaqf!#dAnrml=uVu0$uEc7`3^K#@l z&c~gF8Vpfl?gY*E*9fjBZ)(K_AY3BK>7g(@B+o>WarG{uKVSSLC?8$ix+L&kW0pyP-{UU>E^%m~{Z<_XErdoilX5so7}tw;e}8X$)>&n-Nja|y_H(x~ z8G#wo5wHQv41DCKGIqlSv-9>h_*?t!@3m#?lm+O);|kt@beLnADb0pjZrgu+Sb^6W zqR?`qT9!6tDhA$*2jMKf){~-n=X%OpXaMHQlTWu$AT^;!IR^-?o$(l>XpeS#ZI~CD zZ}hB81o4{$Tj^n?gRmZyT}R!cA{tuyHven=CtF?L?0V=@c8kPjspqYLo!1#(GcrCUq?2#>FJV^gPiK6L4N4Ro^i zbpjTbk{Bfdt1H`o@cZ3xk-(IG2P+5ibN221L>t0Bo$paqq30^lW|bsGH6-c_Lj91V zj6pfl#{6?uGS?9_>OZ%qKF-R)J2p2G@3K5jb(-$AEPbOF6-{RM*Le))S=O%K;ZZgX zNuN);DAuA$m3Sdg>55M@?YgAbE`@gZYasV3?v$^c;LqblwtZNhXEk|pYB`5`DIOB> z7Ymis(b@0cT8BtQQmuc`{GZ_M2DDy&KZy1>81 z8G~ViOrSsLbNlm~P3V-Se%&567d7k$tYwr2LSsQT2F_7EOY=w&&&va#l4RjnCG2n$ zuZoTC0U{UWvYMRGeLzFUUinn7mHaR>UOB<~m#g}P+4^sjX?%lN%<=_$P~ZDuO` zHO$5%g;0G)N%(jSrZ$fzd}5N*yLn8v%P7g0fC<-6`4A~!! zl#4JLeTP_-5?4*4^V!HlP`ir7&-jP?7VzVE63eXyzm9L%@pt)~s+(JybI+u&X1GFnMY4$4VU?*n&Ie#zgI*9Az`EC!)2#FqoG*d{7tZi&Xqs$_{fLpi!Po><icU@ zFQ{}if50*nsLEB@40noEc1Z6`9KVnQTLP2(HSdo5Rk8={mxiONb;M4{ya72vvbfm zkINl}N1r325Hcq@c_=Fljru&E0-)q1pVw${An`><>a%u2MSIO_c@o7Qs)&r!uU@#x zCo)C`T550%rMeT+YqlP&+>zOSt6Sx=Jr^?vl$%jc9gjV{@(i+b2|1(<+l%?^2}*xG zdc`~+bf!)M`Z(@AZdhE(Z7>fGjmQ`@u1w)|aMt+lEvAQPP#(ZID?z=28*Vu+_H=6W zYW93K_bPX-}-9;Zrb79m8KP?<&pEKeO{c#?I}YW4@9w{*+$3zY&-@I*U&7D97? zKtt2M5OW2x%suXXP;sQCoNWi8KNC}N^^f3Ka2^fJYj?VF;p!Y?zGt6-)NuCRA&i z+e_`w+2k&xNI@v3k2y$Rr(d%1cw7|*X8&3)vwOvbnVS@v8s}c0(wJ+g{5-BCE}+Tx z{6+K&^dj%nj5cCFAfV=yjC5V@oq2QUQxEOkBL>mN9R)1SyjP>mMJGu5YGQS|@ur7m zYOd4$Rj)PfOp_uGL3p{hBLM}oJVh-(Y3iQy{wO?pavwHaV2!HFpK7o87Ofby?n}9q z=!ZWu=kquaewzivZitB_d&^45FAAEDD4aJYhP&>bMpI;Z^v}f1gg!8}Pz2CgGtD~Dngjr!I(Ru4ytsMaloUyic35(0a=B5`t8*J)V81}&d z^<^pEy(WC^aFkGLkxaXULb57Po)-05aT8s~iBbUevb!{QOtsxTOA_8Y&b)PEN29s{ zZGnFLTHR)3DuZ`4R7ucCfolojQ}eaMRpkiG)%M6QWH$?4s8PU?YDYmp0+7bYB%h~6 z`!g7aZr%ZlSRV*DI4nVGfSps>cS=CUyN=uVAiuxZEpG4Oo}=ksklaUnv_*js9Lj8p ztQx40LbW%;g}Mr;cBFt*iJVr4evLS=)P@mjox`tA>s7qy*JJf4I4SY0?Yy&It+aFc6b-T07jL1$wt!rS}Zs~jzLXZ*W z-S$?msj`P&L}e5se-GCFJNBdBtCJOONC~_Cq!9xjh_>lZh9g$=W4fi|PpAztuj`)b z#*Jz`8sP|QRY|2v>dqz%nb=XprihJ`iIx)9SxB{iv^?ROOI;1?WuUqEoS?q1H}BhJ zBx8>ZAz;FQXJk+@UExudArTeVgp{hg(M1`{(xKjWmvvK z?nu??j5KdY48JzOAJ|N6pLASsLmW4Uw>-w8q-oH)e>e|f&DTAO>9)~e*QG?s$DzXI zUc*XB%n@$)J#u9n@mwceIgDvAr!AW-^D|4}U=0NX4JlmDf~zcCCLRkhEsQo_f4+0*pbV^&NtQSEt*JLUcF876=iO2Ar%z@ zwC*Qlt1P+vF-)s#Q4~%G;dmf%fQH9>^OTr&ZZ#8?>HA!@d&E5(uxg`$Rf~yQSsCf@ zIXqLX?vv*_p$<326pR2ef_kTtzLxa_Yn>}v5fVk+*!vq^6J_?=yu@1wm<%+-`@|c=@(AW{)w2mnc~<0&O3YXoQMuN*o!oz$Q(JS$n^mIb{x}|y z-)GD}bUpc=Q!gag^>5J`+KWxbsdU&BT;E zo7$)?99}mSe!`myXDyG%>=BUjQ=?_L4v`L7nGUwKVh@H}(5NH{92__P^>xZtfRDL> zsDd4+D*r}|mxF=+&*vPlg-C)EZ6E$ti3q3D3x%zy(@Mh)O9R;GA2-7!%BtH;=cbZv z;!^B=3_Zr(1@VLd%_gQyG>+AF#vlhn9^#3zr!O}7+D$%h43+95{4BBK*!5;V+y4D_ zAMelTwgv)CjQY35dt~RfAG*$JqP8DXw&j3Qxu$eP&bwGSZ)m+rW;d>?^>H5erdmN*}14Go~atU?H_GQ>+^z!1^3tc)~;ggzuhz@DxxF3 zb!z<47|O7FL58NR$L2@fJTD#LEVZVlznN0+AP9`6h@cTuXigA zu^tdX&UDClVQQMV*Ex5>ywS1zS?s7PMtD}gGE?Yb<4K5ZD;fc$7p8{b1g)w;b8>r? z0IK+@S_DjaGFhb2e3i0$&VB|XZinviCT7kVvA8|m@q+R3Y8nIT&8boi4gvsI7)zi# zPOa_oPCWYJpN9uuHK>5Dh7sc79z)lnI-ped7gl_cqlm}jnFy+~oLd(JfA_sDRT*+z z?C%AsNH)oYj_o$aAakOr`GJ&Ht^_iZX+#!;f2r4gqL-L(HzX=$?DyvuMi3y{riWoL zp!_mWnue4z>evd<&AV>z6LCCG-I8mypjDoFL5r9KD);wY#9IdCLUi0~JFUx)LCcS# zljOGW$`WB2$l+3>)S~CvrcFIg_EU+{rzTDEg&EoI5mTTy?88cqOv8Z0p@SR-6bK%> zNYja5FmC9vmpa|o_in-qFZO49H@X{w9SjzB?$9Fke|L~D?OZE-Ol$WekE3aZ?0a1A zl1RnCOK2Azy>(8tA(qQr*SIU}P2A}K*waZk@umlVO~g#ZygdC!n9GNnVYM>dwIugB zACcrO!%CGMH;R~QkUE`-Y^A!$L#1Q1o-Wri5i=7D}?FRB3mm*JD~JdCq+x=bXnfffO=jm zrdD#J_O!RI&U~;E9uTXTuCLGwI#(8u;r=E&n&;6a{f$?3idn`aK^UylSY(-_>Y`xb z46#c1W-#mab*;aB4(xTpk>Gaol3G_>Bi?*g>z|pL))o7yRCmy+!NAnPHpZkKHLn}F zf)fD^a!)zf?=37pq>lXU3N9fyQWcy40!2x6=2=F~fz+_E%OvpWL~=NEJnhtZ;%d4f z*5*e`<@%=6w!0!2Zrku8PX5S>nYRxu0Q<*`OKD=RVqqtn%z(Qr)=5OVLMv)t$$C1i zq8v}5Gd)W1TBH7nYQ9PobD>6^k5TS z)ZPkEU9WH?dCa>-eRMRQtLJv2Q=*mqNTf^CQffy_pBzac0-q#DM1>-4mwvP97P#b6 z8jM+G+^HAx)iZ;8u!C5{_$Lb|tsL*y2S58YrZOZ$F!_5r=9q)2`IR)mE`m7U>5Ie<_zSi#I3bS5WAm=ls<|nSUS!(Kb z&dydrs!_`BrECZyp)I8ru!~6$EKsZBIO5+$S_#}+-?MGm`l6r*K27|iEuK_zT10as z%5b1WLW{NHdDS8^Z8yX)Mh@jNbv{#ABQbuvX%yhy(SlrFF~yYC7R$c1icNcd9%5fo zpL^W)lxWsi{kdkAiaYHa0s9kTnCr;pP2K8FtmP99otE!(V3lI!z!CbdWg5RLsVNTN zRrvQF>nsfn*J*&=sBB9l*kiZwkZ?0`B!8F8*~@N_Rjpw7Jj4^jBNT}8=!B{OQpi{o zT3Wc=GqKMWwKY(q9Sa*Yup0kRQmFWf!h2Onlsq;|n!zgJ^-7iWZQ24FNmVw}=eFw+ zXqRHx@2>>MogUxQ@t%LmT(N9dI?wj*d9PZr81qGu5A?uI?RMAa*DGq}rb3{8W@bgz z=_<4%787Vn<1i*lvd_?eE$iKSMQjzhnJ6qjmm^knU`ckb&TOSNBTSD7!(_Q=y>lWf z5!_f2YR4(}lal=t$jh##;T-hwpq~gYxlp8czt~sZWY)Laz*!3ygBH09wN`NFnyu|M z{1%H8IWO_fERpq+PUTBEw@~j{@_CcfF-Q{b;1s&Ab`Jr@pL2l)y%ge@ik&4o?;*uFjY)Qk5I0o6O;D> zS(zMiv;KRWALWA{3fKUL#Z(AQWexdv%I5vuH8#3lTX%{W*#%MSv6o!_<8dQhd}Xml z!(7oj&MZ~(&t$5rH{CwjcwF@IZt!|BV%jYI^m5z$<$UNZVS6?yEv7-wJm9{Qe79Q~ zEZt{^w@OQr=E%(yRGG14cO*vhNAn-iBLG2jAt6dp>UtuIzsPWZI9AXJ%RJOU^J{+F zNS<3?^erHT(oqNG4peL&0n}oud}u*_T;RD;Tw-?vb_tqQ3Fr*^3Ohg_Y*26Iu1maj z1IOX-AZzLx(Qct?u)B4=;Pc(drOZmv#`%iGC3_WUliySrB36ZmMC*d*G zJoO-u%~SE&=84!4k1pUrY8kXfDK3}GX}|j56xDn@`UzD`LWPLnoAV38B(R83c<5s+ zU2W(AidaQcpY3$<0i*A}s42ADl~A(FYX66NvOe~AC8;6>A@e;!3>{B$7)?%t+w^W} zgv0J7!rL(&X=NKd;-%OWv=Ua%AiTTmquo_u>)$&){q0Ad+fONBs2t}wxADwJ-Bwvn z)PU{hFem^>MgvxsLyBp%Y*fz4xG3N3%^%1bcSYQ(^F!GU99N_ zn(qv0)tT>9Zm86g|4hSdW+I5U9owH5h|*}r{p;-jAAhmM1$7|Nw9zPt6B+7>WxpT4 zqZQ-n6<~u+e=Uee$lR`gcUPtR%q`2A*}elUw?86rkaL-C<_+klpYv7nx<&P+MRr!K0r<}F{w_k5NT;?%mI*<)%t$-;r22))kdZDqVjN26VqCFfrk-pj6$ z#b9b`n=^g+DIptW_wn@?M-L`juJ#Itnq(mStBgVlpzDcy1`HXfK(g>yaNCQl7;-&~ z`q$dU#E`1abHm|@Z(k%UhFN?3!?XU<%^!)=&(+$EoN&yATze_;uWfAkU8V0KH?3zX z{G86nij}@nucPrv-Mrq=!)BVz=%SfW@SGV1oM0YS7oUhIk13;sUL$-qKE(*j#Imde zJAbNJ#~pTbEI{8&#?+zh@Y)Ut<? z?U5Rav*=!Qy2x$+dWWDKsMVi^__9@lxv*RLBLMDhrR@Sh8Xq#n%V!F z)*t?Lc#!^GXn!5?&#wPfcmMM~{`u1SzxsWfzfbe;Z!pA)zW6`=MJ@o5iF^QH67ROji^&Kt9Q z+3Qc$Ci5^PpyWGOK3+qb`@)01v4=(gQ$FBY!~0sjDCLoH}l}xw>yr1{`CV+ zx3BO@+*MsJyHUkmcoBYyc5_JTE-bY6>*5DJ{#)o@Bc#t&1#T1#f9x<+R6#TI5RBhP###vs)OI1BD(A5bw0%#M5}bS77CaH)is+gl*&w;tl&Wyln#o)C_n{!5dI;N#)t0;;e=hqK&^ zG}tI$EK*HdI`;CBo?Hbs;!dj2aDGH|S|c2+Y73zi&h1rzj^?E2Dc(lwxTgc=HPiLi z|7;-^#eX;AJu<|v%nL0-35pV204{KdW3zn^peI4RD*jlt_#f|h)O|k1@sa>~?#RN) zd|1R4mdNTMO)pDpKbXcR^x?seqP$G**#7a!_@|u<;*w> zp0UWJs%zVYm6kqe3Q!10yc|$j;V5NUORm|wK26V)>)(<&OF+hK5WW@sp))tAh{O8&&-q~E+h(+c zBFXY4+^HH6zDJh}y8)=u?M<`$`q3%}A1iMwfdbKJ$gRzy4R>^j5pr z%2)MYCvqsZZ~@<>Uc2er-Emi>M_hL8`XG_diV?@rGcJJhlcLr~*deIZ^`QfewOEYZ zwWZ_iV?5f%!)7CCoHFu8!zr0L(C5@s|MeB$;T5D9|6=7c2Q=2&WPL_$8T42=%42A% zOqfV$^{IJdO;_LnDcseIFgU89j(#8&^{w74ubI3`rS zDj!IzBS;f+PWHe2G^+Z57YX{wt|xuuef7%uU@5})1wpVR@A;3B@Agk9=bB8Y=#E$? zE$zY2HVzi^MDK5#Arz%YOU(lWLT=lbpE6qNJV$i3r2oC7(31b(vr($_?(Z#Hb_w`A z2jKbR^mcVFiyk3Q9gFFMShN60S=g7c74V5G`iuLem}MTZmA}@=c>+ws48F8jSuvol zNxnN(fw`aNGN&~>!C2`}b#}^wCDCW)H};Ct2s!@(U1Pr~&n%Yyo|lW4*2qI3>~$3b z750=j>XBc`r+~$Y1Dg4jFfI5W69LO`Y2Rw?($O1qE_%qUGEj#^3&-#|MF>V#WJ~8_{Y=l zCIiChYrRj7*tMG`PHBBqz5^ynY*LPkCgy(+9w(NmSN~1%48^2l6z+;MrjuuYR zR$SoDyh-LVgMa^p!_o`+Vze=uMvSNj23;#q#>sVNb1#-f!It&z#mm&{FWw#J7bC&B6iR2&40*DO5}D@crsq+8=UCpHz+cgcDk9$&Krfgi~gAgj2|3Z$Q8oZ zB5le%UNZxsFd|E#&U+HQ4u5)INba8fcX2kOVY#_EvDksUCf^fX2=3fS*!p?>x;vBP z8oHm920ALilKa~~`BdlMLO37le3_!(^}Yfz$$sjEcy=-($+3al2KDk;RExgw%g(l1 z&IGk9Lm-`77`}qwPO0cDz*^;~K3xi|-D{{CkUSue7DDUvuKTJLIB(ZlosIVa_pP8q z2N0MRDxVUF;9K{B+TIM*VNz_$E$nWmRtW#c8!l(wKiKH=}snZ@!X0vn`_BH zZy#>7SUK)n62;ZE+bjmu4z6r*AtULn`gW*ihD{q&*7Km-D#?Qp zAk<)@`MOkHmw+J}aG-CZdFLWQwm(@xQpLSh()2}-MBP^Wdk^U0YUb02%{UOiq$$p} zEB|l$QdXS=&@G3tTrBMaBC2Nx^%<0r!=odkh=x;vM3L>i+|;!NYX53qJ!-y2)n*%k zwTx!~kqijr1kt+IqB>Cv636!17<+0BHdWaHhhEzU>}!KadZrFR&%>rIZzmGDgkx-U zF(-rW8F8msUe!zozK$1yup(BNrKKV`K~9!8kRw%7CZSBfzQaun6E55KVg){Ne~WLv zRk{fEI(MLx2!-Srz8p{nydgWYZ;B$U$8A#_DF!|0-uXO}G8pPxOKD?lD+1aa-O&&V z1ptRGj5(|guS7@F**L1UUjF@qwqvlQ(dFRS3A^X5UeMsDh?J(u$(p)r655Dwr4=(S ze<6or7C_N(+Paz%!4= zJ|bTuDk07{-z_)G9;e-}=RzJtY^LbV92MIk*f*R*eI!$8#c zSG~>Srx3c#c}q-?q5IP|qreYx6fL?f)jvz~movCWOog21aD;pxKvK0_26%q?PP5@y z(No9YNr2L4)h0{h0>yF`#{NcvHd?Xz~oZD(QSN7ZWeRydwyY+JQbf0R+Et_F7vA8!1$k4kjD2jAsY_WC?({@7k zcb2`3_RgzYjl%V{AZhcvAnDy)_ZTMmT;*x_0k%QPUAL;!rr1;D!r{mckTP8T>Ynj|D$ zYiCtz_q>6+OFlRP2t@@!lFqz%K{(e}=HzD;;u4pxoMt*YG0+*bo4GFfacs#&&8%%0M;#}0X zJRUQ_5lXB^>j1N}li(OQ5VtXy5(Oi@ZyUqvU2Yw-xq|U!Dnr<`^R;{1JG34*gA}xlVF&8UE6D<1C`0VQf*I=WyOidNhi-13 zed(=#HDq_LS@W%E*kv61JdGI(+COA>?D-9y(1g4|#8xKGDmBHXpovZ(cT zEz~CW^oFQ;IbnOPWont+^P4rU8Ugyujc;Lds%2u$P}iW652C4mwap6oM{$aT} z>_OpP?|uA6LBQ-37Q?cI#98_h_;*PCwKJ1yt;}Vyb&U$aRrH*iWldVE3-&DZ=9W_@ zY?H?RJCzbR0d2_lQ1+}>9&9n$^7zI3p%)JZ2L#gVQpDeTY zm_oaeR8~&lVw_QN%J9O^!=mH%3S0=m&);I|A3rOQ#671pr6Y5F$r9E+?DYs=lWHPH z>mg=13Ckyk`HKG~9%``4bQ%w(%g_{&!G;TKzhn}GSF)S|)q@8yiV`jr|Jru)(&4Xx z1PV96;%4mA&A1+#LObQ(wf;sE@KJgU9b&CI#z{!sI^`hB1Ab%R(>gSa2CFC(2i`Y1 z-vTp}1LsS$>YAd-dfx2*_Lc`seZX+V93V8oapGd8g2?O5)ci!paz2~m)ECT+cXlge zA&Ousxz!ni`CNo|Kwrxxi8wGcLZFAwl$_YxHYQJ;)YSOnfiKD6PWoi+BdQ^|9J$8m0UTJdRRK z%*6`5HH?sLP0`|*ZG^UCKUE!82fpWxXB`(-{ouEW;r)7M(YL!DKE-J1JI)q%m%_@{ zatUq@^}dQ=Wv;c?ibsxB5&Lu1Jmpg+yn*RR-DPhEv`4bxj7jcTcI#}pe*;pWGr2}v zrO?ZAHF~BHL8onXMw7cq03>Cx=&xRoQ~wt_ViDN|G90yr1<8lQ@Aq>T6R2?v{XT+e z(p$T7o=v$TyZYs7K>{p>%hVnu>fQ+&hHdMg(A$K35Jo4AN%DE3^sJ@?WB0xSi5;zA z`@!R~Ah1-#j)lNcG1}h7qmCaQ+-Q*P;@A7yXd%wHJf0u&ZaqKi2V)eQg3!(|5rFRM zZzOyTBM}BSyeq9ia=o?(caio(`w6uB<)hQYqu;4^#Q zEK?b6C-OI8LhP7NUS!JbVH8^5zSqlYy?l-?ANjKDN+JaQ&4$yjWAEAL#XE|&GGTrB z_#I)iaJq|{llI4LrkjM)^{PGA74C5seiOPq(=f>|NHH7msM@qilgS_IqFIm18w$Lm zLw@tp)<>;E&qjg@dj$-Axm^g`!ov2hyh-m2sB=o0<2<*}FkFqS>w10@+$Z%{ojDb? z4Bp>f`<>qpnKkz+g|AKdqeBQ_T**3;*=({IfwNLWrKx?(`yv#}!IegGQl9+!|DuIi zS6P5{LTZ0Pi9eJKfz%_vjFs!la)mmxL!oy3cuY0b#Y18S+ioRsda1}2Vv{CAm}Vsr z=h~DKvTt|%f-f-@9TD=)7~u`RRmO34tW%>&3Vai3=eSOYn6VBdVc&1uMtJRIbT~7~hJ7-P`^Tbp6+dkj?grA^2sU=D_ESh@E#>VFXdKMM!v6(T0(9Vi<(I zKZ~@_)X~0>UtWpogo*6z}7e2cDC(ls);+u7V&XiXn(MOXLgqV<0e{!uMt2v-|+ zTkuO3)4ML|pAoimQ$}7{tN9nb#e4A>2;{_y~4iaw@ZF?ALX7>*$;+*g5798PS~^^K!KWTCaF| zNaK6D%Nlu>-+QOK2RA_uYIHZUM4c<9EGKTGt3@uCXGHZZV(OaZ2ak4Ha{Y|%%KvNK z7-IYAJc?G}bm&~jwYhW-V_Vq^X9+f7-@{LF?l~zT2TnZN*Hnc_{-||=uNE=+(;A6f z?mr&STmR1re1W(^N2g|hU2Zo_cAxzb-s4+}cwI?O^}(GBj?1+BzE0zI>rWjJFZ|z? z!QlIGg`0B92+zQCTEUlUlu}py^Z)l1|8L-Q{(ltJt#v@J*$C8Kw^5tJL4dj^@4p9@ zRsV1D#05WyR>&*SbU0Z8k`AiyC1$r?uK4jcWDpqkwb=>;l)N|TSIq(n#u;UgJplRS z^=)UM6mTO?*gM;KbMd0sdfruWWa&QNahkT;ZHEV7D&mEua~y=ys=c~dcgLEUo%a`_ zHiz^+0zt0T%-J#toa2CDs=&K*D3|*vnTSoo0`yO-}8uspZZ+NtvHxKz3g9 zm~OpW?B<|WTd%=Is=L$Sf_vG>-$?2g&(K=_Lqk^y+f z)9U7ZO!Eiu>n-Uzk0GJI1V?K+vo9eKXes<_;0&!qqoKb~?w_htDMRKTR`iyL_Hutw zN?JMRSJ1c5ik(bD^;QYdJyvWNkn>!LflniU zcZOH0wl*() znw5VE<5oN{Sbh5>34mh`AHK0HcSj^Ni9Bv3Nbnj?kOSyAsa(2Xgl4Txuf^ZEG-=1j z2h_hjoeSac@qeYT{ky#deX=C1x78! z%{UhH?Z=MqUCNqCJgZt4hk+O6b>DFs!%%tqHW^~ATeLHBVL@xcE_dMbdl^fr>;l!43u=oVXFqe6X#Nu*SP&1j<==)c z@y9<&H$Va|L%^B)6!k==qdM()0?64Tx+CevV*g2WeteN)Jdg43+=sHPF_Vm(S7SWS z#el3Mvla1ag*Jl+@ow|gte0Nn(lIO)?7D`|p;0)r70Xv ztEU8iX0qKabHq{T_bP5}szlK*;$z>4$na6u?;kk1sOY$>t-es9G?5$Lrw5CT%WR83 zt>-CnoN|bCy3bmV*n2Ux#|LsaIf;aA@)@ zl(z2?zd)%btQd?Dq>5+POL}}{dH?Ltlp1=&Dsad0-Vog#oOVp`yxr_~esCdw>gR5m zpT9b~KfxR6d_DneOVsgp{XfsN)$CsqPA}?mhF}Fh>dbn+#3o9$a5{I)gl(*7WwbOG zi72v!O{bIrO{ckEsTpuivtsu5MtO;i17vi))+@hF#Ht*3lIRf4 zFpm42=cGzJU&-bL37+lXsF@5v8965lmbqkw>34&|~YM9UZI! zC5&U1p)wy8$>|X}uz86eZM5C3L)i1;c-gOkvvT9ly>$93Hd~C1?mb|Z&TI=!3&GAk z$ch7PN0KDfn=n~nv$)#+)YQ`fqNwNGYT4EMR7ZV0XQTru(jL;Xf+xz9{~64I>=H~Y zKr84m2pwdMRG0jJ$a}A-rq=df6cwaNQ9+av#6}aa(CZRVRHXL~qSA{(=me#zC=nG< zTBLVE2dP4qMnMQY6bT?D5K2Nz2%+pZ`hEYg_ruCnR5rX-X9wpJ6lflDsX%fZp-fX`{j7bNtucGTb%B*YM&4`il!IPB347)d@ zbzZ*Qc*%J)A_WW)psLpOUco5&@mZ#xz31xf@O_(Ac;pCIK|K@^#t*wED{829=U{ID z10vV3uH(^BxORvA^Ls<2!s&8?ynEl77~`1a-d-K(1Q;dfsm87g9!T2;W!Z%}dY?J$E3gt~$hAt%A(2@iz#zRoMbaMX-)Ypj}R zUIaHc^z~MV2yheDIV$lZBcEA};M>STBmH`J2X`zY5P zRX$@7r(G7YMVaxAXx3D~Thnu*7DN%{6l`y8E3b0TEg{kSu)Huq#;}_%l-D&!EC)K2P1f}g7ml17^$yR=|SNEui?`xOT*SHHOU*Bf?+GyVL zZn2-dr13qDlKJ|yrfS%~uEgcyF8*vo(yawi_USuLe&r5iQ?~LyF2EF3cs26}3WT<;tSBUQH z`QWO)T3D{pYqz~%c)@$c{PD0+W=W#y-Owykd?LtuVj5%#W8 z-vfJ*QyYY;Hb2!pSgg{9OWt$&o<5KXvj4F*k6fPvI27m2(h`DcFR#T zafQfM{bt4Poh<>i!k7*n%}?E~h_D^2AYl$4Q_)(pak2vAm$bfccDY^ICpY_V1N2vO zS!vx8%Xjir3+v)wUK-bs8aE^U4TnG*$tK8uVNmV|#<2co->&w=xeISk@$}0f)LiVd z(eH9(f?5*HgDM4&qW%+dVY1>`P9@gwMcYvHVLhtOwr`K;=pqw`!gIi?f zysEKjx`lE3jDA0#=FYv*QsUDaeQC=hl{3iz$a##H=Ny!t%}SfVAJfEu>E+EwuuCmC zbp$fJKsw)K;MVq(f5K)EL!L1>8WTKY+`69{EgvxU2 ztB-!Wiq-Q!YgL(ETFyxfsa<%PpdiZfmYGv<6R{PVH?{5CcyCx{ds%KXpnD983pOw0 znn=~kurDjM$?rJdPP3dvpa8%Rt3;g0lib!gec|S`(X_N5`4{f%P^Aw148FZ)$JroS z9u;)qy1R*KlG{7@wcvBHw;Yf<93L;X@JFO$TUBC@b-+18%;?}^b&`T;9rE2x`b zSX%0Wq6Xu%_OnYh-0>X+-q#|2|NBClcGFlfG~Zi`3(95{QkGhqx$b-FPF4jN^bAuU zeKNQ4MMW+h?hP&<4bI z&}nE_7s_|O$sPpC)~LyS=(}lGOF+0oi6xA6gDNX%N;J4(K_f(XsN1WArM(7detTCd zhLW}iSss2&7&P!tM`s2pkMini0aip5x@Bjf*_t2 zJ;QjA_G^zXXVFxi2@4$}z&-b3U%zM!P4wRd{`$7?rL?FGwwT3PG1b^uArJdJl*FJ=IL=CkXY)wwf!w8Et#n#nDDmp#~GWpBDsS+hXPHa7!9gW4ZxaaVe9@1(4c zK1z@x+^k+t^e$K`P|C2rdDj@=XbDAS=V)3e~4TzmHCn+!AwvTg8v!BwJq z3TpGb>7()kC_%mkG*)l=G%j_2RGCbzb$z!ShHm<}-y6L9!==OT%pry^6MugO@Z!PV zA;8`z&G0<&J!U9KPF(W0BV|FMB`5=;e8es_4iTzqAIQM4YVjBR6nr`gMr>~1q#yAz z^aneG@!>yDb{07J@7t_@{;zYYhZz{oUgU;|OZ3E(SilS3|MUNY7wlJ;+qbxbbwe*; zisc{z`V{~Oo;{LKAo-UPZ3eE9EeyW9K=F?AH@^l?5T}hM4c-;#SA)Q9aQ6qfamme% znlsg&#MbaWgC^&1uMnZbyWz4yMEFa2;?{ylm6Y}?*`D2Ob5cr7R=A2ML4b#SEu=r(EBePF(Juz1`t zhA-i9f36-%Xpo@#lNvCd{>L?!u9Yp{#-GWTw1!ETGu87oAWeExW96t0T&NlhMIHZr zQ8!0n;(doBm?OV3SY*QUnFyp{IBGo|x02BTyll^n!6+$hL% zD8EI{d+c`tR2a-ndjJS#4VV-jCrn!dpC>G@Sbi<)$MH~{8kwl!2(0gFW{AGesNY=E zBA%V$%UdP zX#l*ES5(59a8c#q-;1WMG7);(m7s*NZjfK`6g^*FyZ@B`itBA(wSe)4wUc|-m$tlp z4x%Lbvrutf+T5lpjN?s|g)_~Q@Dp}4KxrAL)u_`QM9M1PE@Y4u=0YC(G$mL)|6MnT zk>Lyde5NFso_%4_cJ!Io2+2sD3&fqrg*%IUb4er0EbfW&Oj#ul7TJ>3O#({x?%9=W z-xh9IR-byvtzJfJ=C~1xQh8g8^2w!2JmW7q)<*|{=wq^&+kxnOx zJj1a3%#5~S@mIVMQAQ^dWGxK!Z9#8!p)V)-y1@;u=xk~#SMFUGUI`6?-(du-4(2Nr zFbcR~sbQrtq+`>vs48hWQOCo&@!sfe`1kU&4+wjbj7zq{p}XRtie8tN1_GZRWG?LS za3&a)K2VZ0%gHc||fBCt%Inr!=*$ZKT6Y>0dzF{0-EOB9E?j0L*`RJ#21DiV+q3hCPh)jt+iLHU zGN0h3W>G%S%P|`_v|_2`Bg|YOf!25`jo3zW?&BE-?T&?mG7lZ_!<;?z_YO%S73zP` z963_dPjevazL)l~sq=h|TgiXR29MaMMyPE*U_boy@UJ0_{fe#>nQ;|jD^cWjhSF$D zD17d_-IpD%26ay;B~xv<`Jqjkvezx&^yt+IfM+l3?UdFz&EWU_N=b!19}f3aI-|1{ zl$fr>G#5nvBVnC>)2aMs34UdJZDv2yl+7HBCEt>9FEcET39t{N$^mHAKcb8Dr{K%2 zXq9nx;aPDBW#OC611>#5mJ&A8#}TN4uc4bwej+S)x^3!MaiK7k$LF%TunV70K`uXB zX@sl&Tcjc$($l~yY&lNOP<3>>P4JVl9tRi{g)*$YofumL+wkfL_T-4nC~tqBn6u|% zG}4uP9ijJ8ej>tLXU*Y;t9hKsGkZUo0*vV!ed5I3K0K+7D}QUXvN1zB1h*?Sj>SNkETJcwC_w|raWJl=bUjC|G}yQ2M-2LQvvOg4sE-hAug++7ATkd9>sB7EG;iPL|s>?XCU3A?70(jk;|T<@iC~*?b%<6Fd++18Jk}GZRN7 z9AUy?6o*ECrS+m0>8yu~%j*w+R!0SI@a@cuN6tv7Ch&)cKO8a)N>>h#J+R%Y6jjsK z!8K|*)eN6Bpvm{Oy7jxT#yR&@{0_UJXns5SqHZ#n^y8Yjh)k0YW_g`I63?x{1Pl^xD)j!k5P)c_Gf70@)l5)?vu zB8GSGt{}bbp0~b%>d9(c>z_Nzz$<3^%V$r-r6--E$HmdNSqD5!lN_t@ox+@OYsOoC z3g_JtoWEUG3eU^~R2kQndHulxOT^^I>ba_OU%N(@e42R~yQ4P-!aBiHplwobB-Y+U zvQxs>-M%PV8J@^K4_>958gaA@MMwx(c+Wk1Ui>tlgmouIYucy;zslC({~aWT${uh( zm%|zl-U(M@8C9;culE0ZcNk1zMX>K!aMSgPguh4Y-JtaCb{?)*gtu;%u7tC3PR<~fjdsW+!8vGr_G8+`yCS`^=231oH2o{l0?Xh|DAn4WdVfTgtZ!mZs(cwDE zmK6SVbmYL|F&zGUbwXEx4xVtH3 zvo!Fvo%+f8f)g&nwp1PIQ55mCcWba>a^m9yQI< z5fRt+gUF%1ze&aFbVD!mGu8p=di%=2Z9W;hb>H{W&cmcDLty&>jvF#G;W3i?IZmp3 zZ6Lk-ybgSat^5G*P_!~<;Xv7|F?@9@@x|tENu*WmPo;h1AXEHfbV7zY@#+)U)X3wL zEA4UDw-JX@RZ_1TJkv(~Q_%fkk$vH(jjx(&v0pLgquW6vgqmol5+E(s0J9bi23&gP#OGG}gDfh)Qt&G*G zuzWJxtA%oDa&Qd!j@h;!5Kf)Ro-Sl-0;qtkhf+P&Jh_J8?cWTGL1$m=w8WRo+!R!D zDl9$xNK94UbL>}Uxyj@~=-1*Q+;Lc|mW#uMh3YMVQu4r7*oUH3tD?_{B4d|LZt9N$ z#OT7+8MS*JEKdqI3TZyYB{rYkIfOey)@Q|mvJ)3q^~0oqs=kDk{OMB3Uz=G2sOMV3J4xw9Ec7}J zNNLHU<=5?#e)NSyHuxk~;}0ET@NN5Z0`g5?t)z<^UYFlyKP^bWfs^PR2d7IEDeJ2l z&t1*Ivi>Vwy(BZ}8LbJUGIG%jt`q;ssak{Cp`38vo!109mA%*yqWYvYE1`@U3l!^I zFa|&5^y_6?iRG1v`bpxuq62-)U^BFC!|T{`xVgY<|BDT$5|P2cg>K_af^1!#$h8hN*&Di6Am~-18PWaXPIbwO)7dCc?JHSNp6oHgU-u&m z9#plQ1iK);l8R01lgkkGB^JnAKKsbsj5Zr(>_g%53c#>O=ejJi6^<}GZQ}m(W$pL0 zO`)TX=X|pyY+7CRNNBbPIHwMX+OZL!Vp(5iS*yI`rl1(I`JpzrHA>31=0eDT-qxzh z7vSxmj&1S5D6I#BTEbbw#ix6+xT2z>7;|v1^XSMQ{z^Ej$#XDkm zO=okyZF#C)Yq%JyLc78e6gbwpM=QI@9Uy!=si*1z#u*J4hU0*!^DE}>8Ppbs2EZ7M zHQihsxWS_2L}$E@q^xO}tp479OHM(>sjTt?x@7#n@!c7PNf`0a9U=5}m!2{3nLWYA ztl=VE1>Gd&9KB3swH@)m7`rC2<<6^7&+X?%eQEoW4&hs`!L>A|Q6QQbGW=v-+7aXy zp5r+Fk2i7~K=y*9%O>qA&-=ihJ?A}U&NVMP`=B3>Fd2LhY3c+8q}w?&)Mkf5*4;vz zu(bU-Q9c>_nlsm3#_3{(kV>1QGg|N7pm@^|Nih%?sSO94hjDDDq}*Po%2E&-2fJNU zyGO?Xje}fr85fdlyN}%y}Q(HSFvi^_)=~}I;?#w1W2ECfkbtU1p6qU zWBn8M=jz9{@IbcmuDgD}BA^_W>orlQ7UFl0|8Oy>@C(mkii5mW<=cl+&dKTIdlRwb z?>TZSS+Zx!!qL*?ai6wPU_{lUArYNX_dc#CmEEaMhKvjjdGp;EJRzEj4zTJWzxT{-T!=DCm?Ox>ylj=$O<*? z=;KT$Vp{al@V;WB^^o@J4Z zLSZIaT;Oq~K$nU}ZtOZnbQjsSIprLLl}ul4yuKYyQY#LI6CyiFiO$Or!uLFoZENP- z9n#yu5cQBj5d45i4GMv0P{48nvwlWNW8fIlHb&wG_bLHB+US+`uF=7b!AI0v_)bG6ZLIzF4XOJ7*$&eN8~5Z$<~3Yqj(`DPctn~aPQ*O_dHt;N|(wQ z3cU+8OFPKbTl^uUd)|k#{!p~v?EhsV+hH{QWC^`n#DF5mu}C|(!sN@pbU2Pncr6%e z*LW$qhMOtbHkjQiu197Y6H3ONhG_Y}HaI#>`iFZeS$ePc8q+hEomW<^E9L;mt}gZ# zm0&_m(G}Rx7Ac2k+2*-HXAZJ?Ii^j;hJvF@ZTAubxrf4Efz73&K+rpt7G9Qi<_x4Z zdkN)seKfAiVk~Q_MOe|dms42j1mi+-`%)4)!HqU$yABT54Vm*^qm6Q^&2oS-0o{-| z;?6=e(AYzx?#ssX&sJds)j;9vkD<`n2D7RwPM>(hi$qwp@2AC_jLi?8u4tzj5e#=VV=~oNf z-{n96M0`tH{<_cp;fOs0ji%Or62$Jgug(ETzl;A)QUp$NTYzqg6h+2g>S%6%$_{9A zHeVlUaAPkqq#89nxk#l+#&*mAaUn-x6gat_?HxCVYsb4_5bFoG+t&l=XZCZMziK_F z$lBekM?u`C#Tw*j8T;#x*WZuBe8627!yi-PE|{FI7Y;l)2xnHK_XiHbI*3OU}Ub z9BPIDAKs}L5+67*d05+F)%x`9nw{dvomh_z`I}mna)MBh<=Ee-TTxLR;Sjubxfz3L zr+vZAE9yLDYU-nMD+Jb@9EZ8cZ`BA8x=QC z_Mdy(2(ymXp+uPCeLDvi)h7*6O1%6xDK7_mO@Z7aQpENcclbL{1CZg7FipQVmSre&P45_hjf_NvRcnD7dfsD?V@7hQ?z~V zYHJxnC(#QwwyvDHnuvM5*t--*RbSE@AFM#ECUXx53}ntq~~Otq9{{HLjq`JY>R?{cVdJDU2diU1dG#5}2a z;(h_FH_aZq`r>O0r;xJec%2U=>HfRysiK%j(-i58EtsRXRnFKTtPftu;dYOH1#{L*ch+gB|j8#C_twh+9U= zK8G5kHc5bX#Sel%*){u*0fuXny&ymAYYA)vm6*RD^#3_r{l5e8HAEimGRViSm2V4z z)$I@44)#MgQS>*-`1cZu?Oiw6*%|lo0#gv={kK_=pz<9Y1A~AuA710F=Xc!h>}+5( zd}tvFf@;GO7G>T*xq5+{% zJBdlkcJtaAF5Mq7=H>fhbgUgV`&=%#Jg+-LL4%I9fo1@(wrhcY#!KAn=3J_@NkG?! zi!R&grdCz>N*zd<8@?wa9@gnl^>9g^Zqs-YYR>efttt`70fgO14*qMGLk1$eP~T)O zM`Ydqld9-lPy&+P`@HTqL+T$?eB#ICV6bfV;%@ z_o5QUF3Qlr)R78#OyG0e12CPfS|Oy>`E{!NA9llfQgjqVRKvzo9KyX`BRrgda9F2P zg014S4;g}OQ{b&Dzo;AXAcYNpa|@UAGGFvpR2(wyxeW*04=?o`$CLjw+YeYU>}m4O z`L(V?!`4jgt{@A!PnB?WKRvh06n{dEIJh`K-Vspq>U;YzU}y7Vv~$>gjE_w<**SEh z%$}@8%G-CRK!!_iE|HYnpD)poGiWPT*DIe*M=aJt0#`tj$_1KaFOQ4o;N*&o5^n< z3h&fyw7bZK{8WrsnU@+LqvJx*RhxDO)T14s)RX&4sr!erjFa5S?YZJFAi@gMM|9t* zmmC~o1-|2}zv8HI(5^g-HC>Lt1yjNg)<4@3u1Kif3e4qUWakxkvx@W`0km8g_YojF zuEZ1>2l5xQ0{in^a9-h}M=Q$yb;Q<#c3=WOFu=MwausxWa`4O54&M;#elJ&VRzi&q zRGBkJ%|ynqwo8ZuIzkS=n_T8NCuz3zj|JF8A4-}&M(4#`0kxSyV-igGQ%X8dsGVkH z;1^WVc`aDAY6Y@GUY^{$559a6AtU4Zsas+;(}`za)ot{Qy)-KUEW{Sp!&U?gDtE5D z9!I)wymw|-dXciYsJulVq5|pRm-BMkilUTes`vvO+`=Ee=R2VXvA!PIJ3K2HS$uvYy$uX8m2?B!18cLj9AhZ;N-w0wu_ zBmb$=4Wm!LT3nDC`B`ltnT6Wz3{X@S2kmn8OWXiaTt_EB zRJ=iNg8b&vn8oy!!Q;HpDV|S*H$WaM(08Be^_%%!_;|0=`KYPnVcgOLHYy2M-;vW{ z4~SkAcXIEu0cSA8d?PrVaQmg^eXxYx^C7qF)_9&rp&{~fZd$$PR=B?&@1VTr_OFQH z;FQaD?O-FXlLIV?yC(sm=#Qxdub<{ z+^4GD{SZ83fGW*k12(X7C5kjkgG{OCktkpO_34*Mq09&YeFu)3hHc>D; z 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/api/v1/users/config/init/` to create an initial Superuser and -provide DNS settings for your installation or run `make test` from the `api` directory to run the test suite. - -Note: if you're running on on MacOS and you find some issues installing the dependencies (specifically around pyre2), -you may need to run the following: - -```bash -brew install cmake re2 -``` - -The application can also be run locally using Docker Compose if required, however, it's beneficial to run locally using -the above steps as it gives you hot reloading. To run using docker compose, run the following command from the project -root: - -```bash -curl -o docker-compose.yml https://raw.githubusercontent.com/Flagsmith/flagsmith/main/docker-compose.yml -docker-compose -f docker-compose.yml up -``` - -## Databases - -Databases are configured in app/settings/\.py - -The app is configured to use PostgreSQL for all environments. - -When running locally, you'll need a local instance of postgres running. The easiest way to do this is to use docker -which is achievable with the following command: - -`docker-compose -f docker/db.yml up -d` - -You'll also need to ensure that you have a value for POSTGRES_PASSWORD set as an environment variable on your -development machine. - -When running on a Heroku-ish platform, the application reads the database connection in production from an environment -variable called `DATABASE_URL`. This should be configured in the Heroku-ish application configuration. - -When running the application using Docker, it reads the database configuration from the settings located in -`app.settings.production` - -## Initialising - -The application is built using django which comes with a handy set of admin pages available at `/admin/`. To access -these, you'll need to create a super user. This user can also be used to access the admin pages or the application -itself if you have the frontend application running as well. This user can be created using the instructions below -dependent on your installation: - -### Locally - -```bash -cd api -python manage.py createsuperuser -``` - -### Environments with no direct console access (e.g. Heroku, ECS) - -Once the app has been deployed, you can initialise your installation by accessing `/api/v1/users/config/init/`. This -will show a page with a basic form to set up some initial data for the platform. Each of the parameters in the form are -described below. - -| Parameter name | Description | -| --------------- | -------------------------------------------------------------------------------------------------------------------------------- | -| Username | A unique username to give the installation super user | -| Email | The email address to give the installation super user | -| Password | The password to give the installation super user | -| Site name | A human readable name for the site, e.g. 'Flagsmith' | -| Site domain[^1] | The domain that the FE of the site will be running on, e.g. app.flagsmith.com. This will be used for e.g. password reset emails. | - -Once you've created the super user, you can use the details to log in at `/admin/`. From here, you can create an -organisation and either create another user or assign the organisation to your admin user to begin using the -application. - -Further information on the admin pages can be found [here](/deployment/configuration/django-admin). - -[^1]: - -Your Flagsmith's domain can also be configured via the `FLAGSMITH_DOMAIN` environment variable. See the -[full list](#application-environment-variables) of variables used for configuration. - -## Deploying - -### Using Docker - -If you want to run the entire Flagsmith platform, including the front end dashboard: - -```bash -curl -o docker-compose.yml https://raw.githubusercontent.com/Flagsmith/flagsmith/main/docker-compose.yml -docker-compose -f docker-compose.yml up -``` - -This will use some default settings created in the `docker-compose.yml` file located in the root of the project. These -should be changed before using in any production environments. - -The docker container also accepts an argument that sets the access log file location for gunicorn. By default this is -set to /dev/null to maintain the default behaviour of gunicorn. It can either be set to `"-"` to redirect the logs to -stdout or to a location on the file system as required. - -### Environment Variables - -The application relies on the following environment variables to run: - -#### Database Environment Variables - -- `DATABASE_URL`: (required) configure the database to connect to. Should be a standard format database url e.g. - postgres://user:password@host:port/db_name -- `REPLICA_DATABASE_URLS`: (optional) configure an optional number of read replicas. Should be a comma separated list of - standard format database urls. e.g. - postgres://user:password@replica1.db.host/flagsmith,postgres://user:password@replica2.db.host/flagsmith -- `REPLICA_DATABASE_URLS_DELIMITER`: (optional) set the delimiter to use for separating replica database urls when using - `REPLICA_DATABASE_URLS` variable. Defaults to `,`. This is useful if, for example, the comma character appears in one - or more passwords. - -You can also provide individual variables as below. Note that if a `DATABASE_URL` is defined, it will take precedent and -the below variables will be ignored. - -- `DJANGO_DB_HOST`: Database hostname -- `DJANGO_DB_NAME`: Database name -- `DJANGO_DB_USER`: Database username -- `DJANGO_DB_PASSWORD`: Database password -- `DJANGO_DB_PORT`: Database port - -#### GitHub Auth Environment Variables - -- `GITHUB_CLIENT_ID`: Used for GitHub OAuth configuration, provided in your **OAuth Apps** settings. -- `GITHUB_CLIENT_SECRET`: Used for GitHub OAuth configuration, provided in your **OAuth Apps** settings. - -#### Application Environment Variables - -- `ENVIRONMENT`: string representing the current running environment, such as "local", "dev", "staging" or "production". - Defaults to 'local' -- `DJANGO_SECRET_KEY`: secret key required by Django, if one isn't provided one will be created using - `django.core.management.utils.get_random_secret_key`. WARNING: If running multiple API instances, its vital that you - define a shared DJANGO_SECRET_KEY. -- `LOG_LEVEL`: DJANGO logging level. Can be one of `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` -- `LOG_FORMAT`: Can be `generic` (plain-text) or `json`. Defaults to `generic`. -- `GUNICORN_CMD_ARGS`: Gunicorn command line arguments. Overrides Flagsmith's defaults. See - [Gunicorn documentation](https://docs.gunicorn.org/en/stable/settings.html) for reference. -- `ACCESS_LOG_FORMAT`: Message format for Gunicorn's access log. See - [variable details](https://docs.gunicorn.org/en/stable/settings.html#access-log-format) to define your own format. -- `ACCESS_LOG_LOCATION`: The location to store web logs generated by Gunicorn if running as a Docker container. If not - set, no logs will be stored. If set to `-`, the logs will be sent to `stdout`. -- `DJANGO_SETTINGS_MODULE`: python path to settings file for the given environment, e.g. "app.settings.develop" -- `ALLOW_ADMIN_INITIATION_VIA_CLI`: Enables the `bootstrap` management command which creates default admin user, - organisation, and project. -- `ADMIN_EMAIL`: Email to use for the default superuser creation. -- `ORGANISATION_NAME`: Organisation name to use for the default organisation. -- `PROJECT_NAME` Project name to use for the default project. -- `ENABLE_GZIP_COMPRESSION`: If Django should gzip compress HTTP responses. Defaults to `False`. -- `GOOGLE_ANALYTICS_KEY`: if google analytics is required, add your tracking code -- `GOOGLE_SERVICE_ACCOUNT`: service account json for accessing the google API, used for getting usage of an - organisation - needs access to analytics.readonly scope -- `INFLUXDB_TOKEN`: If you want to send API events to InfluxDB, specify this write token. -- `INFLUXDB_URL`: The URL for your InfluxDB database -- `INFLUXDB_ORG`: The organisation string for your InfluxDB API call. -- `GA_TABLE_ID`: GA table ID (view) to query when looking for organisation usage -- `USER_CREATE_PERMISSIONS`: set the permissions for creating new users, using a comma separated list of djoser or - rest_framework permissions. Use this to turn off public user creation for self hosting. e.g. - `'djoser.permissions.CurrentUserOrAdmin'` Defaults to `'rest_framework.permissions.AllowAny'`. -- `ALLOW_REGISTRATION_WITHOUT_INVITE`: Determines whether users can register without an invite. Defaults to True. Set to - False or 0 to disable. Note that if disabled, new users must be invited via email. -- `PREVENT_SIGNUP`: Determines whether to prevent new signups. -- `ENABLE_EMAIL_ACTIVATION`: new user registration will go via email activation flow, default False -- `SENTRY_SDK_DSN`: If using Sentry, set the project DSN here. -- `SENTRY_TRACE_SAMPLE_RATE`: Float. If using Sentry, sets the trace sample rate. Defaults to 1.0. -- `DEFAULT_ORG_STORE_TRAITS_VALUE`: Boolean. Set this flag to ensure new organisations default to not persisting traits. - Useful for data sensitive installations that don't want persistent traits. -- `OAUTH_CLIENT_ID`: Google OAuth Client ID to enable accessing django admin pages via Google OAuth. See the - [Django Admin SSO package](https://pypi.org/project/django-admin-sso/) for information on how to set users up to - access the admin pages via SSO. -- `OAUTH_CLIENT_SECRET`: Google OAuth Secret to enable accessing django admin pages via Google OAuth. -- `ENABLE_ADMIN_ACCESS_USER_PASS`: Boolean. Set this flag to enable login to admin panel using username and password. -- `USE_X_FORWARDED_HOST`: Boolean. Default `False`. Specifies whether to use the X-Forwarded-Host header in preference - to the Host header. This should only be enabled if a proxy which sets this header is in use. - [More Info](https://docs.djangoproject.com/en/4.2/ref/settings/#std:setting-USE_X_FORWARDED_HOST). -- `SECURE_PROXY_SSL_HEADER_NAME`: String. The name of the header looked for by Django's - [`SECURE_PROXY_SSL_HEADER`](https://docs.djangoproject.com/en/4.2/ref/settings/#secure-proxy-ssl-header). Defaults to - `HTTP_X_FORWARDED_PROTO`. -- `SECURE_PROXY_SSL_HEADER_VALUE`: String. The value of the header looked for by Django's - [`SECURE_PROXY_SSL_HEADER`](https://docs.djangoproject.com/en/4.2/ref/settings/#secure-proxy-ssl-header). Defaults to - `https`. -- `DJANGO_SECURE_REDIRECT_EXEMPT`: List. Passthrough of Django's - [`SECURE_REDIRECT_EXEMPT`](https://docs.djangoproject.com/en/4.2/ref/settings/#secure-redirect-exempt). Defaults to an - empty list `[]`. -- `DJANGO_SECURE_REFERRER_POLICY`: String. Passthrough of Django's - [`SECURE_REFERRER_POLICY`](https://docs.djangoproject.com/en/4.2/ref/settings/#secure-referrer-policy). Defaults to - `same-origin`. -- `DJANGO_SECURE_SSL_HOST`: String. Passthrough of Django's - [`SECURE_SSL_HOST`](https://docs.djangoproject.com/en/4.2/ref/settings/#secure-ssl-host). Defaults to `None`. -- `DJANGO_SECURE_SSL_REDIRECT`: Boolean. Passthrough of Django's - [`SECURE_SSL_REDIRECT`](https://docs.djangoproject.com/en/4.2/ref/settings/#secure-ssl-redirect). Defaults to `False`. -- [`APPLICATION_INSIGHTS_CONNECTION_STRING`](https://docs.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview). - String. Connection string to set up Flagsmith to send telemetry to Azure Application Insights. -- [`OPENCENSUS_SAMPLING_RATE`](https://opencensus.io/tracing/sampling/probabilistic/): Float. The tracer sample rate. -- `RESTRICT_ORG_CREATE_TO_SUPERUSERS`: Restricts all users from creating organisations unless they are - [marked as a superuser](/deployment/configuration/django-admin#authentication). -- `FLAGSMITH_CORS_EXTRA_ALLOW_HEADERS`: Comma separated list of extra headers to allow when operating across domains. - e.g. `'my-custom-header-1,my-custom-header-2'`. Defaults to `'sentry-trace,'`. -- `FLAGSMITH_DOMAIN`: A custom domain for URLs pointing to your Flagsmith instance in email notifications. Note: if set, - the domain provided during [initial configuration](#environments-with-no-direct-console-access-eg-heroku-ecs) will be - ignored. -- `DISABLE_FLAGSMITH_UI`: Disable the Flagsmith UI which can be rendered by the API containers in a single container - environment. Use `True` to disable, defaults to `False`. -- `SEGMENT_CONDITION_VALUE_LIMIT`: Configure the size of the segment condition value in bytes. Default is 1000. - Minimum 0. Maximum 2000000 (2MB). Note that this environment variable changes the length of the column in the database - and hence should not be modified for already running instances of flagsmith. It should only be used for new - installations, and should not be modified. WARNING: setting this to a higher limit may prevent imports to our SaaS - platform if required in the future. -- `SEGMENT_RULES_CONDITIONS_EXPLICIT_ORDERING_ENABLED`: Forces segment rule condition ordering by primary key, ascending. Default is `False` (no guaranteed order). **Warning**: changing this setting might affect evaluation for existing segments. -- `ENABLE_API_USAGE_TRACKING`: Enable tracking of all API requests in Postgres / Influx. Default is True. Setting to - False will mean that the Usage tab in the Organisation Settings will not show any data. Useful when using Postgres for - analytics in high traffic environments to limit the size of database. -- `USE_CACHE_FOR_USAGE_DATA`: If enabled, this will use in-process caching to track usage data. Defaults to true. -- `API_USAGE_CACHE_SECONDS`: Controls how frequently the usage cache is flushed. Defaults to 60 seconds -- `PROMETHEUS_ENABLED`: Enables the Prometheus `/metrics` endpoint. Default is False. -- `PROMETHEUS_HISTOGRAM_BUCKETS`: Allows to specify your bucket sizes for Prometheus histograms, e.g., `"0.5,0.6,1.0,Inf"`. Defaults to Python Prometheus client default histogram sizes. -- `REQUIRE_AUTHENTICATION_FOR_API_DOCS`: If set to True, users must authenticate with basic authentication to access API docs (using a Flagsmith account). - -#### Security Environment Variables - -- `ALLOWED_ADMIN_IP_ADDRESSES`: restrict access to the django admin console to a comma separated list of IP addresses - (e.g. `127.0.0.1,127.0.0.2`) -- `DJANGO_ALLOWED_HOSTS`: comma separated list of hosts the application will run on in the given environment -- `DJANGO_CSRF_TRUSTED_ORIGINS`: comma separated list of hosts to allow unsafe (POST, PUT) requests from. Useful for - allowing localhost to set traits in development. -- `AXES_ONLY_USER_FAILURES`: If True, only lock based on username, and never lock based on IP if attempts exceed the - limit. Otherwise utilize the existing IP and user locking logic. Defaults to `True`. -- `AXES_FAILURE_LIMIT`: The integer number of login attempts allowed before a record is created for the failed logins. - Defaults to `10`. - -#### Email Environment Variables {#email} - -:::note - -You can self host Flagsmith without setting up an email server/gateway. You can invite additional users to the platform -using invitation links, and the platform will run fine without email. - -::: - -:::tip - -Flagsmith makes use of the `django_site` table to provide the domain name for email template links. You will need to -configure the record in this table to point to your domain for email links to work. - -::: - -- `SENDER_EMAIL`: Email address from which emails are sent -- `EMAIL_BACKEND`: One of: - - `django.core.mail.backends.smtp.EmailBackend` - - `sgbackend.SendGridBackend` - - `django_ses.SESBackend` - -If using `django.core.mail.backends.smtp.EmailBackend` you will need to configure: - -- `EMAIL_HOST` = env("EMAIL_HOST", default='localhost') -- `EMAIL_HOST_USER` = env("EMAIL_HOST_USER", default=None) -- `EMAIL_HOST_PASSWORD` = env("EMAIL_HOST_PASSWORD", default=None) -- `EMAIL_PORT` = env("EMAIL_PORT", default=587) -- `EMAIL_USE_TLS` = env.bool("EMAIL_USE_TLS", default=True) - -If using `sgbackend.SendGridBackend` you will need to configure: - -- `SENDGRID_API_KEY`: API key for the Sendgrid account - -If using AWS SES you will need to configure: - -- `AWS_SES_REGION_NAME`: If using Amazon SES as the email provider, specify the region (e.g. eu-central-1) that contains - your verified sender e-mail address. Defaults to us-east-1 -- `AWS_SES_REGION_ENDPOINT`: ses region endpoint, e.g. email.eu-central-1.amazonaws.com. Required when using SES. -- `AWS_ACCESS_KEY_ID`: If using Amazon SES, these form part of your SES credentials. -- `AWS_SECRET_ACCESS_KEY`: If using Amazon SES, these form part of your SES credentials. - -### API Telemetry - -Flagsmith collects information about self hosted installations. This helps us understand how the platform is being used. -This data is _never_ shared outside of the organisation, and is anonymous by design. You can opt out of sending this -telemetry on startup by setting the `ENABLE_TELEMETRY` environment variable to `False`. - -We collect the following data on startup per API server instance: - -- Total number of Organisations -- Total number of Projects -- Total number of Environments -- Total number of Features -- Total number of Segments -- Total number of Users -- DEBUG django variable -- ENV django variable -- API server external IP address - -### Creating a secret key - -It is important to also set an environment variable on whatever platform you are using for `DJANGO_SECRET_KEY`. If one -is not set then Django will create one for you each time the application starts up, however, this will cause unexpected -behaviour as it is used by Django for encryption of e.g. session tokens, etc. To avoid these issues, please create set -the `DJANGO_SECRET_KEY` variable. Django recommends that this key should be at least 50 characters in length, however, -it is up to you to configure the key how you wish. Check the `get_random_secret_key()` method in the Django source code -if you want more information on what the key should look like. - -### StatsD Integration - -The application is run using python's gunicorn. As such, we are able to tell it to send statsd metrics to a given host -for monitoring purposes. Using our docker image, this can be done and configured by providing the following environment -variables. - -- `STATSD_HOST`: the URL of the host that will collect the statsd metrics -- `STATSD_PORT`: optionally define the port on the host which is listening for statsd metrics (default: 8125) -- `STATSD_PREFIX`: optionally define a prefix for the statsd metrics (default: flagsmith.api) - -Below is an example docker compose setup for using statsd with datadog. Note that it's important to set the -`DD_DOGSTATSD_NON_LOCAL_TRAFFIC` environment variable to `true` to ensure that your datadog agent is able to accept -metrics from external services. - -```yaml -version: '3' -services: - postgres: - image: postgres:15.5-alpine - environment: - POSTGRES_PASSWORD: password - POSTGRES_DB: flagsmith - container_name: flagsmith_postgres - api: - build: - dockerfile: Dockerfile - context: ../../api - environment: - DATABASE_URL: postgres://postgres:password@postgres:5432/flagsmith - DJANGO_SETTINGS_MODULE: app.settings.local - STATSD_HOST: datadog - ports: - - '8000:8000' - depends_on: - - postgres - links: - - postgres - - datadog - datadog: - image: gcr.io/datadoghq/agent:7 - environment: - - DD_API_KEY= - - DD_SITE=datadoghq.eu - - DD_DOGSTATSD_NON_LOCAL_TRAFFIC=true - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - /proc/:/host/proc/:ro - - /sys/fs/cgroup:/host/sys/fs/cgroup:ro - - /var/lib/docker/containers:/var/lib/docker/containers:ro -``` - -If not running our application via docker, you can find gunicorn's documentation on statsd instrumentation -[here](https://docs.gunicorn.org/en/stable/instrumentation.html) - -### GitHub Integration Environment Variables - -- `GITHUB_PEM`: In the 'Private keys' section of your GitHub App, you can create a PEM file within your GitHub App and - then paste it here. -- `GITHUB_APP_ID`: You can find the App ID in the 'About' section -> 'App ID' of your GitHub app. -- `GITHUB_WEBHOOK_SECRET`: You should choose the same random string of text with high entropy that you put in your - GitHub App. - -## Caching - -The application makes use of caching in a couple of locations: - -1. Environment authentication - the application utilises caching for the environment object on all endpoints that use - the X-Environment-Key header. By default, this is configured to use an in-memory cache. This can be configured using - the options defined below. -2. Environment flags - the application utilises an in memory cache for the flags returned when calling /flags. The - number of seconds this is cached for is configurable using the environment variable `"CACHE_FLAGS_SECONDS"` -3. Project Segments - the application utilises an in memory cache for returning the segments for a given project. The - number of seconds this is cached for is configurable using the environment variable - `"CACHE_PROJECT_SEGMENTS_SECONDS"`. -4. Flags and Identities endpoint caching - the application provides the ability to cache the responses to the GET /flags - and GET /identities endpoints. The application exposes the configuration to allow the caching to be handled in a - manner chosen by the developer. The configuration options are explained in more detail below. -5. Environment document - when making heavy use of the environment document, it is often wise to utilise caching to - reduce the load on the database. Details are provided below. - -### Flags & Identities endpoint caching - -To enable caching on the flags and identities endpoints (GET requests only), you must set the following environment -variables: - -| Environment Variable | Description | Example value | Default | -| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------ | --------------------------------------------- | -| GET\_[FLAGS|IDENTITIES]\_ENDPOINT_CACHE_SECONDS | Number of seconds to cache the response to `GET /api/v1/flags` | `60` | `0` | -| GET\_[FLAGS|IDENTITIES]\_ENDPOINT_CACHE_BACKEND | Python path to the django cache backend chosen. See documentation [here](https://docs.djangoproject.com/en/4.2/topics/cache/). | `django.core.cache.backends.memcached.PyMemcacheCache` | `django.core.cache.backends.dummy.DummyCache` | -| GET\_[FLAGS|IDENTITIES]\_ENDPOINT_CACHE_LOCATION | The location for the cache. See documentation [here](https://docs.djangoproject.com/en/4.2/topics/cache/). | `127.0.0.1:11211` | `get_flags_endpoint_cache` | - -An example configuration to cache both flags and identities requests for 30 seconds in a memcached instance hosted at -`memcached-container`: - -``` -GET_FLAGS_ENDPOINT_CACHE_SECONDS: 30 -GET_FLAGS_ENDPOINT_CACHE_BACKEND: django.core.cache.backends.memcached.PyMemcacheCache -GET_FLAGS_ENDPOINT_CACHE_LOCATION: memcached-container:11211 -GET_IDENTITIES_ENDPOINT_CACHE_SECONDS: 30 -GET_IDENTITIES_ENDPOINT_CACHE_BACKEND: django.core.cache.backends.memcached.PyMemcacheCache -GET_IDENTITIES_ENDPOINT_CACHE_LOCATION: memcached-container:11211 -``` - -### Environment authentication caching - -On each request using the X-Environment-Key header, the flagsmith application retrieves the environment to perform the -relevant caching. This can be configured using environment variables to create a shared cache with a longer timeout. The -cache will be cleared automatically by certain actions in the platform when the environment changes. - -| Environment Variable | Description | Example value | Default | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------ | --------------------------------------------- | -| `ENVIRONMENT_CACHE_SECONDS` | Number of seconds to cache the environment for | `60` | `86400` ( = 24h) | -| `ENVIRONMENT_CACHE_BACKEND` | Python path to the django cache backend chosen. See documentation [here](https://docs.djangoproject.com/en/4.2/topics/cache/). | `django.core.cache.backends.memcached.PyMemcacheCache` | `django.core.cache.backends.dummy.DummyCache` | -| `ENVIRONMENT_CACHE_LOCATION` | The location for the cache. See documentation [here](https://docs.djangoproject.com/en/4.2/topics/cache/). | `127.0.0.1:11211` | `environment-objects` | - - -### Environment document caching - -When configuring the environment document caching, there are 2 options: persistent or expiring. The expiring cache -will expire after a configurable period. The benefit of this option is that it can be used with all cache -backends, including those without a centralised storage mechanism. Bear in mind that this will mean, however, that -changes will take up to the configured timeout to be reflected in your Flagsmith clients. The persistent cache instead -makes use of an automated process to rebuild the cache whenever a change is made. - -:::warning - -Persistent cache should only be used with cache backends that offer a centralised cache. It should not be used with -e.g. LocMemCache. - -::: - -:::info - -When using a persistent cache, a change can take a few seconds to update the cache. This can also be optimised by -increasing the performance of your task processor. - -::: - - -| Environment Variable | Description | Example value | Default | -| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | --------------------------------------------- | -| `CACHE_ENVIRONMENT_DOCUMENT_MODE` | The caching mode. One of `PERSISTENT` or `EXPIRING`. Note that although the default is `EXPIRING` there is no caching by default due to the default value of `CACHE_ENVIRONMENT_DOCUMENT_SECONDS` | `PERSISTENT` | `EXPIRING` | -| `CACHE_ENVIRONMENT_DOCUMENT_SECONDS` | Number of seconds to cache the environment for (only relevant when `CACHE_ENVIRONMENT_DOCUMENT_MODE=EXPIRING`) | `60` | `0` ( = don't cache) | -| `CACHE_ENVIRONMENT_DOCUMENT_BACKEND` | Python path to the django cache backend chosen. See documentation [here](https://docs.djangoproject.com/en/4.2/topics/cache/). | `django.core.cache.backends.memcached.PyMemcacheCache` | `django.core.cache.backends.db.DatabaseCache` | -| `CACHE_ENVIRONMENT_DOCUMENT_LOCATION` | The location for the cache. See documentation [here](https://docs.djangoproject.com/en/4.2/topics/cache/). | `127.0.0.1:11211` | `environment-documents` | -| `CACHE_ENVIRONMENT_DOCUMENT_OPTIONS` | JSON object representing any additional options required by the specific cache backend. See [here](https://docs.djangoproject.com/en/4.2/topics/cache/#cache-arguments) for further information. | `{"PASSWORD": "securepassword"}` | {} | - - - -#### Example 1. Expiring local memory cache with 60 second timeout - -The following environment variables provide an example for specifying a cache local to each API instance that -expires after 60 seconds. This can be useful in deployments with just a few environments, where there is flexibility -on how long a change to the flags should take to propagate to the clients. - -``` -CACHE_ENVIRONMENT_DOCUMENT_SECONDS: "60" -CACHE_ENVIRONMENT_DOCUMENT_BACKEND: "django.core.cache.backends.locmem.LocMemCache" -``` - -#### Example 2. Persistent redis cache - -The following environment variables provide an example for specifying a cache, stored in redis, that is automatically -updated whenever a flag is changed. - -``` -CACHE_ENVIRONMENT_DOCUMENT_MODE: "PERSISTENT" -CACHE_ENVIRONMENT_DOCUMENT_BACKEND: "django_redis.cache.RedisCache" -CACHE_ENVIRONMENT_DOCUMENT_LOCATION: "redis://127.0.0.1:6379/1" -CACHE_ENVIRONMENT_DOCUMENT_OPTIONS: "{\"PASSWORD\": \"myredispassword\"}" -``` - -## Unified Front End and Back End Build - -You can run Flagsmith as a single application/docker container using our unified builds. These are available on -[Docker Hub](https://hub.docker.com/repository/docker/flagsmith/flagsmith) but you can also run the front end as part of -the Django Application. Steps to do this: - -```bash -# Update packages and build django. -cd frontend -npm install -npm run bundledjango - -# Copy additional assets with Django -cd ../api -python manage.py collectstatic - -# Boot the server -python manage.py runserver -``` - -### How it works - -Webpack compiles a front end build, sourcing `api/app/templates/index.html`. It places the compiled JS and CSS assets to -`api/static` then copies the annotated `index.html` page to `api/app/templates/webpack/index.html`. - -The Django `collectstatic` command then copies all the additional static assets that Django needs, including -`api/app/templates/webpack/index.html`, into `api/static`. - -## Rolling back to a previous version of Flagsmith - -:::warning - -These steps may result in data loss in the scenario where new models or fields have been added to the database. We -recommend taking a full backup of the database before completing the rollback. - -::: - -If you need to roll back to a previous version of Flagsmith you will need to ensure that the database is also rolled -back to the correct state. In order to do this, you will need to unapply all the migrations that happened in between the -version that you want to roll back to, and the one that you are rolling back from. The following steps explain how to do -that. - -1. Identify the date and time that you deployed the version that you want to roll back to. - -:::tip - -If you are unsure on when you completed the previous deployment, then you can use the `django_migrations` table as a -guide. If you query the table, using the following query then you should see the migrations that have been applied (in -descending order), grouped in batches corresponding to each deployment. - -```sql -SELECT * -FROM django_migrations -ORDER BY applied DESC -``` - -::: - -2. Run the following command inside a Flagsmith API container running the _current_ version of Flagsmith - -```bash -python manage.py rollbackmigrationsafter "" -``` - -3. Roll back the Flagsmith API to the desired version. - -### Steps pre v2.151.0 - -If you are rolling back from a version earlier than v2.151.0, you will need to replace step 2 above with the following 2 -steps. - -1. Replace the datetime in the query below with a datetime after the deployment of the version you want to roll back to, - and before any subsequent deployments. Execute the subsequent query against the Flagsmith database. - -```sql {14} showLineNumbers -select - concat('python manage.py migrate ', - app, - ' ', - case - when substring(name, 1, 4)::integer = 1 then 'zero' - else lpad((substring(name, 1, 4)::integer - 1)::text, 4, '0') - end - ) as "python_commands" -from django_migrations -where id in ( - select min(id) - from django_migrations - where applied >= 'yyyy-MM-dd HH:mm:ss' - group by app -); -``` - -Example output: - -``` - python_commands ------------------------------------------------ - python manage.py migrate multivariate 0007 - python manage.py migrate segment 0004 - python manage.py migrate environments 0034 - python manage.py migrate features 0064 - python manage.py migrate token_blacklist zero -``` - -2. Run the generated commands inside a Flagsmith API container running the _current_ version of Flagsmith - -## Information for Developers working on the project - -### Stack - -- Python -- Django -- Django Rest Framework - -### Development Environment for Contributers - -We're using [Poetry](https://python-poetry.org/) to manage packages and dependencies, using Poetry standard workflows. diff --git a/docs/docs/deployment/hosting/locally-edge-proxy.md b/docs/docs/deployment/hosting/locally-edge-proxy.md deleted file mode 100644 index 74a9286aec08..000000000000 --- a/docs/docs/deployment/hosting/locally-edge-proxy.md +++ /dev/null @@ -1,314 +0,0 @@ ---- -sidebar_label: Edge Proxy -title: Edge Proxy -sidebar_position: 25 ---- - -The [Edge Proxy](/advanced-use/edge-proxy) runs as a -[Docker container](https://hub.docker.com/repository/docker/flagsmith/edge-proxy) with no external dependencies. -It connects to the Flagsmith API to download environment documents, and your Flagsmith client applications connect to it -using [remote flag evaluation](/clients/#remote-evaluation). - -The examples below assume you have a configuration file located at `./config.json`. Your Flagsmith client applications -can then consume the Edge Proxy by setting their API URL to `http://localhost:8000/api/v1/`. - -
-Docker CLI -``` -docker run \ - -v ./config.json:/app/config.json \ - -p 8000:8000 \ - flagsmith/edge-proxy:latest -``` -
- -
-Docker Compose -```yaml title="compose.yaml" -services: - edge_proxy: - image: flagsmith/edge-proxy:latest - volumes: - - type: bind - source: ./config.json - target: /app/config.json - ports: - - '8000:8000' -``` -
- -## Configuration - -The Edge Proxy can be configured with any combination of: - -- Environment variables. -- A JSON configuration file, by default located at `/app/config.json` in the Edge Proxy container. - -Environment variables take priority over their corresponding options defined in the configuration file. - -Environment variable names are case-insensitive, and are processed using -[Pydantic](https://docs.pydantic.dev/2.7/concepts/pydantic_settings/#environment-variable-names). - -### Example configuration - -
- -Configuration file - -```json title="/app/config.json" -{ - "environment_key_pairs": [ - { - "server_side_key": "ser.your_server_side_key_1", - "client_side_key": "your_client_side_key_1" - } - ], - "api_poll_frequency_seconds": 5, - "logging": { - "log_level": "DEBUG", - "log_format": "json" - } -} -``` - -
- -
- -Environment variables - -```ruby -ENVIRONMENT_KEY_PAIRS='[{"server_side_key":"ser.your_server_side_key_1","client_side_key":"your_client_side_key_1"}]' -API_POLL_FREQUENCY_SECONDS=5 -LOGGING='{"log_level":"DEBUG","log_format":"json"}' -``` - -
- -### Basic settings - -#### `environment_key_pairs` - -Specifies which environments to poll environment documents for. Each environment requires a server-side key and its -corresponding client-side key. - -```json -"environment_key_pairs": [{ - "server_side_key": "ser.your_server_side_key", - "client_side_key": "your_client_side_environment_key" -}] -``` - -#### `api_url` - -If you are self-hosting Flagsmith, set this to your API URL: - -```json -"api_url": "https://flagsmith.example.com/api/v1" -``` - -#### `api_poll_frequency_seconds` - -How often to poll the Flagsmith API for changes, in seconds. Defaults to 10. - -```json -"api_poll_frequency_seconds": 30 -``` - -#### `api_poll_timeout_seconds` - -The request timeout when trying to retrieve new changes, in seconds. Defaults to 5. - -```json -"api_poll_timeout_seconds": 1 -``` - -#### `allow_origins` - -Set a value for the `Access-Control-Allow-Origin` header. Defaults to `*`. - -```json -"allow_origins": "https://www.example.com" -``` - -### Endpoint caches - -#### `endpoint_caches` - -Enables a LRU cache per endpoint. - -Optionally, specify the LRU cache size with `cache_max_size`. Defaults to 128. - -```json -"endpoint_caches": { - "flags": { - "use_cache": false - }, - "identities": { - "use_cache": true, - "cache_max_size": 1000, - } -} -``` - -### Server settings - -#### `server.proxy_headers` - -When set to `true`, the Edge Proxy will use the `X-Forwarded-For` and `X-Forwarded-Proto` HTTP headers to determine -client IP addresses. This is useful if the Edge Proxy is running behind a reverse proxy, and you want the -[access logs](#logging.override) to show the real IP addresses of your clients. - -By default, only the loopback address is trusted. This can be changed with the [`FORWARDED_ALLOW_IPS` environment -variable](#environment-variables). - -```json -"server": { - "proxy_headers": true -} -``` - -``` -SERVER_PROXY_HEADERS=true -``` - -### Logging - -#### `logging.log_level` - -Choose a logging level from `"CRITICAL"`, `"ERROR"`, `"WARNING"`, `"INFO"`, `"DEBUG"`. Defaults to `"INFO"`. - -```json -"logging": {"log_level": "DEBUG"} -``` - -#### `logging.log_format` - -Choose a logging format between `"generic"` and `"json"`. Defaults to `"generic"`. - -```json -"logging": {"log_format": "json"} -``` - -#### `logging.log_event_field_name` - -Set a name used for human-readable log entry field when logging events in JSON. Defaults to `"message"`. - -```json -"logging": {"log_event_field_name": "event"} -``` - -#### `logging.colour` - -Added in [2.13.0](https://github.com/Flagsmith/edge-proxy/releases/tag/v2.13.0). - -Set to `false` to disable coloured output. Useful when outputting the log to a file. - -#### `logging.override` - -Added in [2.13.0](https://github.com/Flagsmith/edge-proxy/releases/tag/v2.13.0). - -Accepts -[Python-compatible logging settings](https://docs.python.org/3/library/logging.config.html#configuration-dictionary-schema) -in JSON format. You're able to define custom formatters, handlers and logger configurations. For example, to log -everything a file, one can set up own file handler and assign it to the root logger: - -```json -"logging": { - "override": { - "handlers": { - "file": { - "level": "INFO", - "class": "logging.FileHandler", - "filename": "edge-proxy.log", - "formatter": "json" - } - }, - "loggers": { - "": { - "handlers": ["file"], - "level": "INFO", - "propagate": true - } - } - } -} -``` - -Or, log access logs to file in generic format while logging everything else to stdout in JSON: - -```json -"logging": { - "override": { - "handlers": { - "file": { - "level": "INFO", - "class": "logging.FileHandler", - "filename": "edge-proxy.log", - "formatter": "generic" - } - }, - "loggers": { - "": { - "handlers": ["default"], - "level": "INFO" - }, - "uvicorn.access": { - "handlers": ["file"], - "level": "INFO", - "propagate": false - } - } - } -} -``` - -When adding logger configurations, you can use the `"default"` handler which writes to stdout and uses formatter -specified by the [`"logging.log_format"`](#logginglog_format) setting. - -### Health checks - -The Edge Proxy exposes two health check endpoints: - -* `/proxy/health/liveness`: Always responds with a 200 status code. Use this health check to determine if the Edge - Proxy is alive and able to respond to requests. -* `/proxy/health/readiness`: Responds with a 200 status if the Edge Proxy was able to fetch all its configured - environment documents within a configurable grace period. This allows the Edge Proxy to continue reporting as healthy - even if the Flagsmith API is temporarily unavailable. This health check is also available at `/proxy/health`. - -You should point your orchestration health checks to these endpoints. - -#### `health_check.environment_update_grace_period_seconds` - -Default: `30`. - -The number of seconds to allow per environment key pair before the environment data stored by the Edge Proxy is -considered stale. - -When set to `null`, cached environment documents are never considered stale, and health checks will succeed if all -environments were successfully fetched at some point since the Edge Proxy started. - -The effective grace period depends on how many environments the Edge Proxy is configured to serve. It can be calculated -using the following pseudo-Python code: - -```python -total_grace_period_seconds = api_poll_frequency + (environment_update_grace_period_seconds * len(environment_key_pairs)) -if last_updated_all_environments_at < datetime.now() - timedelta(seconds=total_grace_period_seconds): - # Data is stale - return 500 -# Data is not stale -return 200 -``` - -### Environment variables - -Some Edge Proxy settings can only be set using environment variables: - -- `WEB_CONCURRENCY` The number of [Uvicorn](https://www.uvicorn.org/) workers. Defaults to `1`, which is - [recommended when running multiple Edge Proxy containers behind a load balancer](https://fastapi.tiangolo.com/deployment/docker/#one-load-balancer-multiple-worker-containers). - If running on a single node, set this [based on your number of CPU cores and threads](https://docs.gunicorn.org/en/latest/design.html#how-many-workers). -- `HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`, `NO_PROXY`: These variables let you configure an HTTP proxy that the - Edge Proxy should use for all its outgoing HTTP requests. - [Learn more](https://www.python-httpx.org/environment_variables) -- `FORWARDED_ALLOW_IPS`: Which IPs to trust for determining client IP addresses when using the `proxy_headers` option. - For more details, see the [Uvicorn documentation](https://www.uvicorn.org/settings/#http). diff --git a/docs/docs/deployment/hosting/locally-frontend.md b/docs/docs/deployment/hosting/locally-frontend.md deleted file mode 100644 index ca060b1fe25e..000000000000 --- a/docs/docs/deployment/hosting/locally-frontend.md +++ /dev/null @@ -1,127 +0,0 @@ ---- -sidebar_label: Frontend -title: Flagsmith Frontend -sidebar_position: 20 ---- - -## Getting Started - -These instructions will get you a copy of the project front end up and running on your local machine for development and -testing purposes. See running in production for notes on how to deploy the project on a live system. - -## Prerequisites - -What things you need to install the software and how to install them. Installing nvm and their bash script will ensure -developers are on the same NodeJS version. - -| Location | Suggested Version | -| ----------------------------------------------------------------------- | :---------------: | -| NodeJS | >= 22.0.0 | -| npm | >= 8.0.0 | -| nvm | >= 0.39.0 | -| nvm zshrc setup | n/a | - -## Installing - -```bash -cd frontend -npm i -``` - -## Running the Front End - -### Development - -Hot reloading for client / server - -```bash -cd frontend -npm run dev -``` - -### Production - -You can deploy this application on [Heroku](https://www.heroku.com/) and [Dokku](http://dokku.viewdocs.io/dokku/) -without making any changes, other than the API URL in '/frontend/env/project_prod.js' - -Bundles, minifies and cache busts the project to a build folder and runs node in production. This can be used as part of -your deployment script. - -```bash -cd frontend -npm run bundle -npm start -``` - -## Environment Variables - -Variables that differ per environment are exported globally to `window.Project in` 'frontend/common/project.js', this -file gets replaced by a project.js located in 'frontend/env' by webpack based on what is set to the "ENV" environment -variable (e.g. ENV=prod). - -You can override each variable individually or add more by editing 'frontend/bin/env.js'. - -Current variables used between 'frontend/environment.js' and 'frontend/common/project.js': - -- `FLAGSMITH_API_URL`: The API to hit for requests. E.g. `https://edge.api.flagsmith.com/api/v1/` -- `FLAGSMITH_ON_FLAGSMITH_API_KEY`: The flagsmith environment key we use to manage features - - [Flagsmith runs on Flagsmith](/deployment#running-flagsmith-on-flagsmith). -- `FLAGSMITH_ON_FLAGSMITH_API_URL`: The API URL which the flagsmith client should communicate with. Flagsmith runs on - flagsmith. E.g. `https://edge.api.flagsmith.com/api/v1/`. If you are self hosting and using your own Flagsmith - instance to manage its own features, you would generally point this to the same domain name as your own Flagsmith - instance. -- `DISABLE_ANALYTICS_FEATURES`: Disables any in-app analytics-related features: API Usage charts, flag analytics. E.g. - `DISABLE_ANALYTICS_FEATURES=1`. -- `ENABLE_FLAG_EVALUATION_ANALYTICS`: Determines if the flagsmith sdk should send usage analytics, if you want to enable - Flag Analytics, set this. E.g. `ENABLE_FLAG_EVALUATION_ANALYTICS=1`. -- `PROXY_API_URL`: Proxies the API via this application. Set this to the hostname of the API being proxied. Proxies - `/api/v1/` through to `PROXY_API_URL`. If you are using this, any setting to `FLAGSMITH_API_URL` will be ignored and - the browser will use the front end node server to send API requests. Do not prepend `api/v1/` - it will be added - automatically. -- `GOOGLE_ANALYTICS_API_KEY`: Google Analytics key to track API usage. -- `CRISP_WEBSITE_ID`: Crisp Chat widget Website key. -- `FIRST_PROMOTER_ID`: First Promoter ID for checkout affiliates. -- `ALLOW_SIGNUPS`: **DEPRECATED** in favour of `PREVENT_SIGNUP` in the API. Determines whether to prevent manual signups without - invites. Set it to any value to allow signups. -- `PREVENT_SIGNUP`: **DEPRECATED** in favour of `PREVENT_SIGNUP` in the API. Determines whether to prevent manual signups. Set it to any value to prevent hide signup pages. -- `PREVENT_FORGOT_PASSWORD`: Determines whether to prevent forgot password functionality, useful for LDAP/SAML. Set it - to any value to prevent forgot password functionality. -- `PREVENT_EMAIL_PASSWORD`: Disables email address signup, login and change email functionality. -- `ENABLE_MAINTENANCE_MODE`: Puts the site into maintenance mode. Set it to any value to enable maintenance. -- `AMPLITUDE_API_KEY`: The Amplitude key to use for behaviour tracking. -- `REO_API_KEY`: The Reo key to use for behaviour tracking. -- `SENTRY_API_KEY`: Sentry key for error reporting. -- `ALBACROSS_CLIENT_ID`: Albacross client ID key for behaviour tracking. -- `BASE_URL`: Used for specifying a base url path that's ignored during routing if serving from a subdirectory. -- `USE_SECURE_COOKIES`: Enable / disable the use of secure cookies. If deploying the FE in a private network without a - domain / SSL cert, disable secure cookies to ensure that session token is persisted. Default: true. -- `COOKIE_SAME_SITE`: Define the value of the samesite attribute for the session token cookie set by the frontend. - Further reading on this value is available [here](https://web.dev/articles/samesite-cookies-explained). Default: - 'none'. - -### GitHub Integration Environment Variables - -- `GITHUB_APP_URL`: You can obtain the URL of your GitHub App in the 'About' section -> 'public link' and append - '/installations/select_target' to it. E.g. `https://github.com/apps/my-github-app/installations/select_target` - -## E2E testing - -This project uses [Test Cafe](https://testcafe.io/) for automated end to end testing with Chromedriver. - -```bash -npm test -``` - -## Built With - -- React -- Webpack -- Node - -## Running locally against your own Flagsmith API instance - -We use Flagsmith to manage features we rollout, if you are using your own Flagsmith environment (i.e. by editing -project_x.js-> flagsmith) then you will need to have a replica of our flags. - -A list of the flags and remote config we're currently using in production can be -[found here](/deployment#current-flagsmith-feature-flags). diff --git a/docs/docs/deployment/hosting/openshift.md b/docs/docs/deployment/hosting/openshift.md deleted file mode 100644 index ae47e6f77a2f..000000000000 --- a/docs/docs/deployment/hosting/openshift.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -title: OpenShift -description: Getting Started with Flagsmith on OpenShift -sidebar_label: OpenShift -sidebar_position: 60 ---- - -:::tip - -Support for our OpenShift Operator is only available with an [Enterprise plan](https://flagsmith.com/pricing/). - -::: - -Flagsmith runs on the OpenShift platform via our [Helm charts](kubernetes.md). diff --git a/docs/docs/deployment/hosting/real-time/benchmark.js b/docs/docs/deployment/hosting/real-time/benchmark.js deleted file mode 100644 index 67acc6139c36..000000000000 --- a/docs/docs/deployment/hosting/real-time/benchmark.js +++ /dev/null @@ -1,47 +0,0 @@ -// Stress test for the Flagsmith SSE service -// https://docs.flagsmith.com/deployment/hosting/real-time - -import { sleep } from 'k6'; -import http from 'k6/http'; - -export const options = { - discardResponseBodies: true, - scenarios: { - // Gradually ramp up to 20k concurrent subscribers for the same environment - subscribe: { - exec: 'subscribers', - executor: 'ramping-vus', - stages: [{ duration: '1m', target: 10000 }], - }, - // Publish an update to the same environment every 10s - publish: { - duration: '1m', - exec: 'publish', - executor: 'constant-vus', - vus: 1, - }, - }, -}; - -const env = 'load_test'; -export function subscribers() { - http.get(`http://localhost:8088/sse/environments/${env}/queue-change`, '', { - headers: { - Accept: 'event/stream', - }, - timeout: '3m', - }); -} - -export function publish() { - const body = JSON.stringify({ - updated_at: new Date().toISOString(), - }); - http.post(`http://localhost:8088/sse/environments/${env}/queue-change`, body, { - headers: { - 'Content-Type': 'application/json', - Authorization: 'Token ' + __ENV.SSE_AUTHENTICATION_TOKEN, - }, - }); - sleep(10); -} diff --git a/docs/docs/deployment/hosting/real-time/deployment.md b/docs/docs/deployment/hosting/real-time/deployment.md deleted file mode 100644 index f117573903ee..000000000000 --- a/docs/docs/deployment/hosting/real-time/deployment.md +++ /dev/null @@ -1,156 +0,0 @@ ---- -title: Deployment guide -sidebar_label: Deployment -sidebar_position: 30 ---- - -## Redis - -First, deploy a Redis-compatible store, such as [Valkey](https://valkey.io/). Many managed options for Redis-compatible -stores are offered by different cloud providers. Some examples: - -- [Amazon ElastiCache (AWS)](https://aws.amazon.com/elasticache/features/) -- [Memorystore for Valkey (GCP)](https://cloud.google.com/memorystore/docs/valkey/product-overview) -- [Azure cache for Redis](https://azure.microsoft.com/en-us/products/cache) - -Clustering Redis is recommended for high availability, but not required. All -[current Redis versions](https://redis.io/docs/latest/operate/rs/installing-upgrading/product-lifecycle/) are supported. - -### Authentication - -By default, the SSE service will try to connect to Redis without a username or password. If needed, these can be set -with the `REDIS_USERNAME` and `REDIS_PASSWORD` environment variables. - -## SSE service - -Run the `flagsmith/sse` image, setting these environment variables: - -| Variable name | Description | Default | -|---------------------------------|---------------------------------------------------------------------------------------------------------------------------------|--------------| -| `SSE_AUTHENTICATION_TOKEN` | Shared secret for authentication on the `/queue-change` endpoint | **Required** | -| `REDIS_HOST` | Hostname of the Redis load balancer | `localhost` | -| `REDIS_PORT` | Port number to use when connecting to Redis | 6379 | -| `REDIS_USERNAME` | Username to use when connecting to Redis | None | -| `REDIS_PASSWORD` | Password to use when connecting to Redis | None | -| `REDIS_SCAN_COUNT` | Number of Redis keys to [`SCAN`](https://redis.io/docs/latest/commands/scan/) at once when updating the in-memory cache | 500 | -| `CACHE_UPDATE_INTERVAL_SECONDS` | How long (in seconds) to wait between each `SCAN` to update the in-memory cache | 1 | -| `USE_CLUSTER_MODE` | Whether to connect to Redis with [Cluster Mode](https://redis.io/docs/latest/operate/oss_and_stack/management/scaling/) enabled | `False` | -| `REDIS_USE_SSL` | Whether to connect to Redis using TLS | `False` | -| `MAX_STREAM_AGE` | How long (in seconds) to keep SSE connections alive for. If negative, connections are kept open indefinitely | 30 | -| `STREAM_DELAY` | How long (in seconds) to wait before checking the internal cache for updates | 1 | - -The SSE service will expose its endpoints to `0.0.0.0:8000`. - -## API and task processor - -The Flagsmith API and task processor need to know about the SSE service. On both the API and task processor, set these -environment variables: - -- `SSE_SERVER_BASE_URL` points to the SSE service load balancer. For example: `http://my-sse-service:8000` -- `SSE_AUTHENTICATION_TOKEN` can be set to any non-empty string, as long as the SSE service and task processor share the - same value. - -## Flagsmith configuration - -Make sure the Flagsmith projects you are updating have real-time updates enabled. If not, no tasks will be queued when -its environments are updated. - -Lastly, client applications should set their Flagsmith SDK's realtime endpoint URL to the load balancer for the SSE -service. - -## Example: Helm chart - -The [Flagsmith Helm chart](https://github.com/Flagsmith/flagsmith-charts) can be used to deploy the SSE service when -`sse.enabled`is `true`. A minimal example is shown below. - -```yaml title="values.yaml" -sse: - enabled: true - image: - repository: flagsmith/sse - tag: 4.0.0 - imagePullPolicy: "" - imagePullSecrets: [] - extraEnv: - REDIS_HOST: your_redis.example.com - REDIS_PORT: 6379 - REDIS_USE_SSL: false # set to true if connecting to Redis over TLS - USE_CLUSTER_MODE: false # set to true if connecting to a Redis cluster, not a single node - extraEnvFromSecret: - REDIS_PASSWORD: # Optional, if your Redis requires authentication - secretName: my_redis_secret - secretKey: my_redis_password -``` - -## Example: Docker Compose - -The following Docker Compose file defines a simple Flagsmith deployment. The highlighted lines are required to support -real-time flag updates. - -```yaml title="compose.yaml" -services: - # highlight-start - valkey: - image: valkey/valkey:latest - # highlight-end - - # highlight-start - sse: - image: flagsmith/sse:3.3.0 - environment: - SSE_AUTHENTICATION_TOKEN: changeme - REDIS_HOST: valkey - depends_on: - - valkey - # highlight-end - - flagsmith: - # highlight-start - image: flagsmith/flagsmith-private-cloud:latest - # highlight-end - environment: - # highlight-start - SSE_AUTHENTICATION_TOKEN: changeme - SSE_SERVER_BASE_URL: 'http://sse:8000' - # highlight-end - DATABASE_URL: postgresql://postgres:password@postgres:5432/flagsmith - USE_POSTGRES_FOR_ANALYTICS: 'true' - ENVIRONMENT: production - DJANGO_ALLOWED_HOSTS: '*' - ALLOW_ADMIN_INITIATION_VIA_CLI: 'true' - FLAGSMITH_DOMAIN: 'localhost:8000' - DJANGO_SECRET_KEY: secret - ENABLE_ADMIN_ACCESS_USER_PASS: 'true' - TASK_RUN_METHOD: TASK_PROCESSOR - ports: - - '8000:8000' - depends_on: - - postgres - - # The flagsmith_processor service is only needed if TASK_RUN_METHOD set to TASK_PROCESSOR - # in the application environment - flagsmith_processor: - image: flagsmith/flagsmith:latest - environment: - # highlight-start - SSE_AUTHENTICATION_TOKEN: changeme - SSE_SERVER_BASE_URL: 'http://sse:8000' - # highlight-end - DATABASE_URL: postgresql://postgres:password@postgres:5432/flagsmith - USE_POSTGRES_FOR_ANALYTICS: 'true' - depends_on: - - flagsmith - command: run-task-processor - - postgres: - image: postgres:15.5-alpine - environment: - POSTGRES_PASSWORD: password - POSTGRES_DB: flagsmith - container_name: flagsmith_postgres - volumes: - - pgdata:/var/lib/postgresql/data - -volumes: - pgdata: -``` diff --git a/docs/docs/deployment/hosting/real-time/index.md b/docs/docs/deployment/hosting/real-time/index.md deleted file mode 100644 index 69ef57dcc5b0..000000000000 --- a/docs/docs/deployment/hosting/real-time/index.md +++ /dev/null @@ -1,109 +0,0 @@ ---- -title: Self-hosting real-time updates -sidebar_label: Real-time flags -sidebar_position: 30 ---- - -If you are self-hosting Flagsmith, using [real-time flag updates](/advanced-use/real-time-flags.md) requires you to -deploy additional infrastructure. Start here for an overview of how this infrastructure delivers this capability in your -self-hosted Flagsmith. - -You might also be interested in: - -- [How to deploy the infrastructure for real-time flag updates](deployment) -- [How to operate a real-time flag updates system](operations) - -## Prerequisites - -Real-time flag updates require an Enterprise subscription. - -We assume you already have the [Flagsmith API](/deployment/hosting/locally-api.md) running on your infrastructure. - -Real-time flag updates are only delivered for Flagsmith projects that have the "Real-time Updates" -(`enable_realtime_updates`) setting enabled. This option can be found in **Project Settings** > **SDK Settings**. - -## Infrastructure - -The **real-time flag updates system** is supported by additional infrastructure that your existing Flagsmith deployment -integrates with: - -- **Server-sent events (SSE) service containers**, running the private - [`flagsmith/sse`](https://hub.docker.com/repository/docker/flagsmith/sse) Docker image. These serve the real-time - endpoint that Flagsmith clients can connect to, and receive updates from the - [task processor](/deployment/configuration/task-processor). -- A Redis-compatible key-value store that the [task processor](/deployment/configuration/task-processor.md) and SSE - service can connect to. - -This diagram shows how all the components initiate connections to each other: - -```mermaid -graph LR - api[Task processor] - sse[SSE service] - redis[Redis] - client1[Client] - client2[Client] - - api -->|Publish| sse - sse -->|Fetch| redis - client1 -->|Connect| sse - client2 -->|Connect| sse -``` - -## How it works - -Real-time flags use a fully distributed and horizontally scalable architecture. Any SSE service instance can respond to -any client's request. All components can be scaled out or in as needed. Stateful or sticky sessions are not used. - -The following sequence diagram shows how a Flagsmith client application connects to the real-time updates stream and -receives messages when the environment is updated. - -```mermaid -sequenceDiagram - loop Every second, background - SSE service->>Redis: Fetch update timestamps for all environments - SSE service-->SSE service: Update in-memory cache - end - Client->>SSE service: Subscribe to environment updates - SSE service->>Client: Send latest update timestamp - loop Every second, per subscriber - SSE service-->SSE service: Read latest update from in-memory cache - SSE service->>Client: Send latest update timestamp, if changed - Client-->Client: Store latest update timestamp - end - Task processor->>SSE service: Notify environment updated - SSE service->>Redis: Store latest update timestamp -``` - -### SSE service - -The **server-sent events (SSE)** service provides the real-time API endpoints that Flagsmith clients connect to. Clients -connect to any service instance and hold an HTTP connection open for as long as they want to receive updates over SSE. - -This service also accepts HTTP requests from the Flagsmith task processor to get notified of environment updates. Redis -is used as the storage layer to distribute these updates to clients. - -[HTTP/2 is recommended](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) -for client connections, especially if the clients are web browsers. - -### Redis - -Redis stores a key for each environment containing a timestamp of when it was last updated. SSE service instances will -periodically fetch all environment's keys and cache them in memory. This cache is used to publish update notifications -to connected clients. - -If Redis or its stored data are unavailable, clients will not be able to receive updates. - -## How to use it - -Refer to the [deployment guide](deployment) for instructions on setting up the required infrastructure. - -The `flagsmith/sse` service provides the following HTTP endpoints: - -| Method | Route | Called by | Description | Authentication | -| ------ | ---------------------------------------------- | ------------------- | -------------------------------------------------------------- | -------------------------------- | -| GET | `/sse/environments/{environment}/stream` | Client applications | Subscribe to an SSE stream for the given environment. | None | -| POST | `/sse/environments/{environment}/queue-change` | Task processor | Notify the SSE service that the given environment was updated. | `Token SSE_AUTHENTICATION_TOKEN` | - -The stream protocol is described in the -[documentation for real-time flag updates](/advanced-use/real-time-flags#implementation-details). diff --git a/docs/docs/deployment/hosting/real-time/operations.md b/docs/docs/deployment/hosting/real-time/operations.md deleted file mode 100644 index 13eca56d2248..000000000000 --- a/docs/docs/deployment/hosting/real-time/operations.md +++ /dev/null @@ -1,144 +0,0 @@ ---- -title: Operations -sidebar_label: Operations -sidebar_position: 30 ---- - -The Flagsmith API is always the primary source of flag data for client applications. Real-time flag updates, as the name -implies, are only a messaging channel for notifying client applications of any updates. These updates are control -messages, which do not have any flag data. - -This document describes how to operate the real-time updates system for scale and reliability. - -## Operational goals - -The real-time updates system optimises for the following goals, listed from most to least important: - -- Delivery reliability. All subscribers receive all their updates. -- Operational reliability. In normal circumstances, any part of the system can be replaced at any time while maintaining - availability overall. Handle unexpected traffic and chaotic events gracefully and without involving humans. -- Deployment flexibility. Introduce as few dependencies as possible to minimise security and policy risks. Support - deploying to as many environments as possible, in the cloud or on-premises. -- Scalability. Handle concurrent subscribers in the order of 100.000s with horizontal scaling. -- Speed. Deliver updates as quickly as possible. - -## Scaling factors - -The performance of the real-time updates system will depend mainly on: - -- The latency and reliability of your clients' TCP connections -- How many clients are subscribed at the same time to each SSE instance -- How many clients are being notified by any environment updates -- How often environments with active subscribers are updated - -## Availability - -At least one SSE service instance and one Redis node are required for real-time updates to work. The SSE service will -reject subscriptions from client applications if Redis is unavailable. - -Both Redis and the SSE service can be scaled horizontally to handle more concurrent subscribers and for reliability. - -## Message deliverability - -Clients that use SSE can only receive data and not send data. As such, they cannot acknowledge they have received an -update from the SSE service. Deliverability will be limited by the reliability of your clients' TCP connections. This is -a constraint of the current real-time implementation of all Flagsmith SDKs, which do not have a way to acknowledge -received messages. - -When a client subscribes to real-time updates for an environment, the SSE service will immediately return the latest -update timestamp. This increases reliability in the event of reconnections and undetectable delivery failures. - -The SSE service sends a keep-alive message to each subscriber every 15 seconds if nothing was sent. This is to prevent -load balancers, NAT gateways or other proxies from closing otherwise inactive connections. - -## Latency - -The goal is to minimise **end-to-end latency**, which is the time taken between the Flagsmith API acknowledging an -environment update, and a client getting notified of this update over SSE. Ignoring network delays, this will mainly -depend on: - -- How often the [task processor](https://docs.flagsmith.com/deployment/configuration/task-processor) is polling the API - for new tasks (by default 2 seconds) -- How long the "environment updated" tasks take to execute (varies on task processor and database load) -- How fast Redis can acknowledge the update (usually milliseconds) -- How fast the relevant SSE service instances can pull from Redis (by default every second) -- How often the SSE service checks the in-memory cache for updates for each subscriber (by default every second) -- Network delay between SSE service instances and client applications - -## Storage - -Redis stores a timestamp of when each environment was last updated, in a single key. -This key is named `sse:updated_at_json`. - -Each timestamp is stored for up to 5 minutes. - -## Security - -Subscribing to real-time updates via the SSE service does not require authentication. The SSE service allows all CORS -requests on all endpoints. - -The task processor authenticates with the SSE service when publishing an update by using a shared secret. This secret is -configured using the `SSE_AUTHENTICATION_TOKEN` environment variable. For example: - -``` -curl -X POST -H "Authorization: Token $SSE_AUTHENTICATION_TOKEN" -H "Content-Type: application/json" -d' {"updated_at":"2023-01-12T18:06:42.028060"}' http://localhost:8088/sse/environments/abcxyz/queue-change -``` - -## Monitoring - -Make sure you are [monitoring the task processor](/deployment/configuration/task-processor#monitoring), as the SSE -service depends on it. - -The SSE service exposes a health check endpoint at `/health`, which can be used for liveness and readiness checks. It -responds with 200 if the SSE service can accept incoming connections and is connected to Redis by sending it a -[`PING` command](https://redis.io/docs/latest/commands/ping/). - -## Metrics - -The SSE service does not expose any metrics. - - - -## Benchmarking - -The SSE service is constrained by: - -- The number of open HTTP connections it can keep open at the same time (memory, sockets) -- The number of subscribers that will receive a message after an update (CPU and network) - -An 11-core M3 MacBook Pro with 18 GB of memory can support at least 10.000 concurrent subscribers with simultaneous -publishing at ~1 second latency, constrained by the number of open sockets. This was tested with a -[k6 script](benchmark.js) that opens many subscriptions to one environment on the SSE service while pushing updates to -that environment. - -You can monitor the load test while it's running by connecting to the SSE service as another subscriber: - -``` -$ curl -H "Accept:text/event-stream" -N -i http://localhost:8088/sse/environments/load_test/stream -HTTP/1.1 200 OK -Cache-Control: no-cache -Connection: keep-alive -Content-Type: text/event-stream -Date: Mon, 09 Dec 2024 16:20:00 GMT -Transfer-Encoding: chunked - -event: environment_updated -data: {"updated_at": 1735229888.76} -retry: 15000 - -event: environment_updated -data: {"updated_at": 1735229899.44} -retry: 15000 -``` - -The `queue-change` endpoint can achieve at least 20.000 requests per second on the same hardware. - -If you want to benchmark Redis itself, you can use its -[first-party benchmarking tools](https://redis.io/docs/latest/operate/oss_and_stack/management/optimization/benchmarks/). diff --git a/docs/docs/deployment/index.md b/docs/docs/deployment/index.md deleted file mode 100644 index 1e1fb2e5dee9..000000000000 --- a/docs/docs/deployment/index.md +++ /dev/null @@ -1,745 +0,0 @@ ---- -title: Deployment -sidebar_position: 1 ---- - -## Flagsmith SaaS Platform - -If you would rather skip the hosting and jump straight to integrating Flagsmith with your own application, you can use -[https://flagsmith.com/](https://flagsmith.com/) right now. We have -[paid plans with pricing to suit both startups and enterprise customers alike](https://flagsmith.com/pricing). - -## Terraform Templates - -We have a number of example deployments across different providers and orchestration frameworks in our -[Terraform Examples](https://github.com/Flagsmith/terraform-examples) repository. - -## One Click Installers - -[![Deploy to DigitalOcean](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/flagsmith/flagsmith/tree/main) - -[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/flagsmith/flagsmith/tree/main) - -[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/36mGw8?referralCode=DGxv1S) - -![Fly.io](/img/logos/fly.io.svg) - -We're big fans of [Fly.io](https://Fly.io)! You can deploy to fly.io really easily: - -```bash -git clone git@github.com:Flagsmith/flagsmith.git -cd flagsmith -flyctl postgres create --name flagsmith-flyio-db -flyctl apps create flagsmith-flyio -flyctl postgres attach --postgres-app flagsmith-flyio-db -flyctl deploy -``` - -Fly.io has a global application namespace, and so you may need to change the name of the application defined in -[`fly.toml`](https://github.com/Flagsmith/flagsmith/blob/main/fly.toml) as well as the commands above. - -### Caprover - -You can also deploy to a [Caprover Server](https://caprover.com/) with -[One Click Apps](https://caprover.com/docs/one-click-apps.html). - -## Self Hosting Overview - -You will need to run through the following steps to get set up: - -1. Create a Postgres database to store the Flagsmith data. -2. Deploy the API and set up DNS for it. If you are using health-checks, make sure to use `/health` as the health-check - endpoint. -3. Visit `http:///api/v1/users/config/init/` to create an initial Superuser and provide DNS - info to the platform. -4. Deploy the Front End Dashboard and set up DNS for it. Point the Dashboard to the API using the relevant Environment - Variables. If you are using health-checks, make sure to use `/health` as the health-check endpoint. -5. Create a new Organisation, Project, Environment and Flags via the Dashboard. -6. When using our SDKs, you will need to override the API URL that they point to, otherwise they will default to connect - to our paid-for API at `https://edge.api.flagsmith.com/api/v1`. See the SDK documentation for the library you are - using. - -## Deployment Options - -We recommend running Flagsmith with [Docker](/deployment/hosting/docker). We have options to run within -[Docker](/deployment/hosting/docker), [Kubernetes](/deployment/hosting/kubernetes) or -[RedHat OpenShift](/deployment/hosting/openshift). - -## Architecture - -The Flagsmith architecture is based around a REST API that is accessed by both SDK clients and the Flagsmith Dashboard -Front End Web App. - -![Application Architecture](/img/architecture.svg) - -## Dependencies - -Running the API has the following hard dependencies: - -- Postgres database - the main data store. Postgres 12 or later is required. - -The API can also optionally make use of the following 3rd party services: - -- Google Analytics - for API analytics -- InfluxDB - for API analytics -- SendGrid - for transactional email -- AWS S3 - to store Django Static Assets -- GitHub - OAuth provider -- Google - OAuth provider - -## Flag Analytics - -Flagsmith stores time series data for two use cases: - -1. Flag Analytics -2. API traffic reporting - -Flagsmith can be configured to store and process this data in one of three ways: - -1. To store it in Postgres -2. To store it in InfluxDB -3. To not store it at all - -We recommend option 1. - -### Time series data via Postgres - -Add the following environment variables to the Flagsmith API service: - -```bash -# Set Postgres to store the data -USE_POSTGRES_FOR_ANALYTICS=True - -# Configure the postgres datastore: -# Either -ANALYTICS_DATABASE_URL (e.g. postgresql://postgres:password@postgres:5432/flagsmith) -# Or -DJANGO_DB_HOST_ANALYTICS (e.g. postgres.db) -DJANGO_DB_NAME_ANALYTICS (e.g. flagsmith) -DJANGO_DB_USER_ANALYTICS (e.g. postgres_user) -DJANGO_DB_PASSWORD_ANALYTICS (e.g. postgres_password) -DJANGO_DB_PORT_ANALYTICS (e.g. 5432) -``` - -Note that you don't have to use the same database or database server as the core Flagsmith DB. - -You will also need to be running the [Task Processor](/deployment/configuration/task-processor) for downsampling to work -and the stats to start showing up in the dashboard. This process can take up to 1 hour. - -## API Telemetry - -Flagsmith collects information about self hosted installations. This helps us understand how the platform is being used. -This data is _never_ shared outside of the organisation, and is anonymous by design. You can opt out of sending this -telemetry on startup by setting the `ENABLE_TELEMETRY` environment variable to `False`. - -We collect the following data on startup and then once every 8 hours per API server instance: - -- Total number of Organisations -- Total number of Projects -- Total number of Environments -- Total number of Features -- Total number of Segments -- Total number of Users -- DEBUG django variable -- ENV django variable -- API server external IP address - -## Running Flagsmith on Flagsmith - -Flagsmith uses Flagsmith to control features on the front end dashboard. If you are self hosting the platform, you will -sometimes see features greyed out, or you may want to disable specific features, e.g. logging in via Google and GitHub. -If you are using your own Flagsmith environment then you will need to have a replica of our flags in order to control -access to those features. - -To do this,firstly create a new project within your self-hosted Flagsmith application. This is the project that we will -use to control the features of the self-hosted Flagsmith instance. We will then point the self hosted front end -dashboard at this Flagsmith project in order to control what features show for your self hosted Flagsmith instance. - -Once you have created the project, you need to set the following -[Front End](https://github.com/Flagsmith/flagsmith-frontend) environment variables in order to configure this: - -- `FLAGSMITH_ON_FLAGSMITH_API_KEY` - - The Flagsmith Client-side Environment Key we use to manage features - Flagsmith runs on Flagsmith. This will be the - API key for the project you created as instructed above. -- `ENABLE_FLAGSMITH_REALTIME` - - Determines whether the Flagsmith on Flagsmith SDK uses Realtime. -- `FLAGSMITH_ON_FLAGSMITH_API_URL` - - The API URL which the Flagsmith front end dashboard should communicate with. This will most likely be the domain - name of the Flagsmith API you are self hosting: Flagsmith runs on Flagsmith. E.g. For our SaaS hosted platform, the - variable is `https://edge.api.flagsmith.com/api/v1/`. For example, if you were running everything locally using the - standard [docker-compose setup](https://github.com/Flagsmith/flagsmith-docker), you would use - `http://localhost:8000/api/v1/` - -Once you have set this up, you should see the Flagsmith front end requesting its own flags from the API (you can look in -your browser developer console to see this). You can now start creating flags and overriding the default behaviours of -the platform. For example, if you wanted to disable Google OAuth authentication, you would create a flag called -`oauth_google` and disable it. - -### Current Flagsmith Feature Flags - -The list of the flags and remote config we're currently using in production is below: - -| Flag Name | Description | Text Value | -| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | -| `announcement` | Shows an announcement at the top of the app | None | -| `butter_bar` | Show html in a butter bar for certain users | None | -| `default_environment_names_for_new_project` | Names of default environments to create when creating a new project (e.g. `["Development", "Production"]`) | None | -| `disable_create_org` | Turning this on will prevent users from creating any additional organisations | None | -| `disable_users_as_reviewers` | If enabled, this flag will hide the Assigned users section in the Change Requests and in the Create Change Request modal in the Features page. | None | -| `feature_versioning` | Opt into feature versioning for your environment | None | -| `integration_data` | Integration config for different providers | [See Below](#integration_data) | -| `mailing_list` | Determines if mailing list consent is shown on signup | None | -| `max_api_calls_alert` | If enabled, shows an alert message in the top banner when the organization is over a 70% of its API calls limit | None | -| `oauth_github` | GitHub login key | [See Below](#oauth_github) | -| `oauth_google` | Google login key | [See Below](#oauth_google) | -| `payments_enabled` | Determines whether to show payment UI / seats | None | -| `plan_based_access` | Controls rbac and 2f based on plans | None | -| `saml` | Enables SAML authentication | [See](/system-administration/authentication/SAML) | -| `segment_operators` | Determines what rules are shown when creating a segment | [See Below](#segment_operators) | -| `sso_idp` | For self hosted, this will automatically redirect to the pre configured IdP | None | -| `verify_seats_limit_for_invite_links` | Determines whether to show los invite links | None | - -### `integration_data` - -```json -{ - "datadog": { - "perEnvironment": false, - "image": "/static/images/integrations/datadog.svg", - "docs": "https://docs.flagsmith.com/integrations/apm/datadog", - "fields": [ - { - "key": "base_url", - "label": "Base URL" - }, - { - "key": "api_key", - "label": "API Key", - "hidden": true - }, - { - "key": "use_custom_source", - "label": "Use Custom Source", - "inputType": "checkbox", - "default": true - } - ], - "tags": ["logging"], - "title": "Datadog", - "description": "Sends events to Datadog for when flags are created, updated and removed. Logs are tagged with the environment they came from e.g. production.", - "project": true - }, - "dynatrace": { - "perEnvironment": true, - "image": "/static/images/integrations/dynatrace.svg", - "docs": "https://docs.flagsmith.com/integrations/apm/dynatrace", - "fields": [ - { - "key": "base_url", - "label": "Base URL" - }, - { - "key": "api_key", - "label": "API Key", - "hidden": true - }, - { - "key": "entity_selector", - "label": "Entity Selector" - } - ], - "tags": ["logging"], - "title": "Dynatrace", - "description": "Sends events to Dynatrace for when flags are created, updated and removed. Logs are tagged with the environment they came from e.g. production.", - "project": true - }, - "jira": { - "perEnvironment": false, - "image": "https://docs.flagsmith.com/img/integrations/jira/jira-logo.svg", - "docs": "https://docs.flagsmith.com/integrations/project-management/jira", - "external": true, - "title": "Jira", - "description": "View your Flagsmith Flags inside Jira.", - "project": true, - "organisation": true - }, - "github": { - "perEnvironment": false, - "image": "https://docs.flagsmith.com/img/integrations/github/github-logo.svg", - "docs": "https://docs.flagsmith.com/integrations/project-management/github", - "external": true, - "title": "GitHub", - "isExternalInstallation": true, - "description": "View your Flagsmith Flags inside your GitHub Issues and Pull Request.", - "project": true - }, - "slack": { - "perEnvironment": true, - "isOauth": true, - "image": "/static/images/integrations/slack.svg", - "docs": "https://docs.flagsmith.com/integrations/slack", - "tags": ["messaging"], - "title": "Slack", - "description": "Sends messages to Slack when flags are created, updated and removed. Logs are tagged with the environment they came from e.g. production.", - "project": true - }, - "amplitude": { - "perEnvironment": true, - "image": "/static/images/integrations/amplitude.svg", - "docs": "https://docs.flagsmith.com/integrations/analytics/amplitude", - "fields": [ - { - "key": "api_key", - "label": "API Key", - "hidden": true - }, - { - "key": "base_url", - "label": "Base URL" - } - ], - "tags": ["analytics"], - "title": "Amplitude", - "description": "Sends data on what flags served to each identity.", - "project": true - }, - "new-relic": { - "perEnvironment": false, - "image": "/static/images/integrations/new_relic.svg", - "docs": "https://docs.flagsmith.com/integrations/apm/newrelic", - "fields": [ - { - "key": "base_url", - "label": "New Relic Base URL" - }, - { - "key": "api_key", - "label": "New Relic API Key", - "hidden": true - }, - { - "key": "app_id", - "label": "New Relic Application ID" - } - ], - "tags": ["analytics"], - "title": "New Relic", - "description": "Sends events to New Relic for when flags are created, updated and removed.", - "project": true - }, - "segment": { - "perEnvironment": true, - "image": "/static/images/integrations/segment.svg", - "docs": "https://docs.flagsmith.com/integrations/analytics/segment", - "fields": [ - { - "key": "api_key", - "label": "API Key", - "hidden": true - } - ], - "tags": ["analytics"], - "title": "Segment", - "description": "Sends data on what flags served to each identity.", - "project": true - }, - "rudderstack": { - "perEnvironment": true, - "image": "/static/images/integrations/rudderstack.svg", - "docs": "https://docs.flagsmith.com/integrations/analytics/rudderstack", - "fields": [ - { - "key": "base_url", - "label": "Rudderstack Data Plane URL" - }, - { - "key": "api_key", - "label": "API Key", - "hidden": true - } - ], - "tags": ["analytics"], - "title": "Rudderstack", - "description": "Sends data on what flags served to each identity.", - "project": true - }, - "webhook": { - "perEnvironment": true, - "image": "/static/images/integrations/webhooks.svg", - "docs": "https://docs.flagsmith.com/integrations/webhook", - "fields": [ - { - "key": "url", - "label": "Your Webhook URL Endpoint" - }, - { - "key": "secret", - "label": "Your Webhook Secret", - "hidden": true - } - ], - "tags": ["analytics"], - "title": "Webhook", - "description": "Sends data on what flags served to each identity to a Webhook Endpoint you provide.", - "project": true - }, - "heap": { - "perEnvironment": true, - "image": "/static/images/integrations/heap.svg", - "docs": "https://docs.flagsmith.com/integrations/analytics/heap", - "fields": [ - { - "key": "api_key", - "label": "API Key", - "hidden": true - } - ], - "tags": ["analytics"], - "title": "Heap Analytics", - "description": "Sends data on what flags served to each identity.", - "project": true - }, - "mixpanel": { - "perEnvironment": true, - "image": "/static/images/integrations/mp.svg", - "docs": "https://docs.flagsmith.com/integrations/analytics/mixpanel", - "fields": [ - { - "key": "api_key", - "label": "Project Token", - "hidden": true - } - ], - "tags": ["analytics"], - "title": "Mixpanel", - "description": "Sends data on what flags served to each identity.", - "project": true - }, - "grafana": { - "perEnvironment": false, - "image": "/static/images/integrations/grafana.svg", - "docs": "https://docs.flagsmith.com/integrations/apm/grafana", - "fields": [ - { - "key": "base_url", - "label": "Base URL", - "default": "https://grafana.com" - }, - { - "key": "api_key", - "label": "Service account token", - "hidden": true - } - ], - "tags": ["logging"], - "title": "Grafana", - "description": "Receive Flagsmith annotations to your Grafana instance on feature flag and segment changes.", - "project": true, - "organisation": true - } -} -``` - -### `segment_operators` - -```json -[ - { - "value": "EQUAL", - "label": "Exactly Matches (=)" - }, - { - "value": "NOT_EQUAL", - "label": "Does not match (!=)" - }, - { - "value": "PERCENTAGE_SPLIT", - "label": "% Split" - }, - { - "value": "GREATER_THAN", - "label": ">", - "type": "number" - }, - { - "value": "GREATER_THAN_INCLUSIVE", - "label": ">=", - "type": "number" - }, - { - "value": "LESS_THAN", - "label": "<", - "type": "number" - }, - { - "value": "LESS_THAN_INCLUSIVE", - "label": "<=", - "type": "number" - }, - { - "value": "GREATER_THAN:semver", - "label": "SemVer >", - "append": ":semver" - }, - { - "value": "GREATER_THAN_INCLUSIVE:semver", - "label": "SemVer >=", - "append": ":semver" - }, - { - "value": "LESS_THAN:semver", - "label": "SemVer <", - "append": ":semver" - }, - { - "value": "LESS_THAN_INCLUSIVE:semver", - "label": "SemVer <=", - "append": ":semver" - }, - { - "value": "MODULO", - "label": "Modulo", - "valuePlaceholder": "Divisor|Remainder" - }, - { - "value": "CONTAINS", - "label": "Contains" - }, - { - "value": "NOT_CONTAINS", - "label": "Does not contain" - }, - { - "value": "IN", - "label": "In", - "valuePlaceholder": "Value1,Value2" - }, - { - "value": "REGEX", - "label": "Matches regex" - }, - { - "value": "IS_SET", - "label": "Is set", - "hideValue": true - }, - { - "value": "IS_NOT_SET", - "label": "Is not set", - "hideValue": true - } -] -``` - -### `oauth_github` - -Find instructions for GitHub Authentication [here](/system-administration/authentication/OAuth#github). - -Create an OAuth application in the GitHub Developer Console and then provide the following as the Flag value: - -- Create the Flagsmith on Flagsmith flag as below replacing your `client_id` and `redirect_uri` - -```json -{ - "url": "https://github.com/login/oauth/authorize?scope=user&client_id=&redirect_uri=" -} -``` - -For example, our SaaS value looks like this (but with our Client ID redacted) - -```json -{ - "url": "https://github.com/login/oauth/authorize?scope=user&client_id=999999999999&redirect_uri=https%3A%2F%2Fapp.flagsmith.com%2Foauth%2Fgithub" -} -``` - -### `oauth_google` - -Create an OAuth application in the Google Developer Console and then provide the following as the Flag value: - -```json -{ - "clientId": "" -} -``` - -If you are using the [unified Docker image](https://hub.docker.com/repository/docker/flagsmith/flagsmith), which serves -both the API and the frontend through Django, ensure you configure the following environment variable in your -deployment: - -``` -DJANGO_SECURE_CROSS_ORIGIN_OPENER_POLICY=same-origin-allow-popups -``` - -For those hosting the frontend independently, make sure you set the `Cross-Origin-Opener-Policy` to -`same-origin-allow-popups` for Google OAuth flow to work. - -## Integrations - -Some [Integrations](/integrations) require additional configuration - -### Slack Integration - -Create a private Slack app. You will then need to provide the following environment variables on the API side: - -- `SLACK_CLIENT_ID` -- `SLACK_CLIENT_SECRET` - -You can retrieve these values from Slack. You will also need to add the following scopes: - -- `channels:read` -- `chat:write` -- `chat:write.public` - -You will also need to set up the redirect URLs for your application. For more information on this see Slack's docs on -creating your own app, and the OAuth flow that goes along with that. The production Flagsmith App Manifest reads as -follows and can be used as a template: - -```json -{ - "display_information": { - "name": "Flagsmith Bot", - "description": "Get notified in Slack whenever changes are made to your Flagsmith Environments", - "background_color": "#000000", - "long_description": "Use our application for Slack to receive Flagsmith state changes directly in your Slack channels. Whenever you create, update or delete a Flag within Flagsmith, our application for Slack will send a message into a Slack channel of your choosing.\r\n\r\nFlagsmith is an open source, fully featured, Feature Flag and Remote Config service. Use our hosted API, deploy to your own private cloud, or run on-premise." - }, - "features": { - "bot_user": { - "display_name": "Flagsmith Bot", - "always_online": false - } - }, - "oauth_config": { - "redirect_urls": [ - "https://api.flagsmith.com/api/v1/environments", - "https://api-staging.flagsmith.com/api/v1/environments" - ], - "scopes": { - "bot": ["channels:read", "chat:write", "chat:write.public"] - } - }, - "settings": { - "org_deploy_enabled": false, - "socket_mode_enabled": false, - "token_rotation_enabled": false - } -} -``` - -### Time series data via InfluxDB - -Flagsmith has a soft dependency on InfluxDB to store time-series data. You don't need to configure Influx to run the -platform; by default this data will be stored in Postgres. If you are running very high traffic loads, you might be -interested in deploying InfluxDB. - -1. Create a user account in influxdb. You can visit [http://localhost:8086/] -2. Go into Data > Buckets and create three new buckets called `default`, `default_downsampled_15m` and - `default_downsampled_1h` -3. Go into Data > Tokens and grab your access token. -4. Edit the `docker-compose.yml` file and add the following `environment` variables in the api service to connect the - api to InfluxDB: - - `INFLUXDB_TOKEN`: The token from the step above - - `INFLUXDB_URL`: `http://influxdb` - - `INFLUXDB_ORG`: The organisation ID - you can find it - [here](https://docs.influxdata.com/influxdb/v2.0/organizations/view-orgs/) - - `INFLUXDB_BUCKET`: `default` -5. Restart `docker-compose` -6. Create a new task with the following query. This will downsample your per millisecond api request data down to 15 - minute blocks for faster queries. Set it to run every 15 minutes. - -```text -option task = {name: "Downsample (API Requests)", every: 15m} - -data = from(bucket: "default") - |> range(start: -duration(v: int(v: task.every) * 2)) - |> filter(fn: (r) => - (r._measurement == "api_call")) - -data - |> aggregateWindow(fn: sum, every: 15m) - |> filter(fn: (r) => - (exists r._value)) - |> to(bucket: "default_downsampled_15m") -``` - -Once this task has run you will see data coming into the Organisation API Usage area. - -7. Create another new task with the following query. This will downsample your per millisecond flag evaluation data down - to 15 minute blocks for faster queries. Set it to run every 15 minutes. - -```text -option task = {name: "Downsample (Flag Evaluations)", every: 15m} - -data = from(bucket: "default") - |> range(start: -duration(v: int(v: task.every) * 2)) - |> filter(fn: (r) => - (r._measurement == "feature_evaluation")) - -data - |> aggregateWindow(fn: sum, every: 15m) - |> filter(fn: (r) => - (exists r._value)) - |> to(bucket: "default_downsampled_15m") -``` - -Once this task has run, and you have made some flag evaluations with analytics enabled (see documentation -[here](/advanced-use/flag-analytics.md) for information on this) you should see data in the 'Analytics' tab against each -feature in your dashboard. - -8. Create another new task with the following query. This will downsample your per millisecond api request data down to - 1 hour blocks for faster queries. Set it to run every 1 hour. - -```text -option task = {name: "Downsample API 1h", every: 1h} - -data = from(bucket: "default") - |> range(start: -duration(v: int(v: task.every) * 2)) - |> filter(fn: (r) => - (r._measurement == "api_call")) - -data - |> aggregateWindow(fn: sum, every: 1h) - |> filter(fn: (r) => - (exists r._value)) - |> to(bucket: "default_downsampled_1h") -``` - -9. Create another new task with the following query. This will downsample your per millisecond flag evaluation data down - to 1 hour blocks for faster queries. Set it to run every 1 hour. - -```text -option task = {name: "Downsample API 1h - Flag Analytics", every: 1h} - -data = from(bucket: "default") - |> range(start: -duration(v: int(v: task.every) * 2)) - |> filter(fn: (r) => - (r._measurement == "feature_evaluation")) - |> filter(fn: (r) => - (r._field == "request_count")) - |> group(columns: ["feature_id", "environment_id"]) - -data - |> aggregateWindow(fn: sum, every: 1h) - |> filter(fn: (r) => - (exists r._value)) - |> set(key: "_measurement", value: "feature_evaluation") - |> set(key: "_field", value: "request_count") - |> to(bucket: "default_downsampled_1h") -``` - -## Manual Installation - -If you want a more configurable environment, you can manually install both the Front End and the API. - -### Server Side API - -The source code and installation instructions can be found at -[the GitHub project](https://github.com/Flagsmith/flagsmith/tree/main/api). The API is written in Python and is based on -Django and the Django Rest Framework. The Server side API relies on a Postgres SQL installation to store its data, and a -Redis installation as a cache. - -### Front End Website - -The source code and installation instructions can be found at -[the GitHub project](https://github.com/Flagsmith/flagsmith/tree/main/frontend). The Front End Website is written in -React/Javascript and requires NodeJS. diff --git a/docs/docs/edge-api/Overview.md b/docs/docs/edge-api/Overview.md index c58280c8008f..655e60bdb446 100644 --- a/docs/docs/edge-api/Overview.md +++ b/docs/docs/edge-api/Overview.md @@ -13,7 +13,7 @@ find within the Flagsmith administrative area. ## Core API -Please note that the Edge API is specifically to be used with our [SDKs](/clients). If you want to drive aspects of -Flagsmith programmatically, you need to use our private [Core API](/clients/rest#private-admin-api-endpoints). +Please note that the Edge API is specifically to be used with our [SDKs](/integrating-with-flagsmith/integration-overview). If you want to drive aspects of +Flagsmith programmatically, you need to use our private [Core API](/integrating-with-flagsmith/flagsmith-api-overview/admin-api). You can find the full spec to our Core API [here](https://api.flagsmith.com/api/v1/docs/). diff --git a/docs/docs/experimentation-ab-testing.md b/docs/docs/experimentation-ab-testing.md new file mode 100644 index 000000000000..1ac74ca5e555 --- /dev/null +++ b/docs/docs/experimentation-ab-testing.md @@ -0,0 +1,81 @@ +--- +title: Experimentation (A/B Testing) +sidebar_label: Experimentation (A/B Testing) +sidebar_position: 4 +--- + +A/B testing enables you to experiment with design and functionality variants of your application. The data generated will allow you to make modifications to your app, safe in the knowledge that it will have a net positive effect. + +You can use Flagsmith to perform A/B tests. Using a combination of [multivariate flags](/managing-flags/core-management) and a 3rd party analytics tool like [Amplitude](https://amplitude.com/) or [Mixpanel](https://mixpanel.com/), you can easily perform complex A/B tests that will help improve your product. + +Running A/B tests require two main components: a bucketing engine and an analytics platform. The bucketing engine is used to put users into a particular A/B testing bucket. These buckets will control the specific user experience that is being tested. The analytics platform will receive a stream of event data derived from the behaviour of the user. Combining these two concepts allows you to deliver seamless A/B test. + +We have [integrations](/third-party-integrations/analytics/segment) with a number of analytics platforms. If we don't integrate with the platform you are using, you can still manually send the test data to the downstream platform manually. + +By the end of this tutorial, you will be able to: + +- Set up a multivariate flag in Flagsmith for A/B testing. +- Implement logic in your application to bucket users and display variants. +- Send A/B test data to an analytics platform. +- Understand how to use anonymous identities for A/B testing on unknown users. + +## Before you begin + +To follow this tutorial, you will need: + +- A basic understanding of [multivariate flags](/managing-flags/core-management) in Flagsmith. +- Access to a third-party analytics platform (e.g., Amplitude, Mixpanel) where you can send custom events. You can explore Flagsmith [integrations](/third-party-integrations/analytics/segment) for this purpose. +- A development environment for your application where you can implement changes and integrate the Flagsmith SDK. + +## Scenario - Testing a new Paypal button + +For this example, lets assume we have an app that currently accepts credit card payments only. We have a hunch that we are losing out on potential customers that would like to pay with PayPal. We're going to test whether adding PayPal to the payment options increases our checkout rate. + +We have a lot of users on our platform, so we don't want to run this test against our entire user-base. We want 90% of our users to be excluded from the test. Then for our test, 5% of our users will see the new Paypal button, and the remaining 5% will not see it. So we will have 3 buckets: + +1. Excluded (Control) Users +2. Paypal test button users +3. Test users that don't see the Paypal button + +Because Flagsmith flags can contain both boolean states as well as multivariate flag values, we can make use of both. We will use the boolean flag state to control whether to run the test. Then, if the flag is `enabled`, check the multivariate value. In this example, we will only show the PayPal button if the value is set to `show`. + +## Steps + +1. Create a new [multivariate flag](/managing-flags/core-management) that will control which of the 3 buckets the user is put into. We'll call this flag `paypal_button_test`. We will provide 3 variate options: + + 1. Control - 90% of users + 2. Paypal button - 5% of users + 3. Test users that don't see the Paypal button - 5% of users + +2. In our app, we want to [identify](/flagsmith-concepts/identities) each user before they start the checkout process. All Flagsmith multivariate flags need us to identify the user, so we can bucket them in a reproducible manner. +3. When we get to the checkout page, check the `value` of the `paypal_button_test` flag for that user. If it evaluates to `show`, show the PayPal payment button. Otherwise, don't show the button. +4. Send an event message to the analytics platform, adding the name/value pair of `paypal_button_test` and the value of the flag; in this case it would be one of either `control`, `show` or `hide`. +5. Deploy our app, enable the flag and watch the data come in to your analytics platform. + +Here is what creating the flag would look like. + +![Image](/img/ab-test-paypal-example.png) + +Once the test is set up, and the flag has been enabled, data will start streaming into the analytics platform. We can now evaluate the results of the tests based on the behavioral changes that the new button has created. + +## Handling Anonymous/Unknown Identities + +To do A/B testing you need to use identities. Without an identity to key from, it's impossible for the platform to serve a consistent experience to your users. + +What if you want to run an A/B test in an area of your application where you don't know who your users are? For example on the homepage of your website? In this instance, you need to generate _anonymous identities_ values for your users. In this case we will generate a GUID for each user. + +A GUID value is just a random string that has an extremely high likelihood of being unique. There's more info about generating GUID values [on Stack Overflow](https://stackoverflow.com/a/2117523). + +The general flow would be: + +1. A new browser visits your website homepage for the first time. +2. You see that this is an anonymous user, so you generate a random GUID for that user and assign it to them. +3. You send that GUID along with an identify call to Flagsmith. This will then segment that visitor. +4. You add a cookie to the browser and store the GUID. That way, if the user returns to your page, they will still be in the same segment. + +These techniques will be slightly different depending on what platform you are developing for, but the general concept will remain the same. + +## Next steps + +- Explore [Flagsmith's integrations](/third-party-integrations/analytics/segment) with analytics platforms. +- Learn more about [managing identities](/flagsmith-concepts/identities) in Flagsmith. diff --git a/docs/docs/flagsmith-concepts/_category_.json b/docs/docs/flagsmith-concepts/_category_.json new file mode 100644 index 000000000000..02b4880fd8f4 --- /dev/null +++ b/docs/docs/flagsmith-concepts/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Flagsmith Concepts", + "position": 2, + "collapsed": true +} diff --git a/docs/docs/flagsmith-concepts/data-model.md b/docs/docs/flagsmith-concepts/data-model.md new file mode 100644 index 000000000000..63b8a164fd4f --- /dev/null +++ b/docs/docs/flagsmith-concepts/data-model.md @@ -0,0 +1,54 @@ +--- +title: Data Model +sidebar_label: Data Model +sidebar_position: 1 +--- + +Flagsmith uses a flexible data model to help you manage feature flags and remote configurations across multiple projects, environments, and user groups. + +Here's a high-level overview of the Flagsmith data model. + +![Image](/img/flagsmith-model.svg) + +## Key Concepts + +Below you'll find a quick explanation of the main building blocks that make up the Flagsmith data model. + +### Organisations + +Organisations allow you and other team members to manage projects and their features. A user can be a member of multiple organisations. + +### Projects + +Projects contain one or more environments that share a single set of features. Organisations can have any number of projects. Paid/paying organisations can have unlimited number of projects. + +### Environments + +Environments are a way to separate the configuration of your features. For example, a feature might be enabled in your project's Development and Staging environments but turned off in your Production environment. A project can have any number of environments. + +### Features + +Features are shared across all environments within a project, but their values/states can be modified per environment. Features can be toggled on/off or assigned values (e.g., string, integer, boolean, or multivariate values). + +### Identities + +Identities are individual users associated with each environment. Registering identities within the client application allows you to manage features for individual users. Identity features can be overridden from your environment defaults. For example, joe@yourwebsite.com would be a different identity in your development environment to the one in production, and they can have different features enabled for each environment. + +For more information, see [Identities](/flagsmith-concepts/identities). + +### Traits + +You can store any number of traits against an identity. Traits are key-value pairs that can store any type of data. Some examples of traits that you might store against an identity include: + +- The number of times the user has logged in. +- Whether they have accepted the application terms and conditions. +- Their theme preference (eg. dark mode). +- Whether they have performed certain actions within your application. + +For more information, see [Traits](/flagsmith-concepts/identities#identity-traits). + +### Segments + +Segments define a group of users by traits such as login count, device, location, or any number of custom-defined traits. Similar to individual users, you can override environment defaults for features for a segment. For example, you might show certain features for a "power user" segment. + +For more information, see [Segments](/flagsmith-concepts/segments). diff --git a/docs/docs/flagsmith-concepts/identities.md b/docs/docs/flagsmith-concepts/identities.md new file mode 100644 index 000000000000..9c97629834d1 --- /dev/null +++ b/docs/docs/flagsmith-concepts/identities.md @@ -0,0 +1,110 @@ +--- +title: Identities +sidebar_label: Identities +sidebar_position: 4 +--- + +Feature flags are great, but they can be a very blunt tool, only allowing you to enable or disable flags across your entire user base. In order to target users more precisely, and to be able to perform [staged feature roll-outs](/managing-flags/rollout/rollout-by-percentage) or [A/B and multivariate tests](/experimentation-ab-testing), you need to _identify your users_. + +Identities are created within Flagsmith automatically the first time they are identified from your client SDKs. Generally, you would make a call to identify a user with a unique string or identifier whenever they log into your application or site. The SDK will then send an API message to the Flagsmith API, with the relevant identity information. + +:::tip + +The SDK part of the Flagsmith API is public by design; the environment key is intended to be public. When identifying users, it is important to use an identity value that is not easy to guess. For example, if you used an incrementing integer to identify your users, it would be trivial to request identities by enumerating this integer. This would effectively provide public access to any user traits that are associated with users. + +We strongly recommend using an unguessable, unidentifiable identity key, such as a [GUID](https://en.wikipedia.org/wiki/Universally_unique_identifier), when identifying your users, to prevent unintentionally leaking identity trait data. + +::: + +## Identity Overrides + +Once you have uniquely identified a user, you can then override features for that user from your environment defaults. For example, you've pushed a feature into production, but the relevant feature flag is still hiding that feature from all of your users. You can now override that flag for your own user, and test that feature. Once you are happy with everything, you can roll that feature out to all of your users by enabling the flag itself. + +Identities are specific and individual for each environment within your project. For example, joe@yourwebsite.com would be a different identity in your development environment to the one in production, and they can have different features enabled for each environment. + +## Identity Feature Flags + +By default, identities receive the default flags for their environment. The main use-case for identities is to be able to override flags and configs on a per-identity basis. You can do this by navigating to the users page, finding the user +and modifying their flags. + +## Identity Traits + +You can also use Flagsmith to store 'traits' against identities. traits are key/value pairs that are associated with individual identities for a particular environment. traits have two purposes outlined below, but the main use case is to drive [segments](./segments). + +:::important + +The maximum size of each individual trait value is **_2000 bytes_**. You cannot store more data than that in a single trait, and the API will return a 500 error if you try to do so. + +::: + +### Using Traits to Drive Segments + +Let's say you are working on a mobile app, and you want to control a feature based on the version of the application that the identity is using. When you integrate the Flagsmith SDK, you would pass the application version number to the Flagsmith platform as a trait key/value pair: + +```javascript +const identifier = "user_512356"; +const traits = { + app_version: YourApplication.getVersion() +} + +const flags = flagsmith.getIdentityFlags(identifier, traits); +``` + +Here we are setting the trait key `app_version` with the value of `YourApplication.getVersion()`.You can now create a [segment](./segments) that is based on the application version and manage features based on the application version. + +Traits are completely free-form. You can store any number of traits, with any relevant information you see fit, in the platform and then use segments to control features based on these trait values. + +## Identity and Trait Storage + +Identities are persisted within the Flagsmith platform, along with any traits that have been assigned to them. When flags are evaluated for an identity, the full complement of traits stored within the platform are used, even if they were not all sent as part of the request. + +This can be useful if, at runtime, your application does not have all the relevant trait data available for that particular identity; any traits provided will be combined with the traits stored within Flagsmith before the evaluation engine runs. + +There are some [exceptions to this rule](/integrating-with-flagsmith/sdks/server-side) with Server-side SDKs running in local evaluation mode. + +:::info + +Note that, when using our SaaS platform, there might be a short delay from the initial request to write or update traits for an identity and them being used in subsequent evaluations. + +::: + +### Using Traits as a Data-store + +Traits can also be used to store additional data about your users that would be cumbersome to store within your application. Some possible uses for traits could be: + +- Storing whether the user has accepted a new set of terms and conditions. +- Storing the last viewed page of the application so that you can resume the users place later, across any device. + +Generally if they are lower-value pieces of information about your user, it might be simpler/easier to store them in Flagsmith rather than in your core application. + +Traits are stored natively as either numbers, strings or booleans. + +## Traits Powering Segments + +Traits can be used within your application, but they can also be used to power [segments](/flagsmith-concepts/segments). + +## Trait Value Data Types + +:::tip + +You can remove a trait by sending `null` as the trait value. + +::: + +Trait values can be stored as one of four different data types: + +- Boolean. +- String (max length 2000 bytes). +- Int (32 bit signed). +- Float (typically has a range of around 1E-307 to 1E+308 with a precision of at least 15 digits). + +If you need to store 64 bit integers or very high precision floats we suggest storing them as strings and then doing the type conversion within the SDK. + +## Bulk Uploading Identities and Traits + +Identities are lazily created within Flagsmith. There might be instances where you want to push identity and trait data into the platform outside of a user session. We have [bulk upload API endpoints](/edge-api/bulk-insert-identities-update) for this. + +For more information, see: + +- [Managing identities](/flagsmith-concepts/identities). + diff --git a/docs/docs/flagsmith-concepts/platform-architecture.md b/docs/docs/flagsmith-concepts/platform-architecture.md new file mode 100644 index 000000000000..89f54272b6f8 --- /dev/null +++ b/docs/docs/flagsmith-concepts/platform-architecture.md @@ -0,0 +1,60 @@ +--- +title: Platform Architecture +sidebar_label: Platform Architecture +sidebar_position: 2 +--- + +# Flagsmith Platform Architecture + +Flagsmith architecture supports a range of deployment models to suit different organisational needs, from fully managed SaaS to self-hosted and on-premises solutions. + +## Conceptual Overview + +The most important components in Flagsmith's architecture are: + +- **Frontend dashboard**: A web interface for managing projects, environments, feature flags, and user permissions. This is where most configuration and management tasks are performed. +- **Core API**: RESTful API that powers the dashboard and SDKs. These endpoints are used for automation, integrations, and direct management of Flagsmith resources. +- **Edge API**: A globally distributed API for low-latency flag evaluation, especially useful for applications with users around the world. +- **SDKs**: Client libraries (available for many languages and platforms) that connect your applications and services to Flagsmith, enabling real-time feature flag evaluation and remote configuration. +- **Integrations**: Optional connectors to third-party tools and services (e.g., analytics, notifications, monitoring). These can be configured via the dashboard or API. +- **Database**: Stores all persistent data, such as organisations, projects, features, and audit logs. Managed by Flagsmith in SaaS, but by your team in self-hosted/on-prem deployments. + +The architecture is REST-based, with both SDK clients and the dashboard interacting with the Core API. The platform is designed to be cloud-native, supporting containerisation and orchestration for scalability and reliability. + +## Deployment Models + +Flagsmith can be deployed in three main ways, each with conceptual differences in management, control, and responsibility. The following diagrams illustrate the architecture for each model: + +### 1. SaaS (Cloud-Hosted) + +- **Managed by Flagsmith**: No infrastructure to manage; get started instantly. +- **Automatic scaling, security, and updates**: All handled by the Flagsmith team. +- **Global Edge API**: Provides low-latency flag delivery worldwide. +- **Best for**: Teams who wish to focus on product development and avoid infrastructure overhead. + +![SaaS Architecture](/img/saas-architecture.svg) + +*The diagram above shows the SaaS deployment model, where all infrastructure is managed by Flagsmith and the Edge API ensures global low-latency delivery.* + +### 2. Self-Hosted (Cloud or Private Cloud) + +- **Managed by your team**: Deploy Flagsmith on your own cloud infrastructure (e.g., AWS, GCP, Azure, DigitalOcean) using Docker, Kubernetes, or other orchestration tools. +- **Full control**: You manage scaling, security, updates, and integrations. +- **Customisation**: Integrate with your own authentication, analytics, and infrastructure. +- **Best for**: Teams with specific compliance, data residency, or custom integration requirements. + +### 3. On-Premises (Enterprise Edition) + +- **Deployed in your private data centre or isolated cloud**: For maximum control and data sovereignty. +- **Enterprise features**: Advanced authentication (Okta, LDAP, SAML, ADFS), custom fields, support for additional databases (Oracle, MySQL), and more. +- **Orchestration support**: Kubernetes, OpenShift, AWS ECS, GCP AppEngine, Azure Container Instances, and more. +- **Best for**: Organisations with strict regulatory, security, or data sovereignty requirements. + +![Self-Hosted / On-Prem Architecture](/img/architecture.svg) + +*The diagram above illustrates the Self-Hosted and On-Premises deployment models, where your team manages the infrastructure and has full control over security, compliance, and integrations.* + +## What's next +- [Deployment Options](/deployment-self-hosting) +- [Version Comparison](https://flagsmith.com/pricing) +- [Enterprise Edition](/deployment-self-hosting/enterprise-edition) diff --git a/docs/docs/flagsmith-concepts/segments/index.md b/docs/docs/flagsmith-concepts/segments/index.md new file mode 100644 index 000000000000..cebe9650c45b --- /dev/null +++ b/docs/docs/flagsmith-concepts/segments/index.md @@ -0,0 +1,161 @@ +--- +title: Segments +sidebar_label: Segments +--- + +import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; + +# Segments + +A segment is a subset of [identities](/flagsmith-concepts/identities), defined by a set of rules that match identity [traits](/flagsmith-concepts/identities#identity-traits). An identity always belongs to a single environment and can belong to any number of segments. + +Once you have defined a segment, you can create **segment overrides** for features within an environment. A segment override allows you to control the state of a feature only for identities that belong to a specific segment. This is similar to how [identity overrides](/flagsmith-concepts/identities#identity-overrides) let you control the state of features for an explicit set of identities that is known in advance. + +Because segments are driven by identity traits, your application must identify the user when retrieving flags in order for segment overrides to be applied. If your user is not identified, no overrides will be applied and all flags will be returned exactly as they are defined in the current environment. + +Segments and segment overrides can be used to implement many scenarios. For example: + +- Test features in production before they are released by overriding them only for internal users or a QA team. +- Deliver features only to "power users" who have logged in a certain number of times, have used specific functionality within your application, or any combination of factors. +- Force a group of users into a specific [A/B test](/experimentation-ab-testing) variation by overriding weightings on [multivariate flags](/managing-flags/core-management). +- Override behaviour based on the [application version number](/best-practices/mobile-app-versioning), e.g. by using the SemVer rule operators. +- Control features based on the time of day, date, weekday, etc. by passing it as a trait when evaluating flags for an identity. + +## Security and privacy + +The Flagsmith API to set user traits, e.g. the `setTraits` method from the JavaScript SDK, does not require authentication or credentials. This means that users can change their own traits, which could be a security problem if you are using segments for authorisation or access control. If you must use segments for access control, make sure to disable the ["Persist traits when using client-side SDK keys" option](/administration-and-security/governance-and-compliance/security) on every environment that needs it, and use server-side SDKs to set traits instead. You can still use client-side SDKs to read traits and flags derived from segments in this case. + +Segment names and definitions might include sensitive or proprietary information that you do not wish to expose to your users. Because of this, segments are transparent to applications and are not included in API responses when using [remote evaluation mode](/integrating-with-flagsmith/sdks/server-side#when-running-in-remote-evaluation-mode). + + +Segment definitions _are_ served to clients running in [local evaluation mode](/integrating-with-flagsmith/sdks/server-side#when-running-in-local-evaluation-mode), as this allows them to calculate segments without making requests to the Flagsmith API. This is only an implementation detail and no segment information is exposed when retrieving flags using any SDK method. + +## Creating project or feature-specific segments + +Segments created from the segments page of the Flagsmith dashboard can be used to override any feature within a single project. + +To create a segment override, click on a feature in a specific environment and go to the segment overrides tab. + +If you need to create a segment that will only ever be used to override a single feature, you can create a **feature-specific segment** by clicking on "Create Feature-Specific Segment" when creating a segment override. Feature-specific segments are otherwise functionally identical to project segments. By default, feature-specific segments are not shown in the Segments page, unless you enable the "Include Feature-Specific" option. + +Once created, project segments cannot be changed into feature-specific segments or vice versa. + +## Order of rules within segments + +Segment rules are evaluated in order, i.e. from top to bottom when viewed in the Flagsmith dashboard. + +For example, consider the following segment: + +1. 10% percentage split +2. `is_subscriber = true` + +This segment would first select 10% of _all_ identities, and then choose subscribers from that cohort. Instead, if we used the opposite order: + +1. `is_subscriber = true` +2. 10% percentage split + +This would first select all subscriber identities, and then randomly choose 10% of them. + +## Multiple segment overrides for one feature + +If a feature has multiple segment overrides, they are evaluated in order, i.e. from top to bottom when viewed in the Flagsmith dashboard. The first matching override will be used to determine the state of a feature for a given identity. + +## Flag evaluation precedence + +Identity overrides always take precedence over segment overrides. Simply put, the order of precedence when evaluating a flag is: + +1. Identity overrides +2. Segment overrides +3. Default value for the current environment + +## Context values + +:::warning + +Currently, context values are only available for remote evaluation. In local evaluation, rules using context values will evaluate to `false`. + +::: + +In addition to identity [traits](/flagsmith-concepts/identities#identity-traits), you can use the following context values as Segment rule properties. For more information about identities and traits, see the [Identities documentation](/flagsmith-concepts/identities). + +- **Identifier**: a unique identifier for an identity, used for segment and multivariate feature flag targeting, and displayed in the Flagsmith UI. Useful for bucketing via the [`% Split`](./segment-rule-operators#percentage-split) operator, or managing a list of users via the [`In`](./segment-rule-operators#in) operator. +- **Identity Key**: a key used when selecting a value for a multivariate feature, or for % split segmentation. Set to an internal identifier or a composite value based on the environment key and identifier, depending on Flagsmith implementation. +- **Environment Name**: an environment's human-readable name. Useful for restricting Segments to certain environments. + +Context values can be used to control your targeting more precisely: + +### Gradual rollout across tenants + +Suppose your application supports users who can belong to multiple organisations. Now, you want to roll out a new feature to 20% of your organisations, ensuring that users only have access to the feature when they are part of an organisation included in that 20%. If they switch to an organisation outside this group, they will no longer have access to the feature. + +Here's how you might define a segment to achieve this: + +| Rule Order | Property | Operator | Value | +| ---------- | ------------------------- | -------- | ----- | +| 1 | `organisation_name` Trait | % Split | 20 | + +This setup, along with a segment override, instructs the evaluation engine to enable the feature if the user's current organisation belongs to consistent 20% of all organisations. + +:::info Use transient traits for multi-tenancy + +To avoid persisting the `organisation_name` trait on the user identity, mark it as [transient](/flagsmith-concepts/identities#transient-traits). + +::: + +### Restrict evaluation to select environments + +Segments and segment overrides are defined at the project level. This means they apply across all environments, so you may want to restrict your targeting to specific environments — for example, to avoid exposing features in production while still running complex evaluations in staging or development. + +Here's how you could define such a segment: + +| Rule Order | Property | Operator | Value | +| ---------- | ---------------- | ------------------- | ------------ | +| 1 | Environment Name | Does Not Match (!=) | `production` | +| 2 | Identifier | % Split | 20 | + +This setup will enable the feature for 20% of users, but only in non-production environments. + +## Trait data types + +Each individual trait value is always stored as one of the following data types: + +- String. +- Boolean. +- Integer. +- Float. + +Values in segment rules, on the other hand, are always stored as strings. When segment rules are evaluated, rule values will be coerced to be the same type as the trait value. If the rule value cannot be coerced, that rule will evaluate as false. This provides some flexibility if you ever need to change the data type of a trait, e.g. from boolean to string, while maintaining backwards and forwards compatibility in your application. + +For example, consider the following code using the JavaScript SDK: + +```javascript +flagsmith.identify('example_user_1234'); +flagsmith.setTrait('accepted_cookies', true); +``` + +The value of the `accepted_cookies` trait will be stored as a boolean for this identity. If you define a segment rule like `accepted_cookies = true`, the rule value `true` is stored as a string. Because the `accepted_cookies` was stored as a boolean for this identity, the segment engine will coerce the rule's string value into a boolean, and things will work as expected. + +Suppose later on you needed to store a third possible state for the trait `accepted_cookies`, for example if users can partially accept cookies. Your application can start storing this trait as a string without needing to modify your existing segment: + +```javascript +flagsmith.setTrait('accepted_cookies', 'partial'); +``` + +This would continue to work as expected for identities that already have this trait set as a string value. Always storing the trait as a string would also work, for example: + +```javascript +flagsmith.setTrait('accepted_cookies', 'true'); +``` + +The following string trait values will evaluate to `true`: + +- `"True"` +- `"true"` +- `"1"` + + +## What's next + +- Learn more about [segment rule operators](./segment-rule-operators.md) +- Learn about [targeting and rollouts](../targeting-and-rollouts) +- Understand [identities and traits](/flagsmith-concepts/identities) for more advanced user targeting diff --git a/docs/docs/flagsmith-concepts/segments/segment-rule-operators.md b/docs/docs/flagsmith-concepts/segments/segment-rule-operators.md new file mode 100644 index 000000000000..9b5c945a961b --- /dev/null +++ b/docs/docs/flagsmith-concepts/segments/segment-rule-operators.md @@ -0,0 +1,107 @@ +--- +title: Segment Rule Operators +sidebar_label: Segment Rule Operators +--- + +import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; + +Segment rule operators in Flagsmith allow you to define how trait values are compared when evaluating segment membership. Note that all operators are case-sensitive. + +| Name | Description | +| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `Exactly Matches (=)` | Trait value is equal to the rule value | +| `Does not match (!=)` | Trait value is not equal to the rule value | +| `% Split` | Identity is in the percentage bucket. [Learn more](?operators=percent#operator-details) | +| `>` | Trait value is greater than the rule value | +| `>=` | Trait value is greater than or equal to the rule value | +| `<` | Trait value is less than the rule value | +| `<=` | Trait value is less than or equal to the rule value | +| `In` | Trait value is equal to any element in a comma-separated list (case-sensitive). [Learn more](?operators=in#operator-details) | +| `Contains` | Rule value is a substring of the trait value | +| `Does not contain` | Rule value is not a substring of the trait value | +| `Matches regex` | Trait value matches the given regular expression | +| `Is set` | Trait value is set for the given identity and trait key | +| `Is not set` | Trait value is not set for the given identity and trait key | +| `SemVer` | Trait value is compared against the rule value according to [Semantic Versioning](https://semver.org/). [Learn more](?operators=semver#operator-details) | + +### Operator details + + + + +The `In` operator enables you to match a trait value against a comma-separated list of values. For example, the segment rule value might read `21,682,8345`. This would match against a trait value of `682` but not against a trait value of `683` or `834`. + +The `In` operator can be useful for building segments that represent a specific set of tenants in your application. For example, you could create a segment with the following rule: `tenant_id In tenant_1,tenant_2,tenant_3` + + + + +[SemVer](https://semver.org/) operators compare semantic version values. Consider the following segment rule: + +`version` `SemVer >=` `4.2.52` + +This segment would include all users that have a `version` trait set to `4.2.52` or greater. For example, any of the following `version` values would match: + +- `4.2.53` +- `4.10.0` +- `5.0.0` + +Versions are compared as defined by the [Semantic Versioning specification](https://semver.org/#spec-item-11). + + + + +Percentage Split is the only operator that does not require a trait. You can use it to drive [A/B tests](/experimentation-ab-testing) and [staged feature rollouts](/managing-flags/rollout/rollout-by-percentage). + +Percentage Split deterministically assigns a "bucket" to each identity solely based on its ID and not any traits, meaning that Segment overrides that use Percentage Split will always result in the same feature value for a given identity. + +If you create a segment with a single percentage split rule, identities who are members of that split when the split value is set to, say, 10% will be guaranteed to also be in that split if it is changed to a value higher than 10%. + +If the percentage split is reduced in value, some identities will be removed from that percentage split to maintain the balance. The algorithm is fairly simple and good to understand – it is [described here](/managing-flags/rollout/rollout-by-percentage#how-it-works). + + + + +This operator performs a [modulo operation](https://en.wikipedia.org/wiki/Modulo_operation), which returns the remainder of dividing a numeric trait value by a given divisor. The operator accepts rule value in the format `divisor|remainder`. For example: + +`user_id` `%` `2|0` + +This segment will include all identities having a `user_id` trait that is divisible by 2, i.e. even numbers. This is equivalent to the following expression in many programming languages: + +`user_id % 2 == 0` + + + + +### Minimum SDK versions for local evaluation mode + +When running in local evaluation mode, SDK clients evaluate segment rules locally, which means they must be updated to support the latest operators. + +If an SDK client tries to evaluate a segment rule that has an unrecognised operator, that rule will silently evaluate to `false`. The table below lists the minimum required SDK version required by each operator: + +| | Modulo | In | +| ------- | ------ | ----- | +| Python | 2.3.0 | 3.3.0 | +| Java | 5.1.0 | 7.1.0 | +| .NET | 4.2.0 | 5.0.0 | +| Node.js | 2.4.0 | 2.5.0 | +| Ruby | 3.1.0 | 3.2.0 | +| PHP | 3.1.0 | 4.1.0 | +| Go | 2.2.0 | 3.1.0 | +| Rust | 0.2.0 | 1.3.0 | +| Elixir | 1.1.0 | 2.0.0 | + +## Limits + +These are the default limits for segments and rules: + +- 100 segments per project +- 100 segment overrides per environment +- 100 rules per segment override +- 1000 bytes per segment rule value + +See the [documentation on System Limits](/administration-and-security/governance-and-compliance/system-limits) for more details. + +## Custom fields + +Optional or required custom fields can be defined when creating or updating segments. [Learn more](/administration-and-security/governance-and-compliance/custom-fields) diff --git a/docs/docs/flagsmith-concepts/targeting-and-rollouts/_category_.json b/docs/docs/flagsmith-concepts/targeting-and-rollouts/_category_.json new file mode 100644 index 000000000000..5a6032d08a84 --- /dev/null +++ b/docs/docs/flagsmith-concepts/targeting-and-rollouts/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Targeting and Rollouts", + "position": 3, + "collapsed": true +} diff --git a/docs/docs/flagsmith-concepts/targeting-and-rollouts/index.md b/docs/docs/flagsmith-concepts/targeting-and-rollouts/index.md new file mode 100644 index 000000000000..7ab850a1207a --- /dev/null +++ b/docs/docs/flagsmith-concepts/targeting-and-rollouts/index.md @@ -0,0 +1,66 @@ +--- +title: Targeting and Rollouts +description: Conceptual overview of targeting and rollout operations for feature flags in Flagsmith. +sidebar_label: Targeting & Rollouts +--- + +# Targeting and Rollouts: Concepts & Overview + +Flagsmith provides powerful targeting and rollout capabilities to help you deliver features safely, progressively, and with maximum control. This page explains the concepts and available operations for targeting and rollouts. + +## What is Targeting? + +**Targeting** is the practice of controlling feature flag states for specific users or groups, rather than for all users at once. This enables you to: +- Release features to internal users, beta testers, or select customers before a full launch. +- Target features to users with specific traits (such as geography, subscription level, device type, or application version). +- Run experiments (A/B or multivariate tests) by segmenting your user base. +- Roll out features gradually to reduce risk and monitor impact. + +## What is a Rollout? + +A **rollout** is a staged release of a feature to a subset of users, often increasing the percentage of users over time. Rollouts help you to: +- Minimise the impact of bugs or regressions by limiting exposure. +- Monitor performance and user feedback before a full launch. +- Quickly disable ("kill switch") a feature for affected users if issues arise. + +## Targeting and Rollout Operations in Flagsmith + +Flagsmith supports several operations for targeting and rollouts. + +### Environment-level Flags +- Control a feature for all users in a given environment (e.g., development, staging, production). +- Useful for broad control and environment-specific testing. + +### Identity Targeting +- Override feature flags for individual users ("identities"). +- Enables internal testing, QA, customer support, or personalised experiences at the user level. + +### Segment Targeting +- Define **segments**—groups of users matching rules based on traits (e.g., location, plan, app version, usage). +- Override feature flags for all users in a segment. +- Segments can be combined with percentage rollouts and other rules for advanced targeting. + +### Staged Rollouts +- Gradually enable a feature for a percentage of users, either globally or within a segment. +- Useful for canary releases, progressive delivery, and experimentation. +- Flagsmith uses a deterministic hashing algorithm to ensure consistent user experiences during rollouts. + +### Multivariate Flags +- Assign users to different flag "variants" (e.g., for A/B/n testing) based on defined weightings. +- Can be combined with targeting and rollouts for advanced experiments and optimisation. + +## Why Use Targeting and Rollouts? + +- **Reduce risk:** Catch issues early by exposing new features to a small group first. +- **Personalise experiences:** Deliver features to users who will benefit most. +- **Experiment and optimise:** Test multiple variants and measure impact. +- **Comply with requirements:** Target features to specific regions, plans, or device types. +- **Respond quickly:** Instantly disable features for affected users if problems arise. + +## Learn More + +- [Managing Identities](/flagsmith-concepts/identities) +- [Segments & Segment Targeting](/flagsmith-concepts/segments) +- [Staged Feature Rollouts](/managing-flags/rollout/rollout-by-percentage) +- [A/B and Multivariate Testing](/experimentation-ab-testing) +- [Managing Features & Multivariate Flags](/managing-flags/core-management) diff --git a/docs/docs/getting-started/_category_.json b/docs/docs/getting-started/_category_.json new file mode 100644 index 000000000000..344a6e089ced --- /dev/null +++ b/docs/docs/getting-started/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Getting Started", + "position": 1, + "collapsed": true +} diff --git a/docs/docs/getting-started/feature-flags.md b/docs/docs/getting-started/feature-flags.md new file mode 100644 index 000000000000..ce3a3be14eeb --- /dev/null +++ b/docs/docs/getting-started/feature-flags.md @@ -0,0 +1,42 @@ +--- +title: Feature Flags +sidebar_label: Feature Flags +sidebar_position: 1 +--- + +Feature Flags are a development methodology that allow you to ship code and features in your application before they are finished. A feature flag is a control point in your code that determines whether a particular feature or behaviour is active. Flags can be simple on/off (boolean) switches, or multivariate, allowing you to select from multiple options or variants. + +### What do Feature Flags enable? + +- **Decouple deployment from release:** Ship code to production with features hidden behind flags, then enable them for users when ready. + +- **Staged rollouts:** Gradually enable features for a subset of users, reducing risk and allowing for real-world testing. [Learn more](/managing-flags/rollout/rollout-by-percentage). + +- **A/B testing and experimentation:** Test multiple variants of a feature and measure impact. [Learn more](/experimentation-ab-testing). + +- **Remote configuration:** Change app behaviour or configuration in real time, without redeploying. + +### Advantages of using Feature Flags + +- **Safer releases:** Reduce the risk of deploying new features by controlling exposure. +- **Faster iteration:** Test and iterate on features quickly, without waiting for deployment cycles. +- **Targeted rollouts:** Enable features for specific users, groups, or environments. +- **Easy rollback:** Instantly turn off features if bugs or issues are detected. +- **Experimentation:** Run experiments and gather data to inform product decisions. + +### Workflow + +1. You are about to start working on a new feature. Let's imagine you are going to implement a sharing button with your application. + +2. Create a new feature flag in Flagsmith, calling it "sharing_button". Set it to enabled on your development environment, and disabled on your production environment. + +3. Start working on the feature. Whenever you write code that shows the button within the UI, wrap it in a conditional statement, testing against the value of the flag "sharing button". Only show the button if the flag is enabled. + +4. Because your button only shows when the "sharing_button" flag is enabled, you are safe to deploy your code as you work on the feature. Your code will be live within the production platform, but the functionality is hidden behind the flag. + +5. Once you are happy with your feature, you can enable the "sharing_button" for other members of your team and with beta testers. + +6. If everything is working as intended, enable the "sharing_button" flag for everyone in your production environment, and your feature is rolled out. + +If you want to learn more about Feature Flags, +[Flickr wrote the seminal blog post on it in 2009](https://code.flickr.net/2009/12/02/flipping-out/). diff --git a/docs/docs/getting-started/glossary.md b/docs/docs/getting-started/glossary.md new file mode 100644 index 000000000000..bba89e16acd7 --- /dev/null +++ b/docs/docs/getting-started/glossary.md @@ -0,0 +1,43 @@ +--- +title: Glossary +sidebar_label: Glossary +sidebar_position: 3 +--- + +This glossary provides concise definitions for some of the key concepts within Flagsmith: + +- [**A/B Testing**](/experimentation-ab-testing): A method of testing different feature variants with different user groups, often implemented using multivariate flags and percentage splits. + +- [**Core API**](/edge-api/overview): Flagsmith's private API for programmatic control of the platform. + +- [**Edge API**](/performance/edge-api): Flagsmith's publicly accessible API, specifically intended for use with our SDKs. + +- [**Edge Proxy**](/performance/edge-proxy): A self-hosted service that provides a local, low-latency interface to the Flagsmith API. + +- [**Environment**](/flagsmith-concepts/data-model#environments): Environments are a way to separate the configuration of your features. A project can have any number of environments. + +- [**Environment Document**](/integrating-with-flagsmith/integration-overview): A JSON document containing all configuration for feature flags in an environment. + +- [**Feature**](/flagsmith-concepts/data-model#features): A configuration that can be enabled, disabled, or set to a specific value. Features are shared across all Environments in a project, but their values/states can be modified between Environments. + +- [**Feature Flag**](/getting-started/feature-flags): A boolean or multivariate switch to enable/disable features or set their values dynamically without deploying code. + +- [**Identity**](/flagsmith-concepts/data-model#identities): An entity within a particular environment, against which you can manage and override feature settings. + +- [**Local Evaluation Mode**](/integrating-with-flagsmith/integration-overview): A mode where the SDK evaluates feature flags locally using a downloaded environment document, reducing latency and API calls. + +- [**Multivariate Flag**](/managing-flags/core-management): A feature flag that can take on multiple values (not just on/off), useful for A/B testing. + +- [**Organisation**](/flagsmith-concepts/data-model#organisations): Organisations are a way for you and other team members to manage projects and their features. Users can be members of multiple organisations. + +- [**Project**](/flagsmith-concepts/data-model#projects): Projects contain one or more Environments that share a single set of Features across all of the Environments within the Project. Organisations can have any number of Projects. + +- [**Role-Based Access Control (RBAC)**](/administration-and-security/access-control/rbac): A system for managing user permissions and access within an organisation. + +- [**SDK (Software Development Kit)**](/integrating-with-flagsmith/integration-overview): Client libraries provided by Flagsmith for integrating feature flagging into applications. + +- [**Segment**](/flagsmith-concepts/data-model#segments): A group of identities defined by traits (e.g., logins, device, location, or custom traits). You can override feature defaults for segments, such as enabling features for a "power user" group. + +- [**Staged Rollout**](/managing-flags/rollout/rollout-by-percentage): Gradually enabling a feature for increasing percentages of your identities to reduce risk. + +- [**Trait**](/flagsmith-concepts/data-model#traits): A key-value pair associated with an identity that can store any type of data. diff --git a/docs/docs/quickstart.md b/docs/docs/getting-started/quick-start.md similarity index 56% rename from docs/docs/quickstart.md rename to docs/docs/getting-started/quick-start.md index 0b409d17ab64..128452dd3e48 100644 --- a/docs/docs/quickstart.md +++ b/docs/docs/getting-started/quick-start.md @@ -1,30 +1,35 @@ --- -sidebar_position: 2 +title: Quick Start sidebar_label: Quick Start +sidebar_position: 2 --- import CodeBlock from '@theme/CodeBlock'; import { JsVersion } from '@site/src/components/SdkVersions.js'; # Flagsmith Quick Start Guide -Let's get up and running in 5 minutes. We're going to run through the following steps: +This tutorial will guide you through the core concepts of integrating Flagsmith into your application. -1. Create an account on [Flagsmith.com](https://flagsmith.com/) and add your first Flag. -2. Import our Javascript SDK into your web page. -3. Connect to the Flagsmith API and get your flags. -4. Update your application based on the flag value. +## Learning objectives -## 1. Create an account with Flagsmith +By the end of this tutorial, you will be able to: -:::tip +- Create a Project and your first flag in the Flagsmith dashboard. +- Import the Flagsmith JavaScript SDK into your web page. +- Connect to the Flagsmith API and retrieve your flags. +- Update your application's behaviour based on a flag's value. -To get up and running quickly, we're going to use the hosted service at flagsmith.com, but you can run the -[platform locally via Docker](/deployment/hosting/docker.md). +## Prerequisites +You will need access to a Flagsmith instance (either using the SaaS platform or self-hosted) and a basic understanding of HTML and JavaScript. + +:::tip +If you're using the SaaS platform, log in at [app.flagsmith.com](https://app.flagsmith.com/). If you're self-hosting, log in to your deployed instance. ::: -Head over to [Flagsmith](https://app.flagsmith.com/signup) and create an account. We're going to create an Organisation -and a Project. +## 1. Create a Project and Flag in the Dashboard + +We are going to create an organisation (if you haven't already), a project, and your first flag. ![Create Organisation](/img/quickstart/demo_create_1.png) @@ -32,21 +37,17 @@ Flagsmith manages Flags with Projects, so let's create one now: ![Create Project](/img/quickstart/demo_create_2.png) -Flagsmith organises Projects into separate Environments. When you create a Project, Flagsmith automatically creates -`Development` and `Production` Environments. We will come to these Environments later. Let's go ahead and create our -first Flag. This flag will control whether a button shows on our web page. +Flagsmith organises Projects into separate Environments. When you create a Project, Flagsmith automatically creates `Development` and `Production` Environments. We will come to these Environments later. Let us go ahead and create our first Flag. This flag will control whether a button shows on our web page. ![Flagsmith Overview](/img/quickstart/demo_create_3.png) -Flags within Flagsmith are a combination of both: A Boolean value - the `Flag State` and then optionally: A -String/Integer/Boolean value - the `Flag Value`. For now, we're only going to use the `Boolean` value of the flag to -control whether the button shows. Create a flag called `show_demo_button`, and leave it as Disabled by default: +Flags within Flagsmith are a combination of both: A Boolean value - the `Flag State` and then optionally: A String/Integer/Boolean value - the `Flag Value`. For now, we're only going to use the `Boolean` value of the flag to control whether the button shows. Create a flag called `show_demo_button`, and leave it as Disabled by default: ![Flagsmith Overview](/img/quickstart/demo_create_4.png) ## 2. Import the Javascript SDK -OK so we've set up our flag; now let's bring it into our application. We have a (pretty small!) web page: +Ok, so we have set up our flag; now let us bring it into our application. We have a (quite small!) web page: ```html @@ -74,9 +75,7 @@ For the purposes of this quickstart tutorial, we will import the SDK inline into ## 3. Connect to the Flagsmith API -We can now connect to the Flagsmith API and get our Flags. When you initialise the Flagsmith SDK, you have to provide an -Environment ID. This way, the SDK knows which Project and Environment to grab flags for. Head to the Environment -Settings page within Flagsmith, and copy the API key: +We can now connect to the Flagsmith API and get our Flags. When you initialise the Flagsmith SDK, you have to provide an Environment ID. This way, the SDK knows which Project and Environment to retrieve flags for. Head to the Environment Settings page within Flagsmith, and copy the API key: ![SDK Keys](/img/quickstart/demo_create_6.png) @@ -119,7 +118,7 @@ Let's hook this value up to our button, so that the value of the flag controls w This code sets up a callback, which is triggered when we get a response back from the Flagsmith API. We will check for the state of the flag and set the display visibility based on the result. -Our entire webpage now reads like this: +Our entire web page now reads like this: { ` @@ -158,14 +157,31 @@ dashboard and enable the flag: ![Flag View](/img/quickstart/demo_create_10.png) -Return to your browser, refresh the page, and the button will re-appear. +Return to your browser, refresh the page, and the button will reappear. ## Finishing Up This was a pretty quick demo, but it covers the core concepts involved in integrating Flagsmith into your application. From here, some areas of the documentation you might want to check out are: -- A deeper overview of the application - [Features](basic-features/managing-features.md), - [Identities](basic-features/managing-identities.md) and [Segments](basic-features/segments.md). -- More details about our [API and SDKs](clients/rest.md). -- How you can [run Flagsmith yourself](/deployment) or use our [Hosted API](https://flagsmith.com/). +- A deeper overview of the application - [Features](/managing-flags/core-management), + [Identities](/flagsmith-concepts/identities) and [Segments](/flagsmith-concepts/segments). +- More details about our [API and SDKs](/integrating-with-flagsmith/integration-overview). +- How you can [run Flagsmith yourself](/deployment-self-hosting/) or use our [Hosted API](https://flagsmith.com/). + +## Next Steps + +### Target Use Cases +- [Advanced Targeting and Segmentation](/flagsmith-concepts/segments): Learn how to target features to specific users, groups, or segments for advanced rollout strategies. + +### Best Practices for Using Flags in Code +- [When to use feature flags](/best-practices/when-to-use-flags): Understand the core concepts and workflows for using feature flags effectively. +- [Using flags in frontend and backend code](/integrating-with-flagsmith/integration-overview): Practical guidance and examples for both client and server-side usage. +- [How to test your application using flags](/experimentation-ab-testing): Strategies for testing and rolling out features safely. + +### Supported SDKs +- [SDKs & Integrations](/integrating-with-flagsmith/integration-overview): Explore all supported SDKs for integrating Flagsmith with your technology stack. + +### Automation & API Access +- [REST API Reference](/edge-api/): Learn how to manage flags programmatically and automate flag changes. + diff --git a/docs/docs/guides-and-examples/_category_.json b/docs/docs/guides-and-examples/_category_.json deleted file mode 100644 index 03d03ace8de3..000000000000 --- a/docs/docs/guides-and-examples/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Guides & Examples", - "position": 50, - "collapsed": true -} diff --git a/docs/docs/guides-and-examples/defensive-coding.md b/docs/docs/guides-and-examples/defensive-coding.md deleted file mode 100644 index 5a12043a38a1..000000000000 --- a/docs/docs/guides-and-examples/defensive-coding.md +++ /dev/null @@ -1,100 +0,0 @@ ---- -title: Defensive Coding and Designing for Failure -sidebar_label: Defensive Coding ---- - -Introducing Feature Flags and Remote Config to your applications can provide a wealth of benefits, but there are also a -few drawbacks. Fortunately, the majority of these can be avoided through [defensive coding](#defensive-coding) and -sensible approaches to default flags. - -In addition to the approaches you can take integrating our SDKs, there are a number of -[Design for Failure](#designing-for-failure) concepts that are built into the platform architecture to ensure that -Flagsmith provides a reliable, dependable solution. - -## Defensive Coding - -### Don't expect a 200 response from the Flagsmith API - -First up - we care deeply about our [SaaS uptime and stability](http://status.flagsmith.com/). Whilst no one has 100% -uptime, in our experience there are numerous situations where your application is unable to get a response from our API: - -- They are using a mobile device, open your app, and step into a lift. -- They are using a web application in a hotel that has the craziest DNS setup you have ever seen. -- As above but for TLS certificates. -- They are running a DNS blocker that has over-zealous blacklists. - -The list goes on and on, but the bottom line is that whilst our API being down is extremely unlikely, you cannot rely on -200's from our API in 100% of cases. - -So with that in mind, here are some rules you can follow to avoid any issues stemming from the above. - -### Don't block your application waiting on our response - -The solution here really depends on which of our SDKs you are using. By default our Client SDKs will not block your main -application thread, and are designed to work around an asynchronous callback model. - -Where our Server Side SDKs are being used, it really depends on if you are using them in -[local or remote evaluation mode](/clients#networking-model). When running in local evaluation mode, once the SDKs have -received a response from the API with the Environment related data, they will keep that data in memory. In the event of -the SDKs then not receiving an update, they will continue to function. - -In the event that the SDKs aren't able to contact the API at all, they will time out and resort to -[Default flags](#progressively-enhance-your-application-with-default-flags). When running in remote evaluation mode, you -will need to decide what the best approach is based on your particular application. Again, -[Default flags](#progressively-enhance-your-application-with-default-flags) can help here. - -### Progressively enhance your application with default flags - -The most effective way of dealing with these issues is to provide a sane set of default values for _all of your flags_. -The Flagsmith SDKs all have provision for specifying default values for both flag boolean and flag text values. We -strongly recommend setting defaults for all of your flags as a matter of routine. - -Your application should operate in a default, safe mode and its behaviour should only be modified or enhanced with flags -on receiving an API response. - -### Cache flags where possible - -Our Javascript SDK has the capability of caching the last received flags in the localStorage of the user's browser. When -the browser starts a new session, the last cached flags will be used while waiting for a response from the API for a -fresh set of flags. This pattern helps if the browser never receives a response from the API. - -## Designing for Failure - -### The Edge API - -When our SDKs request their flags, they will make requests to our [Edge API](/advanced-use/edge-api.md). This -infrastructure is serverless both at the compute and data-store, as well as being replicated across eight AWS regions. -We also provide latency based routing and regional fault tolerance. - -What this means is that your application will be served by the nearest region to the request. In addition to this, in -the event of the failure of an entire AWS region, requests will automatically be routed to the next nearest region. - -### Caching, Performant SDKs - -Our client-side SDKs will always remember their last known flags and use these in the event that they cannot reach our -API; for example if they are on a mobile device set to flight mode. - -In addition to this, by default our client-sde SDKs will only make a network call when they are initialised. Subsequent -requests for flag evaluation will happen locally in memory in fractions of a millisecond. - -### Server side SDKs and local evaluation mode - -If you need sub-millisecond latency for end-to-end flag evaluation, for example in the event that you are running a -multi-variate test on a landing page of your website, you can employ one of our Server Side SDKs running in -[local evaluation mode](/clients#local-evaluation) mode. This will provide sub-millisecond latency of the entire flag -evaluation rules engine, running locally within your server infrastructure, allowing you to run multivariate tests with -zero latency impact. - -### No proxy-server required - -We do not require you to run any infrastructure whatsoever to run our platform. There are no relays, proxies, caches or -CDNs in between your client request and our API. - -We do have an [Edge Proxy](/advanced-use/edge-proxy) if required, but it is entirely optional. - -### Bring your own CDN or DNS if you wish - -Because we don't do any caching, you are free to add your own reverse proxy into the architecture if you wish. Customers -often do this to provide an additional layer of security (for example, only allowing authenticated clients to get their -flags). They also do this to ensure that requests from their application don't make requests to a third party domain, -for example by setting up their own DNS namespace for our API. diff --git a/docs/docs/guides-and-examples/efficient-api-usage.md b/docs/docs/guides-and-examples/efficient-api-usage.md deleted file mode 100644 index 1de1fb008312..000000000000 --- a/docs/docs/guides-and-examples/efficient-api-usage.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -title: Efficient API Usage -sidebar_label: Efficient API Usage ---- - -It is good engineering practise to reduce the frequency of API calls made from your applications to Flagsmith. There are -a number of reasons for this: - -- Reducing network activity means your applications use less client resources like mobile device battery, CPU and - network. -- You don't need to reflect state updates in your application as often. -- It saves you money! Whether you are self hosting or using our SaaS API, reducing API call volume costs you less. - -## Client Side - -### Getting Flags Once Per Session - -The most common, most efficient workflow we have found with people using Flagsmith on the client side (browsers, mobile -apps etc) is the following: - -1. The user opens the app for the first time, as an anonymous user. -2. The application loads, using Default Flag values as defined in code. -3. The application makes a request for the Flags for the Environment (_not_ the Identity as the user is still unknown at - this stage). -4. The user logs into the application. A request is then made for the Identity Flags (along with any Traits for that - Identity). -5. This data is then cached locally on the device and used for the duration of the user's session. -6. When the user reopens the app (for example, the following day) the cached values in the previous step are used. The - application then re-requests the Identity flags (in case any flags have changed in the meantime) and caches the data. - -### Setting Traits Efficiently - -Every time a Trait as set via the SDK, they will make a request to the Flagsmith API with the Trait data and receive an -updated set of flags. - -In order to reduce these calls, we recommend setting the full complement of traits in a single SDK call. There's more -info around achieving this in our [Javascript FAQ](/clients/javascript#faqs). - -### Real Time Flag Updates - -In our experience, most applications do not benefit a great deal from real time flag updates. In addition, and -especially with client-side flags, thought needs to be given to ensuring features/UI widgets don't appear/disappear in -real time due to flag changes. - -That being said, there are use-cases for real time flags. Using our -[real-time flags service](/advanced-use/real-time-flags) negates the need to poll the API from the client SDK, which can -significantly reduce API usage. - -## Server Side - -### Local Evaluation Mode - -The most efficient way of evaluating Flags on the Server is using [Local Evaluation mode](/clients#local-evaluation). -There are [some caveats](/clients#local-evaluation), so please be aware of them! - -### CDN Usage - -There are 3 main API calls the Flagsmith SDK can make: - -1. Get the [Environment Document](/clients#the-environment-document) for - [Local Evaluation mode](/clients#local-evaluation). -2. Get the Flags for an Environment. -3. Get the Flags for an Identity. - -Of these 3, the first two are candidates for caching. If your project can tolerate a longer period of time between -someone modifying a flag in Flagsmith and that flag change being reflected within the SDK, you can place a cache between -Flagsmith and your server side SDKs. The easiest way to do this is with a CDN, specifying the TTL to whatever you can -tolerate, and overriding the Flagsmith API URL within the SDK to point to your CDN. - -Note that you almost certainly _don't_ want to cache the Get the Flags for an Identity. - -### Edge Proxy - -Using the [Edge Proxy](/advanced-use/edge-proxy) can significantly reduce API usage. A single instance of the Edge Proxy -makes one API request by default every 10 seconds to the Flagsmith API. With that request data it can then serve -potentially thousands of requests per second. - -Consideration needs to be given to the caveats of running the Edge Proxy, but its deployment can have a dramatic effect -on reducing API call volume. diff --git a/docs/docs/guides-and-examples/flag-lifecycle.md b/docs/docs/guides-and-examples/flag-lifecycle.md deleted file mode 100644 index 4fa2675c7b18..000000000000 --- a/docs/docs/guides-and-examples/flag-lifecycle.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: Feature Flags Lifecycles ---- - -Feature Flags generally have two lifecycles: - -1. Short-Lived Flags -2. Long-Lived Flags - -Lets go over each type in detail. - -## Short-Lived Flags - -Short-lived flags are designed to be removed from your code and from Flagsmith at some point in the future. Their -typical lifecycle is normally something like: - -1. Create flag in Flagsmith. -2. Add the flag to your application code. -3. Toggle the flag and/or apply Segment overrides to control your application behaviour. -4. Once you are finished with the flag, remove the flag from your codebase. -5. Deploy your application, so that there is no reference to the flag. -6. Remove the flag from Flagsmith. - -Short lived flags are typically used for the following use-cases: - -### Feature Roll-Outs - -The most common use of flags in general is to decouple the deployment of a feature from it's release. When using a flag -to achieve this, you will generally remove the flag from your code and from Flagsmith once you are happy with the -feature and it is rolled out to your entire user population. - -Once this is the case, there is no reason to have the flag exist either in Flagsmith or your code, hence it is -considered good practise to remove it from both. - -### Experimentation - -You can use Multi-variate flags to drive [A/B and multivariate tests](../advanced-use/ab-testing.md). Once your -experiment is complete, there is typically no need for the flag to remain, and hence it can be removed. - -## Long-Lived Flags - -Conversely, sometimes you will create flags that are long-lived - quite possibly for the lifetime of the application. -Here are a few use cases where this approach can be used. - -### Kill Switches - -There are often times where you need to be able remotely remove a feature, area of your application or sometimes the -application as a whole (in the event of a large deployment, for example). In these instances, flags can be used as 'kill -switches' to remove a feature altogether, or maybe to prevent users from using the application in the event of downtime. - -Kill switches are generally long-lived; they often exist in the event of an unexpected event or error, and so they need -to be long-lived in case they need to be put to use. - -### Feature Management Flags - -You can make use of [Segments](../basic-features/segments.md) and Flags to control how different features are enabled or -disabled depending on the user. For example, you can send a Trait `plan` with the relevant user value (e.g. `scale-up`) -to Flagsmith, then create a Segment that defines all users on the `scale-up` plan. You can then show or hide features -based on this Segment and plan. - -When employing feature flags in this manner, generally you would never remove this Flag or Segment, as you are using -them to drive platform features for the lifetime of the application. diff --git a/docs/docs/guides-and-examples/integration-approaches.md b/docs/docs/guides-and-examples/integration-approaches.md deleted file mode 100644 index d21f03d1a3c2..000000000000 --- a/docs/docs/guides-and-examples/integration-approaches.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -title: Integration Approaches ---- - -## Client Side SDK Flag endpoints are public - -The API endpoints that our SDKs make calls to are all public. Your Environment API key should also be considered public. -Think of it in the same way you would a Google Analytics key. The key is sent to browsers in plain HTML or Javascript -and as a result should not be considered 'secret'. - -Given this fact, it is important to ensure that attackers cannot enumerate or guess Identity keys. If an attacker is -able to do this, they can easily overwrite trait values for Identities that are not related to their user. The simplest -way to achieve this is to use a computer generated GUID or hash as the Identity key. You might already be doing this -(for example if your datastore associates a GUID with a User record). - -If, for example, your database uses an auto-incrementing integer as the user record key, we strongly recommend you -either store a GUID alongside that record, or compute a 2-way hash of the user and use that as the Identity key. - -Note that this only relates to _Client Side Keys_. _Server Side Keys_, on the other hand, should be considered secret -and stored appropriately. - -You can also prevent client-side SDKS from -[setting Traits](/system-administration/security#preventing-client-sdks-from-setting-traits). - -### Segment and Targeting rules are not leaked to the client - -If flags are evaluated within the client-side SDKs (Web Browser, Mobile App), the entire set of rules for targeting -users based on Segments etc need to be sent to the client. Given these endpoints are public by default, we think this is -a leak of potentially sensitive information. We think the best place for your flags to be evaluated is on our server or -your server. Not on the client. - -### You can get your flags with a single HTTP GET - -You don't need to run a set of complicated rule evaluations to get your flags. Just hit our endpoint and you get your -flags. You won't receive any information on Segments or rollout rules, and this is by design. If you want to run your -own HTTP client within your application it's just an HTTP GET and you're good. - -### Build Time Flag Retrieval - -:::tip - -We have a [Flagsmith CLI](https://github.com/Flagsmith/flagsmith-cli) which can be helpful here! - -::: - -A more advanced technique is to grab the Flag defaults from the Flagsmith API at build time and include them on your -application build. The steps for this might look something like this: - -1. Push your code to your git repository. -2. An automated build pipeline is triggered. -3. One stage of the pipeline is to grab the current default flag states from the `/flags` endpoint and store the JSON - response within your application build. -4. Upon startup of your application, read the JSON file is embedded within your application first to get sane default - flags and config. -5. Asynchronously call the Flagsmith API to get the most recent Flag and Config values. - -## Caching Flags Locally - -This approach depends on whether your application has an ability to persist data to the host OS during runtime. Locally -caching flags within your application environment ensures that you can subsequently start your application without -having to block for a call to the Flagsmith API. A common workflow would then be: - -1. Build your application with sane defaults. -2. Start your app, using the sane defaults, and asynchronously call the Flagsmith API to retrieve up-to-date Flags. -3. Once the up-to-date Flags are retrieved, store them locally. -4. On subsequent app launches, check local storage to see if any flags are available. If they are, load them - immediately. -5. Asynchronously call the Flagsmith API to retrieve the up-to-date Flags. - -The official [Javascript Client](/clients/javascript/) offers optional caching built in to the SDK. - -## Caching Flags on a Server (Flagsmith Client) - -:::tip - -Note that you can also [evaluate flags locally](/clients) in our Server Side SDKs. - -::: - -When running the Flagsmith SDK within a Server environment, it is difficult for the SDK to ascertain what sort of -caching infrastructure is available to it. For this reason, caching flags in a Server Environment needs to be integrated -by hand. However, it's pretty simple! - -1. When your server (flagsmith client) starts up, get the Flags from the Flagsmith API. Flagsmith server will now store - the Flags in memory within the server runtime. -2. If you have caching infrastructure available (for example, memcache, redis etc), you can then store the flags for - that environment within your caching infrastructure. -3. You can set up a [Web Hook](/system-administration/webhooks) within Flagsmith that sends flag change events to your - server infrastructure. -4. Write an API endpoint within your infrastructure that receives flag change events and stores them in your local - cache. -5. You can now rely on your local cache to get up to date flags. diff --git a/docs/docs/guides-and-examples/micro-service-architectures.md b/docs/docs/guides-and-examples/micro-service-architectures.md deleted file mode 100644 index b376b8cfaa26..000000000000 --- a/docs/docs/guides-and-examples/micro-service-architectures.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: Feature Flags and the Micro-Service Architecture ---- - -## The Problem - -Sometimes it can be tricky to figure out how to map Flagsmith Projects to your own Applications. When you are working on -an application that has 1 core server-side service, it's generally fairly obvious to map 1 Flagsmith Project to your -server-side application. - -Things get a bit more tricky when you start working with a micro-service architecture. Let's say you have 5 -micro-services that are all employed to power your front end application. Should you create a single Flagsmith Project -to cover all 5 services? Or 5 Projects, one for each service? Or maybe some number in between? - -## The Solution - -Like a lot of problems in Software Engineering, the right answer is "it depends". Maybe not the most helpful answer, but -easily the most accurate! What it boils down to is coupling. - -Generally if we are looking at a collection of micro-services that are tightly coupled, they may well be a good -candidate for sharing a single Flagsmith project. On the other had, if your services are loosely coupled with will -defined interface boundaries, you will probably want to go with mapping 1 Flagsmith project to 1 micro-service. - -### Why though? - -It really boils down to interface boundaries. If you are writing a service that has an agreed upon, established API -interface, the services consuming your API really don't care too much about your implementation. That being the case, by -definition they shouldn't really care about your feature flags either. Why would they? - -On the other hand, if you are working on a service that is tightly coupled with another, you probably do care about -their flags. Let's take an example. Imagine three micro-services that share a common database. You want to make a schema -change to the database, but that means coordinating the new schema code amongst your 3 services. Coordinating -deployments is fraught with danger, but using feature flags is a great solution to this problem. In this case, having -the 3 micro-services sharing the 1 flagsmith project is really helpful - -### Other things to consider - -If you have a single Flagsmith project powering several micro-services, you need to be aware of the following aspects. - -All the team members of the project will have access to your flags. If an engineer on another team doesn't really have -any business managing your production flags, it might be a sign to split out into 2 Flagsmith projects. - -It can be helpful for segment definitions to be shared amongst micro-services. That would point to having a single -Flagsmith project. If the context that your users access your service is the same/similar, sharing segments makes a lot -of sense. diff --git a/docs/docs/guides-and-examples/mobile-app-versioning.md b/docs/docs/guides-and-examples/mobile-app-versioning.md deleted file mode 100644 index c11f9903b5a1..000000000000 --- a/docs/docs/guides-and-examples/mobile-app-versioning.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -title: Mobile App Versioning ---- - -## The Problem - -Feature Flags really come into their own when managing the features of a mobile application. If you ship a bug in your -mobile app, there is a significant time delay in getting a fix onto your user's device: - -1. You have to wait for App/Play Store approval (although this time period has got much better in recent years) -2. Once your new app version is live, you then have to wait for your users to upgrade their application, which could - take weeks or even months. - -Combined, these two problems can cause real headaches if you ship a bug. Feature flags to the rescue... - -## The Solution - -We're going to create a Segment of affected users, and override (in this case, disable) the affected feature from _just -the affected users_ of our application altogether. Here's how we go about it. - -### 1. Put Features behind Flags - -It sounds obvious, but if you don't wrap features in feature flags, you lose the ability to control them remotely. Make -it a part of your routine to wrap new features/code in flags so you can start managing them remotely. - -### 2. Start telling Flagsmith about the Device and Application Version - -Using [Identities and Traits](/basic-features/managing-identities.md), make sure you are transmitting data about your -device type and version to Flagsmith. We recommend using the following Traits: - -- Platform (iOS or Android) -- Platform Version (e.g. Android 11, iOS 14) -- Your Application Version (this would be the version number you ship your app as - generally the one that shows up in - the App/Play Store) - -### 3. Track down your bug - -When you inevitably do ship a bug (don't worry; we've all been there!), and the bug reports start rolling in, try and -rapidly isolate exactly what subset of devices are affected: - -- Is it just iOS devices running the just-shipped version? -- Or have all Android devices broken for some reason? -- Did you actually ship the bug 2 versions back but have only just realised now? - -This is generally the hardest part of the process. Work to isolate and define the smallest subset of your user-base that -is affected. - -### 4. Segment your Users based on the Bug - -:::tip - -We can make use of [Semver Aware Operators](/basic-features/segments?operators=semver) to drive these Segment rules. - -::: - -From your work in #3, create a [Segment](/basic-features/segments.md) in Flagsmith that captures the defined set of -users from #3. Let's say we just shipped version `5.4.1`, but we have figured out that the bug actually showed up in -version `5.4.0`. Also, this issue is only affecting iOS devices; Android users don't have the problem. So our Segment -would contain 2 rules and read something like: - -- Trait `platform` _equals_ `iOS` -- Trait `version` _semver_ {'>='} `5.4.0` **AND** _semver_ {'<='} `5.4.1` - -### 5. Override your Feature with your Segment - -Locate the feature that is causing the problem. Get to the Overrides tab, add the Segment you defined in #4, and set -that Feature Override to **_disabled_**. - -And breathe... - -## What just happened? - -We've done the hard work and isolated which precise subset of users are affected by this issue. We want the feature to -continue to show and work for all our other users (in this case Android users and iOS users on versions older than -`5.4.0`), but we want to disable it for the affected users. - -So we created a Segment that precisely identified the affected users, and then used that Segment to override the -feature, **_but just for those users_**. - -## What happens next? - -Firstly, (sounds obvious but who knows!) ship a fix! Push version 5.4.2 that fixes the issue. As users upgrade, their -`version` trait will automatically change to 5.4.2 and they will drop out of the Segment. Flagsmith will then start -sending an Enabled flag for this feature, and your users will have access to the feature that you just fixed. - -We can keep this Segment and the override in place. In fact, it's really important that we do! Lots of people don't -bother upgrading their apps at all. That's fine though; with the Segment in place, they will never know that you shipped -a clanger. diff --git a/docs/docs/guides-and-examples/staged-feature-rollouts.md b/docs/docs/guides-and-examples/staged-feature-rollouts.md deleted file mode 100644 index e88a003c8476..000000000000 --- a/docs/docs/guides-and-examples/staged-feature-rollouts.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: Staged Feature Rollouts ---- - -## What are Staged Feature Rollouts - -Staged Feature Rollouts allow you to test a new feature with a small subset of your user base. If you are happy with the -feature, you can increase the percentage of users that see the feature until it is available to your entire user base. - -This method can increase your confidence in rolling out a new feature. If there are issues with the rollout, you can -disable the Feature Flag, thus hiding the feature within your application. - -## Creating Staged Rollouts - -:::important - -Staged Rollouts **_only_** come into effect if you are getting the Flags for a particular Identity. If you are just -retrieving the flags for an Environment without passing in an Identity, your user will never be included in the "% -Split% Segment. - -::: - -You can achieve staged rollouts by creating a [Segment](/basic-features/segments.md) and adding a rule defined with the -"% Split" condition. Specifying a "% Split" value between 1 and 100 then defines what percentage of your user base are -included within this Segment. - -Once you have created the Segment, you can then go ahead and connect it up to a Feature Flag as per regular -[Segments](/basic-features/segments.md). - -Note that you can include the "% Split" rule alongside other Segment rules if you wish. - -## How does it work - -Flagsmith merges the Segment unique identifier and a context value chosen for the `% Split` rule — usually an identifier -or a trait — then hashes the result. A floating point value between 0.0 and 1.0 is generated from this hash. This value -is then evaluated against the threshold set for the `% Split` rule. - -### An Example - -So to take an example. For a single Identity, we perform the following steps: - -1. Take the internal Segment ID and their internal Identity ID and combine them into a single string -2. We then hash that string -3. We then generate a float value between 0 and 1 based on that hash - -So for every Segment/Identity combination, a value of between 0 and 1 is generated. Due to the hashing algorithm used, -we ensure a consistent spread of values from 0 to 1. - -So lets say that number comes out at 0.351 for a particular Identity. If you create a Segment % split to be 30%, that -Identity will not be included in that Segment because 0.351 is great than 0.3 (30%). If you then modify the Segment to -be a 40% split, the Identity WILL be in that Segment because 0.4 > 0.351. That way you get a consistent experience as an -end-user. This works because the ID of a Segment doesn't change after it has been created. - -A second Identity might have their value hash be equal to 0.94. In that case, they would not be in the Segment with the -split at either 30% of 40%. diff --git a/docs/docs/guides-and-examples/testing-push-notifications.md b/docs/docs/guides-and-examples/testing-push-notifications.md deleted file mode 100644 index f6a19fa5a614..000000000000 --- a/docs/docs/guides-and-examples/testing-push-notifications.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -title: Testing Push Notifications ---- - -## The Problem - -Sending out Push Notifications (and Email messages!) accurately and without error is notoriously difficult. Combine the -fact that they can be extremely sensitive/error prone across dev/staging/production environments, along with the -inability to recall them if you've sent them out in error, and you have a subject area that can strike fear into even -the most hardened engineer. Deploying code that sends out messages to potentially millions of users is not a relaxing -experience. - -When you send a push notification via [Firebase FCM](https://firebase.google.com/docs/cloud-messaging), you can test -things out by sending messages to a specific device via a device token. The problem is that it’s quite technical to get -set up, and these tokens are not readily surfaced within an app. In addition, if you want to test out a campaign it’s -likely you will want to try sending push notifications to more than 1 device so you can test between QA, product owners, -developers and marketing when validating behavior and fixing bugs. - -## The Solution - -We want to be able to test out the end-to-end process of sending marketing push notifications, before rolling them out -in production. This often means working across marketing, product and engineering teams in what can become a complex, -error prone process. Testing this process before pushing things live is one of the best ways of making sure that your -procedures are working. - -You can utilise Flagsmith in combination with Firebase FCM topics to make this easier. When sending a push notification -to a topic, Firebase will send that notification to all the users that are subscribed to that named topic. We will -eventually use these topics to target marketing messages to our user-base, but we can also use them to test out the -messaging with our team before sending them out to all our users. - -We're going to create a flag in Flagsmith called `fcm_marketing_beta`. We want anyone with this flag enabled in -Flagsmith to be subscribed to the `marketing` FCM topic. We will then send messages out via this topic. - -We use the following code in our React Native application to do this: - -```javascript -const isInMarketingBeta = flagsmith.hasFeature('fcm_marketing_beta'); - -if (isInMarketingBeta) { - messaging().subscribeToTopic('marketing'); -} -``` - -### Adding Individual Users - -Once our code is deployed, we can start enabling this flag for individual users of our application. Using the -[Identities](/basic-features/managing-identities.md) aspect of Flagsmith, we can enable this flag for our user in the -application, as well as enabling it for some of our close team mates. - -![Overriding the Flag for our User](/img/guides/fcm-user-override.png) - -Refreshing the application will cause the `messaging().subscribeToTopic('marketing');` code to execute, adding us to the -FCM topic. - -![FCM Subscriptions](/img/guides/fcm-subscribed.png) - -Our device has now appeared in this topic, and we can use the topic to start sending test marketing messages. - -### Adding Teams of Users with Segments - -We can now make use of [Flagsmith Segments](/basic-features/segments.md) to add all our team to this beta FCM group. - -Create a Segment, add a rule that will include our team (in our case we match against the domain name of an email -Trait), and override the `fcm_marketing_beta` Flag with this Segment. - -We're now able to test these messages with our entire team, no further code or deployments required! diff --git a/docs/docs/index.md b/docs/docs/index.md deleted file mode 100644 index 682ebc14fd2c..000000000000 --- a/docs/docs/index.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -id: intro -slug: / -title: Welcome to the Flagsmith Docs -sidebar_position: 1 -sidebar_label: Overview ---- - -[Flagsmith](https://flagsmith.com/) is a feature flag tool that lets you manage features across web, mobile and server -side applications. - -[Flagsmith is Open Source](https://github.com/Flagsmith). Host yourself or let us take care of the hosting. - -## Getting Started - -If you're new to Flagsmith or Feature Flags in general we would recommend: - -- [Signing up for a free account on our SaaS platform](https://app.flagsmith.com/signup) -- [Go through our 5 minute Quickstart Guide](quickstart.md) -- [Set up an SDK for your Project](clients) -- [Dive into the Docs](basic-features) - -## Digging Deeper - -- [View our SaaS Features and how SaaS compares to Self Hosted](version-comparison.md) -- [Learn about our globally distributed Edge API](advanced-use/edge-api.md) -- [Use Flagsmith to run A/B and Multivariate Tests](advanced-use/ab-testing.md) -- [Integrate with 3rd party applications](integrations) -- [Use Change Requests and Scheduled Flags to manage your workflows](advanced-use/change-requests.md) - -## Check out our SDKs - -### Client Side SDKs - -- [Javascript](/clients/javascript) -- [Android/Kotlin](/clients/android) -- [Flutter](/clients/flutter) -- [iOS/Swift](/clients/ios) -- [React & React Native](/clients/react) -- [Next.js, Svelte and SSR](/clients/next-ssr) - -### Server Side SDKs - -Check out our [Server Side SDK architecture first!](/clients) - -- [Node.js](/clients/server-side?language=nodejs) -- [Java](/clients/server-side?language=java) -- [.Net](/clients/server-side?language=dotnet) -- [Python](/clients/server-side?language=python) -- [PHP](/clients/server-side?language=php) -- [Ruby](/clients/server-side?language=ruby) -- [Rust](/clients/server-side?language=rust) -- [Go](/clients/server-side?language=go) -- [Elixir](/clients/server-side?language=elixir) - -## Open Source vs SaaS vs Enterprise - -Learn more about the [different ways you can run Flagsmith](version-comparison.md). diff --git a/docs/docs/clients/CLI.md b/docs/docs/integrating-with-flagsmith/CLI.md similarity index 90% rename from docs/docs/clients/CLI.md rename to docs/docs/integrating-with-flagsmith/CLI.md index 7aa1395ac7f3..058885876f64 100644 --- a/docs/docs/clients/CLI.md +++ b/docs/docs/integrating-with-flagsmith/CLI.md @@ -1,7 +1,7 @@ --- description: Flagsmith Command Line Interface (CLI) sidebar_label: CLI -sidebar_position: 6 +sidebar_position: 40 --- # Flagsmith CLI @@ -33,7 +33,7 @@ FLAGS -o, --output= [default: ./flagsmith.json] The file path output DESCRIPTION - Retrieve flagsmith features from the Flagsmith API and output them to a file. + Retrieve flagsmith feature flags from the Flagsmith API and output them to a file. EXAMPLES $ FLAGSMITH_ENVIRONMENT=x flagsmith get diff --git a/docs/docs/integrating-with-flagsmith/_category_.json b/docs/docs/integrating-with-flagsmith/_category_.json new file mode 100644 index 000000000000..e6784028120b --- /dev/null +++ b/docs/docs/integrating-with-flagsmith/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Integrating with Flagsmith", + "position": 8, + "collapsed": true +} diff --git a/docs/docs/integrations/_category_.json b/docs/docs/integrating-with-flagsmith/flagsmith-api-overview/_category_.json similarity index 54% rename from docs/docs/integrations/_category_.json rename to docs/docs/integrating-with-flagsmith/flagsmith-api-overview/_category_.json index e054f408b467..6269dca75138 100644 --- a/docs/docs/integrations/_category_.json +++ b/docs/docs/integrating-with-flagsmith/flagsmith-api-overview/_category_.json @@ -1,5 +1,5 @@ { - "label": "Integrations", + "label": "Flagsmith API Overview", "position": 60, "collapsed": true } diff --git a/docs/docs/integrating-with-flagsmith/flagsmith-api-overview/admin-api/authentication.md b/docs/docs/integrating-with-flagsmith/flagsmith-api-overview/admin-api/authentication.md new file mode 100644 index 000000000000..b3c0fa25b865 --- /dev/null +++ b/docs/docs/integrating-with-flagsmith/flagsmith-api-overview/admin-api/authentication.md @@ -0,0 +1,28 @@ +--- +title: Authentication +sidebar_label: Authentication +--- + +To interact with the Admin API, you need to authenticate your requests using an API Token associated with your Organisation. + +## Generating an API Token + +You can generate an API Token from the **Organisation Settings** page in the Flagsmith dashboard. + +1. Click on your Organisation name in the top navigation panel. +2. Go to the **API Keys** tab. +3. Click **Create API Key**. + +Give your key a descriptive name so you can remember what it's used for. + +## Using the API Token + +Once you have your token, you need to include it in your API requests as an `Authorization` header. The token should be prefixed with `Api-Key`. + +```bash +Authorization: Api-Key +``` + +This token grants access to manage all projects within that organisation, so be sure to keep it secure and never expose it in client-side applications. + +For SaaS customers, the base URL for the Admin API is `https://api.flagsmith.com/`. If you are self-hosting, you will need to use your own API URL. \ No newline at end of file diff --git a/docs/docs/integrating-with-flagsmith/flagsmith-api-overview/admin-api/code-examples.md b/docs/docs/integrating-with-flagsmith/flagsmith-api-overview/admin-api/code-examples.md new file mode 100644 index 000000000000..555b05b56988 --- /dev/null +++ b/docs/docs/integrating-with-flagsmith/flagsmith-api-overview/admin-api/code-examples.md @@ -0,0 +1,21 @@ +--- +title: Code Examples +sidebar_label: Code Examples +--- + +Here is a simple example of how to use the Admin API with `curl` to create a new environment within a project. + +```bash +curl 'https://api.flagsmith.com/api/v1/environments/' \ + -H 'Content-Type: application/json' \ + -H 'Authorization: Api-Key ' \ + --data-binary '{"name":"New Environment","project":""}' +``` + +### Parameters + +- `Authorization`: Your Organisation API Token, prefixed with `Api-Key`. +- `Content-Type`: `application/json` +- `--data-binary`: The JSON payload containing the details of the environment you want to create. You'll need to provide the `name` for the new environment and the `project` ID it belongs to. + +For more complex examples and different languages, please refer to the full code examples in the [Flags API documentation](../flags-api/code-examples). \ No newline at end of file diff --git a/docs/docs/integrating-with-flagsmith/flagsmith-api-overview/admin-api/index.md b/docs/docs/integrating-with-flagsmith/flagsmith-api-overview/admin-api/index.md new file mode 100644 index 000000000000..4f53ddbd3602 --- /dev/null +++ b/docs/docs/integrating-with-flagsmith/flagsmith-api-overview/admin-api/index.md @@ -0,0 +1,18 @@ +--- +title: Admin API +sidebar_label: Admin API +--- + +The Admin API allows you to programmatically manage your Flagsmith projects, environments, features, segments, and users. Essentially, any action you can perform in the Flagsmith dashboard can also be accomplished via the Admin API. + +This API is designed for automation, integrations, and building custom workflows on top of Flagsmith. + +## API Explorer + +You can explore the full Admin API via Swagger at [https://api.flagsmith.com/api/v1/docs/](https://api.flagsmith.com/api/v1/docs/). You can also get the OpenAPI specification in [JSON](https://api.flagsmith.com/api/v1/docs/?format=.json) or [YAML](https://api.flagsmith.com/api/v1/docs/?format=.yaml) format. + +We also have a [Postman Collection](https://www.postman.com/flagsmith/workspace/flagsmith/overview) that you can use to experiment with the API. + +:::info +Our Admin API has a [Rate Limit](/administration-and-security/governance-and-compliance/system-limits#admin-api-rate-limit) that you should be aware of. +::: \ No newline at end of file diff --git a/docs/docs/integrating-with-flagsmith/flagsmith-api-overview/flags-api/authentication.md b/docs/docs/integrating-with-flagsmith/flagsmith-api-overview/flags-api/authentication.md new file mode 100644 index 000000000000..ae35aa02eb85 --- /dev/null +++ b/docs/docs/integrating-with-flagsmith/flagsmith-api-overview/flags-api/authentication.md @@ -0,0 +1,24 @@ +--- +title: Authentication +sidebar_label: Authentication +--- + +The Flags API uses a non-secret **Environment Key** for authentication. This key is safe to be exposed in public, client-side applications. + +## Finding Your Environment Key + +You can find the Environment Key for each of your environments in the Flagsmith dashboard. + +1. Navigate to the project you want to work with. +2. Go to the **Environments** tab. +3. You will see a list of your environments, each with its own Client-side Environment Key. + +## Using the Environment Key + +You must supply the Environment Key with each request in an HTTP header named `X-Environment-Key`. + +```bash +X-Environment-Key: +``` + +The SDKs handle this for you automatically when you initialize them with the key. If you are making direct calls to the API, you will need to include this header in every request. \ No newline at end of file diff --git a/docs/docs/integrating-with-flagsmith/flagsmith-api-overview/flags-api/code-examples.md b/docs/docs/integrating-with-flagsmith/flagsmith-api-overview/flags-api/code-examples.md new file mode 100644 index 000000000000..0dffe8287fa9 --- /dev/null +++ b/docs/docs/integrating-with-flagsmith/flagsmith-api-overview/flags-api/code-examples.md @@ -0,0 +1,42 @@ +--- +title: Code Examples +sidebar_label: Code Examples +--- + +Here are some `curl` examples demonstrating how to interact directly with the Flags API. + +### Get Environment Flags + +This command retrieves all the default flag states and remote config values for a specific environment. + +```bash +curl 'https://edge.api.flagsmith.com/api/v1/flags/' \ + -H 'X-Environment-Key: ' +``` + +### Get Flags for an Identified User + +This command performs the entire SDK identity workflow in a single call: + +1. Lazily creates an identity if it doesn't already exist. +2. Sets or updates traits for that identity. +3. Receives the flags for that identity, including any segment or identity-specific overrides. + +```bash +curl --request POST 'https://edge.api.flagsmith.com/api/v1/identities/' \ + --header 'X-Environment-Key: ' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "identifier":"user_12345", + "traits": [ + { + "trait_key": "subscription_plan", + "trait_value": "premium" + }, + { + "trait_key": "has_beta_access", + "trait_value": true + } + ] + }' +``` \ No newline at end of file diff --git a/docs/docs/integrating-with-flagsmith/flagsmith-api-overview/flags-api/index.md b/docs/docs/integrating-with-flagsmith/flagsmith-api-overview/flags-api/index.md new file mode 100644 index 000000000000..9c2e80b9949c --- /dev/null +++ b/docs/docs/integrating-with-flagsmith/flagsmith-api-overview/flags-api/index.md @@ -0,0 +1,17 @@ +--- +title: Flags API Reference +sidebar_label: Flags API +--- + +The Flags API is the public-facing API that your SDKs use to retrieve feature flags and remote configuration for your users. It's designed for high performance and low latency, with a globally distributed infrastructure to serve requests quickly, wherever your users are. + +This API is used for **reading** flag states and user traits, not for managing your projects. + +## Endpoints + +The two main endpoints you will interact with via the SDKs are: + +- `/flags/`: Get all flags for a given environment. +- `/identities/`: Get all flags and traits for a specific user identity. + +For SaaS customers, the base URL for the Flags API is `https://edge.api.flagsmith.com/`. Our Edge API specification is detailed [here](/edge-api/overview). \ No newline at end of file diff --git a/docs/docs/integrating-with-flagsmith/flagsmith-api-overview/index.md b/docs/docs/integrating-with-flagsmith/flagsmith-api-overview/index.md new file mode 100644 index 000000000000..619e8baea38f --- /dev/null +++ b/docs/docs/integrating-with-flagsmith/flagsmith-api-overview/index.md @@ -0,0 +1,27 @@ +--- +title: Flagsmith API Overview +sidebar_label: Overview +sidebar_position: 10 +--- + +The Flagsmith API is divided into two distinct parts, each serving a different purpose. Understanding the difference is key to integrating with Flagsmith effectively. + +### 1. The Flags API (Public SDK API) + +This is the API that your client and server-side SDKs interact with to get flag and remote configuration values for your environments and users. It's designed to be fast, scalable, and publicly accessible. + +- **Purpose:** Serving flags to your applications. +- **Authentication:** Uses a public, non-secret **Environment Key**. +- **Security:** Open by design. The Environment Key can be exposed in client-side code. + +[Learn more about the Flags API](./flags-api). + +### 2. The Admin API (Private Admin API) + +This is the API you use to programmatically manage your Flagsmith projects. Anything you can do in the Flagsmith dashboard, you can also do via the Admin API. + +- **Purpose:** Creating, updating, and deleting projects, environments, flags, segments, and users. +- **Authentication:** Uses a secret **Organisation API Token**. +- **Security:** Requires a secret key that should never be exposed in client-side code. + +[Learn more about the Admin API](./admin-api). \ No newline at end of file diff --git a/docs/docs/integrating-with-flagsmith/integration-overview.md b/docs/docs/integrating-with-flagsmith/integration-overview.md new file mode 100644 index 000000000000..5be7300c5c6f --- /dev/null +++ b/docs/docs/integrating-with-flagsmith/integration-overview.md @@ -0,0 +1,54 @@ +--- +title: Integration Overview +sidebar_label: Integration Overview +sidebar_position: 1 +--- + +Flagsmith is designed to be integrated into your applications in a variety of ways, depending on your architecture and requirements. This guide provides an overview of the different integration options available. + +## Deciding between Front-end and Back-end Feature Flags + +One of the first decisions to make when integrating Flagsmith is whether to evaluate flags on the front-end (client-side) or back-end (server-side). + +### Front-end / Client-side + +Client-side SDKs (for browsers, mobile apps, etc.) are great for features that directly impact the user interface, such as showing or hiding a new element, changing button colours, or running A/B tests on UI components. + +- **Pros:** Fast UI updates, easy to implement for UI-related features. +- **Cons:** The Environment Key is public, and segment/targeting rules are not exposed to the client to prevent leaking sensitive information. + +### Back-end / Server-side + +Server-side SDKs run in your trusted back-end environment. They are ideal for controlling deeper application logic, such as enabling a new API endpoint, changing the behaviour of an algorithm, or managing access to certain features based on user permissions that are only known on the server. + +- **Pros:** Secure environment, full access to all targeting rules, can be used to control critical application logic. +- **Cons:** May require an additional API call from the front-end to the back-end to get the flag state if it's needed in the UI. + +You can read more about the differences in our [SDKs Overview documentation](/integrating-with-flagsmith/integration-overview). + +## Identities and Traits + +To get the most out of Flagsmith, you'll want to identify your users. This allows you to: + +- Override flags for specific users. +- Run A/B tests. +- Gradually roll out features to a percentage of your users. +- Target features to specific segments of users. + +An **identity** represents a single user in your application. You can also store **traits** against an identity. Traits are key-value pairs that describe a user, for example, their subscription plan, their location, or how many times they've logged in. + +You can learn more in our documentation on [Managing Identities](/flagsmith-concepts/identities). + +### Transient Traits and Identities + +For privacy-sensitive use cases, you can use transient traits and identities. This allows you to evaluate flags based on user data without persisting that data in Flagsmith. This is useful for things like: + +- Using PII (Personally Identifiable Information) for segmentation without storing it. +- Running experiments on anonymous users. +- Temporarily overriding a trait for a single session. + +Learn more about this feature in our [Transient Traits and Identities documentation](/flagsmith-concepts/identities#transient-traits). + +## Third-party Integrations + +Flagsmith also integrates with a variety of third-party tools for analytics, project management, and more. You can browse all available integrations in the [Integrations section](/third-party-integrations/analytics/segment). \ No newline at end of file diff --git a/docs/docs/clients/openfeature.md b/docs/docs/integrating-with-flagsmith/openfeature.md similarity index 61% rename from docs/docs/clients/openfeature.md rename to docs/docs/integrating-with-flagsmith/openfeature.md index 7cee9bd4bead..d79ee9ee7c4e 100644 --- a/docs/docs/clients/openfeature.md +++ b/docs/docs/integrating-with-flagsmith/openfeature.md @@ -1,24 +1,20 @@ --- description: OpenFeature sidebar_label: OpenFeature -sidebar_position: 2 +sidebar_position: 50 --- # OpenFeature -[OpenFeature](https://openfeature.dev/) is an open standard for feature flag management, created to support a robust -feature flag ecosystem using cloud native technologies. OpenFeature provides a unified API and SDK, and a -developer-first, cloud-native implementation, with extensibility for open source and commercial offerings. +[OpenFeature](https://openfeature.dev/) is an open standard for feature flag management, created to support a robust feature flag ecosystem using cloud native technologies. OpenFeature provides a unified API and SDK, and a developer-first, cloud-native implementation, with extensibility for open source and commercial offerings. -Flagsmith is proud to contribute to this initiative, and is a governance board member of this CNCF project. Our goal, -and that of OpenFeature, is to recommend using OpenFeature as the default SDK interface for Flagsmith projects. +Flagsmith is proud to contribute to this initiative, and is a governance board member of this CNCF project. Our goal, and that of OpenFeature, is to recommend using OpenFeature as the default SDK interface for Flagsmith projects. -OpenFeature is being actively worked on; we encourage anyone interested Feature Flags and Open Source to get involved! +OpenFeature is being actively worked on; we encourage anyone interested in feature flags and open source to get involved! -## Flagsmith [OpenFeature](https://www.flagsmith.com/openfeature) Providers +## Flagsmith OpenFeature Providers -We currently offer [OpenFeature Providers](https://docs.openfeature.dev/docs/reference/concepts/provider) for the -following languages: +We currently offer [OpenFeature Providers](https://docs.openfeature.dev/docs/reference/concepts/provider) for the following languages: - [Go](https://github.com/open-feature/go-sdk-contrib/tree/main/providers/flagsmith) - [Java](https://github.com/open-feature/java-sdk-contrib/tree/main/providers/flagsmith) diff --git a/docs/docs/clients/_category_.json b/docs/docs/integrating-with-flagsmith/sdks/_category_.json similarity index 70% rename from docs/docs/clients/_category_.json rename to docs/docs/integrating-with-flagsmith/sdks/_category_.json index c9009f2f8bd2..888d7ebc65a9 100644 --- a/docs/docs/clients/_category_.json +++ b/docs/docs/integrating-with-flagsmith/sdks/_category_.json @@ -1,5 +1,5 @@ { "label": "SDKs", - "position": 60, + "position": 2, "collapsed": true } diff --git a/docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/_category_.json b/docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/_category_.json new file mode 100644 index 000000000000..fac485a57824 --- /dev/null +++ b/docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Client-Side SDKs", + "position": 20, + "collapsed": true +} diff --git a/docs/docs/clients/client-side/android.md b/docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/android.md similarity index 98% rename from docs/docs/clients/client-side/android.md rename to docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/android.md index 7845d8787b00..97f046eea679 100644 --- a/docs/docs/clients/client-side/android.md +++ b/docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/android.md @@ -2,7 +2,6 @@ title: Flagsmith Android/Kotlin SDK sidebar_label: Android / Kotlin description: Manage your Feature Flags and Remote Config in your Android applications. -slug: /clients/android --- import CodeBlock from '@theme/CodeBlock'; import { AndroidVersion } from '@site/src/components/SdkVersions.js'; @@ -81,7 +80,7 @@ flagsmith.getFeatureFlags { result -> } ``` -### Get Flags for an identity +### Get Flags for an Identity To get feature flags for a specific identity: @@ -142,7 +141,7 @@ flagsmith.setTrait(Trait(key = "set-from-client", value = "12345"), identity = " ### Get all Traits To retrieve a trait for a particular identity as explained here -[Traits](../../basic-features/managing-identities.md#identity-traits) +[Traits](/flagsmith-concepts/identities#identity-traits) ```kotlin flagsmith.getTraits(identity = "test@test.com") { result -> diff --git a/docs/docs/clients/client-side/flutter.md b/docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/flutter.md similarity index 95% rename from docs/docs/clients/client-side/flutter.md rename to docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/flutter.md index b019eb2bcac7..57cc86dcc237 100644 --- a/docs/docs/clients/client-side/flutter.md +++ b/docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/flutter.md @@ -1,8 +1,7 @@ --- title: Flagsmith Flutter SDK sidebar_label: Flutter -description: Manage your Feature Flags and Remote Config in your Flutter applications. -slug: /clients/flutter +description: Manage your Feature Flags and Remote Config in your Flutter Applications. --- import CodeBlock from '@theme/CodeBlock'; import { FlutterVersion } from '@site/src/components/SdkVersions.js'; @@ -146,7 +145,7 @@ bool isFeatureEnabled = flagsmithClient.hasCachedFeatureFlag('feature'); ### Identifying users -To check if a feature exists for an Identity: +To check if a feature exists for an identity: ```dart final user = Identity(identifier: 'flagsmith_sample_user'); @@ -158,7 +157,7 @@ if (featureEnabled) { } ``` -To get the feature flag configuration value for an Identity: +To get the feature flag configuration value for an identity: ```dart final myRemoteConfig = await flagsmithClient.getFeatureFlagValue('my_test_feature', user: user); @@ -169,7 +168,7 @@ if (myRemoteConfig != null) { } ``` -To get the user traits for an Identity: +To get the user traits for an identity: ```dart final userTraits = await flagsmithClient.getTraits(user) @@ -180,7 +179,7 @@ if (userTraits != null && userTraits) { } ``` -To get the trait value for an Identity and specific Trait key: +To get the trait value for an identity and specific trait key: ```dart final userTrait = await flagsmithClient.getTrait(user, 'cookies_key'); @@ -191,7 +190,7 @@ if (userTrait != null) { } ``` -Or get user traits for an Identity and specific Trait keys: +Or get user traits for an identity and specific trait keys: ```dart final userTraits = await flagsmithClient.getTraits(user, keys: ['cookies_key', 'other_trait']); @@ -202,7 +201,7 @@ if (userTraits != null) { } ``` -To update a user trait for an Identity: +To update a user trait for an identity: ```dart final userTrait = await flagsmithClient.getTrait(user, 'cookies_key'); diff --git a/docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/index.md b/docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/index.md new file mode 100644 index 000000000000..2f53c30b11bc --- /dev/null +++ b/docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/index.md @@ -0,0 +1,29 @@ +--- +title: Client-Side SDKs +sidebar_label: Client-Side SDKs +sidebar_position: 1 +--- + +# Client-Side SDKs + +Client-side SDKs are designed to run in browser environments, mobile applications, and other client-side contexts where you need to evaluate feature flags on the user's device. + +## Available SDKs + +- [JavaScript](/integrating-with-flagsmith/sdks/client-side-sdks/javascript) - For web applications +- [React](/integrating-with-flagsmith/sdks/client-side-sdks/react) - For React applications +- [Next.js and SSR](/integrating-with-flagsmith/sdks/client-side-sdks/nextjs-and-ssr) - For Next.js applications with server-side rendering +- [Android](/integrating-with-flagsmith/sdks/client-side-sdks/android) - For Android applications +- [iOS](/integrating-with-flagsmith/sdks/client-side-sdks/ios) - For iOS applications +- [Flutter](/integrating-with-flagsmith/sdks/client-side-sdks/flutter) - For Flutter applications + +## Key Features + +- **Real-time updates**: Client-side SDKs can receive real-time flag updates +- **Local evaluation**: Some SDKs support local evaluation for better performance +- **Trait management**: Ability to set and manage user traits +- **Analytics integration**: Built-in support for flag analytics + +## Getting Started + +Choose the SDK that matches your platform and follow the specific integration guide for your technology stack. diff --git a/docs/docs/clients/client-side/ios.mdx b/docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/ios.mdx similarity index 90% rename from docs/docs/clients/client-side/ios.mdx rename to docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/ios.mdx index bd18f192b84a..06a7e7355ad7 100644 --- a/docs/docs/clients/client-side/ios.mdx +++ b/docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/ios.mdx @@ -2,13 +2,12 @@ title: Flagsmith iOS SDK sidebar_label: iOS / Swift description: Manage your Feature Flags and Remote Config in your iOS applications. -slug: /clients/ios --- import CodeBlock from '@theme/CodeBlock'; import { CocoapodsVersion, SwiftPMVersion } from '@site/src/components/SdkVersions.js'; -This library can be used with iOS and macOS applications. The source code for the client is available on +This library can be used with iOS and Mac applications. The source code for the client is available on [GitHub](https://github.com/flagsmith/flagsmith-ios-client). ## Installation @@ -54,7 +53,7 @@ The SDK is initialised against a single environment within a project on [https:/ for example the Development or Production environment. You can find your Client-side Environment Key in the Environment settings page. -### Initialisation +### Initialization Within your application delegate (usually _AppDelegate.swift_) add: @@ -117,9 +116,9 @@ Flagsmith.shared.getFeatureValue(withID: "test_feature2", forIdentity: nil) { (r ``` These methods can also specify a particular identity to retrieve the values for a user registration. See -[Identities](/basic-features/managing-identities/) , using the **forIdentity** parameter. +[Identities](/flagsmith-concepts/identities) , using the **forIdentity** parameter. -To retrieve a trait for a particular identity (see [Traits](/basic-features/managing-identities#identity-traits)): +To retrieve a trait for a particular identity (see [Traits](/flagsmith-concepts/identities#identity-traits)): ```swift Flagsmith.shared.getTraits(forIdentity: "test_user@test.com") {(result) in @@ -304,15 +303,16 @@ If more customisation is required, you can override the cache implemention with Flagsmith.shared.cacheConfig.cache = ``` -### Real-time updates +### Real Time Updates -[Real-time flag updates](/advanced-use/real-time-flags) are disabled by default. To enable them, set the following flag: +By default, real-time updates are disabled. When enabled the SDK will listen for changes to flags and update the cache as +needed. If you want to enable real-time updates you can do so by setting the following flag: ```swift -Flagsmith.shared.enableRealtimeUpdates = true +Flagsmith.shared.enableRealTimeUpdates = true ``` -Then, the `flagStream` property will emit new flags as updates are received: +It's possible to listen to updates in real time from the SDK using the `flagStream` property. ```swift func subscribeToFlagUpdates() { @@ -331,14 +331,16 @@ you replace your Environment Key in the `AppDelegate.swift` file. ## Override default configuration -By default, the client connects to the public Flagsmith SaaS. You can override the configuration as follows: +By default, the client uses a default configuration. You can override the configuration as follows: + +Override just the default API URI with your own: ```swift func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { Flagsmith.shared.apiKey = "" - Flagsmith.shared.baseURL = URL(string: "https://flagsmith.example.com/api/v1/")! - Flagsmith.shared.eventSourceBaseURL = URL(string: "https://realtime.flagsmith.example.com/")! + Flagsmith.shared.baseURL = "https://flagsmith.example.com/api/v1/" + Flagsmith.eventSourceBaseURL = "https://realtime.flagsmith.example.com/" // The rest of your launch method code } ``` diff --git a/docs/docs/clients/client-side/javascript.md b/docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/javascript.md similarity index 96% rename from docs/docs/clients/client-side/javascript.md rename to docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/javascript.md index 0db612e8d029..ebbf7731e52a 100644 --- a/docs/docs/clients/client-side/javascript.md +++ b/docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/javascript.md @@ -1,15 +1,12 @@ --- title: Flagsmith JavaScript SDK sidebar_label: JavaScript -description: Manage your Feature Flags and Remote Config in your JavaScript applications. -slug: /clients/javascript +description: Manage your Feature Flags and Remote Config in your JavaScript Applications. --- -This library can be used with pure JavaScript, React (and all other popular frameworks/libraries) and React Native -projects. The source code for the client is available on [GitHub](https://github.com/flagsmith/flagsmith-js-client). +This library can be used with pure JavaScript, React (and all other popular frameworks/libraries) and React Native projects. The source code for the client is available on [GitHub](https://github.com/flagsmith/flagsmith-js-client). -Example applications for a variety of JavaScript frameworks such as React, Vue and Angular, as well as React Native, can -be found here: +Example applications for a variety of JavaScript frameworks such as React, Vue and Angular, as well as React Native, can be found here: - [Flagsmith Framework Examples](https://github.com/Flagsmith/flagsmith-js-examples/tree/main) @@ -27,7 +24,7 @@ npm i flagsmith --save The React Native SDK shares the exact same implementation of Flagsmith, however it requires an implementation of AsyncStorage to be provided (e.g. @react-native-community/async-storage) in order to utilise analytics and caching. See -[here](/clients/javascript#initialisation-options). +[here](/integrating-with-flagsmith/sdks/client-side-sdks/javascript#initialisation-options). ::: @@ -82,7 +79,7 @@ defaults. The promise will reject if there is no cache and an invalid or no API ### Providing Default Flags You can define default flag values when initialising the SDK. This ensures that your application works as intended in -the event that it [cannot receive a response from our API](/guides-and-examples/defensive-coding). +the event that it [cannot receive a response from our API](/best-practices/defensive-coding). ```javascript import flagsmith from 'flagsmith or react-native-flagsmith'; //Add this line if you're using flagsmith via npm @@ -106,11 +103,11 @@ try { ### Default Flag Offline Handler You can automatically set default flags for your frontend application as part of your CI/CD process by using our -[CLI](/clients/CLI) and offline hander in your build pipelines. +[CLI](/integrating-with-flagsmith/CLI) and offline hander in your build pipelines. The main steps to achieving this are as follows: -1. Install the [CLI](/clients/CLI) `npm i flagsmith-cli --save-dev` +1. Install the [CLI](/integrating-with-flagsmith/CLI) `npm i flagsmith-cli --save-dev` 2. Call the CLI as part of npm postinstall to create a `flagsmith.json` file each time you run `npm install`. This can be done by either: @@ -126,7 +123,7 @@ cli commands can be found [here](https://github.com/Flagsmith/flagsmith-cli). ## Identifying users Identifying users allows you to target specific users from the Flagsmith dashboard and configure features and traits. -You can call this before or after you initialise the project, calling it after will re-fetch features from the API. +You can call this before or after you initialise the project, calling it after will re-fetch feature flags from the API. You can identify the users as part of initialising the client or after with the function `flagsmith.identify`. @@ -148,7 +145,7 @@ flagsmith.init({ const { isFromServer } = params; //determines if the update came from the server or local cached storage - //Set a trait against the Identity + //Set a trait against the identity flagsmith.setTrait('favourite_colour', 'blue'); //This save the trait against the user, it can be queried with flagsmith.getTrait //Check for a feature @@ -196,7 +193,7 @@ flagsmith.init({ const { isFromServer } = params; //determines if the update came from the server or local cached storage - //Set a trait against the Identity + //Set a trait against the identity flagsmith.setTrait('favourite_colour', 'blue'); //This save the trait against the user, it can be queried with flagsmith.getTrait //Check for a feature @@ -229,14 +226,14 @@ All function and property types can be seen | Property | Description | Required | Default Value | | ------------------------------------------------------------------------------------------- | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -------: | ----------------------------------------------------: | -| `environmentID: string` | Defines which project environment you wish to get flags for. _example ACME Project - Staging._ | **YES** | null | +| `environmentID: string` | Defines which project environment you wish to get flags for. _example ACME project - Staging._ | **YES** | null | | `onChange?: (previousFlags:IFlags, params:IRetrieveInfo, loadingState:LoadingState)=> void` | Your callback function for when the flags are retrieved `(previousFlags,{isFromServer:true/false,flagsChanged: true/false, traitsChanged:true/false})=>{...}` | **YES** | null | | `onError?: (res:{message:string}) => void` | Callback function on failure to retrieve flags. `(error)=>{...}` | | null | -| `realtime?:boolean` | Whether to listen for [Real Time Flag events](/advanced-use/real-time-flags) | | false | +| `realtime?:boolean` | Whether to listen for [Real Time Flag events](/performance/real-time-flags) | | false | | `AsyncStorage?:any` | Needed in certain frameworks cacheFlags and enableAnalytics options, used to tell the library what implementation of AsyncStorage your app uses, e.g. @react-native-community/async-storage, for web this defaults to an internal implementation. | | built in implementation for web, otherwise undefined. | | `cacheFlags?: boolean` | Any time flags are retrieved they will be cached, flags and identities will then be retrieved from local storage before hitting the API (see cache options). Requires AsyncStorage to be accessible. | | null | | `cacheOptions?: \{ttl?:number, skipAPI?:boolean, loadStale?:boolean\}` | A ttl in ms (default to 0 which means infinite) and option to skip hitting the API in flagsmith.init if there's cache available. Setting `loadStale: true` will still use cached values regardless of skipping the API. | | \{ttl:0, skipAPI:false, loadStale: false\} | -| `enableAnalytics?: boolean` | [Enable sending flag analytics](/advanced-use/flag-analytics.md) for getValue and hasFeature evaluations. | | false | +| `enableAnalytics?: boolean` | [Enable sending flag analytics](/managing-flags/flag-analytics) for getValue and hasFeature evaluations. | | false | | `enableLogs?: boolean` | Enables logging for key Flagsmith events | | null | | `defaultFlags?: {flag_name: {enabled: boolean, value: string,number,boolean}}` | Allows you define default features, these will all be overridden on first retrieval of features. | | null | | `preventFetch?: boolean` | If you want to disable fetching flags and call getFlags later. | | false | @@ -256,7 +253,7 @@ All function and property types can be seen | getTrait(key:string)=> string|number|boolean | Once used with an identified user you can get the value of any trait that is set for them e.g. `flagsmith.getTrait("accepted_cookie_policy")` | | getAllTraits()=> Record<string,string|number|boolean> | Once used with an identified user you can get a key value pair of all traits that are set for them e.g. `flagsmith.getTraits()` | | getState()=>IState | Retrieves the current state of flagsmith, useful in NextJS / isomorphic applications. `flagsmith.getState()` | -| setState(state: IState)=>void | Set the current state of flagsmith, [useful in NextJS / isomorphic applications.](/clients/next-ssr#comparing-ssr-and-client-side-flagsmith-usage) e.g. `flagsmith.setState({identity: 'mary@mycompany.com'})`. | +| setState(state: IState)=>void | Set the current state of flagsmith, [useful in NextJS / isomorphic applications.](/integrating-with-flagsmith/sdks/client-side-sdks/nextjs-and-ssr) e.g. `flagsmith.setState({identity: 'mary@mycompany.com'})`. | | setTrait(key:string, value:string|number|boolean)=> Promise<IFlags> | Once used with an identified user you can set the value of any trait relevant to them e.g. `flagsmith.setTrait("accepted_cookie_policy", true)` | | setTraits(values:Record\)=\> Promise<IFlags> | Set multiple traits e.g. `flagsmith.setTraits({foo:"bar",numericProp:1,boolProp:true})`. Setting a value of null for a trait will remove that trait. | | incrementTrait(key:string, value:number)=> Promise<IFlags> | You can also increment/decrement a particular trait them e.g. `flagsmith.incrementTrait("click_count", 1)` | diff --git a/docs/docs/clients/client-side/nextjs-and-ssr.md b/docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/nextjs-and-ssr.md similarity index 91% rename from docs/docs/clients/client-side/nextjs-and-ssr.md rename to docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/nextjs-and-ssr.md index cb17580c0917..b096cfeb8e02 100644 --- a/docs/docs/clients/client-side/nextjs-and-ssr.md +++ b/docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/nextjs-and-ssr.md @@ -2,7 +2,6 @@ title: Flagsmith React SDK sidebar_label: Next.js and SSR description: Manage your Feature Flags and Remote Config with NextJS and SSR. -slug: /clients/next-ssr --- The JavaScript Library contains a bundled isomorphic library, allowing you to fetch flags in the server and hydrate your @@ -34,15 +33,15 @@ settings page. ## Comparing SSR and client-side Flagsmith usage -The SDK is initialised and used in the same way as the [JavaScript](/clients/javascript) and [React](/clients/react) +The SDK is initialised and used in the same way as the [JavaScript](/integrating-with-flagsmith/sdks/client-side-sdks/javascript) and [React](/integrating-with-flagsmith/sdks/client-side-sdks/react) SDK. The main difference is that Flagsmith should be imported from `flagsmith/isomorphic`. The main flow with Next.js and any JavaScript-based SSR can be as follows: 1. Fetch the flags on the server, optionally passing an identity to - [`flagsmith.init({})`](/clients/javascript#initialisation-options) -2. Pass the resulting state to the client with [`flagsmith.getState()`](/clients/javascript#available-functions) -3. Initialise flagsmith on the client with [`flagsmith.setState(state)`](/clients/javascript#available-functions) + [`flagsmith.init({})`](/integrating-with-flagsmith/sdks/client-side-sdks/javascript#initialisation-options) +2. Pass the resulting state to the client with [`flagsmith.getState()`](/integrating-with-flagsmith/sdks/client-side-sdks/javascript#available-functions) +3. Initialise flagsmith on the client with [`flagsmith.setState(state)`](/integrating-with-flagsmith/sdks/client-side-sdks/javascript#available-functions) ### Example: Initialising the SDK with Next.js @@ -154,7 +153,7 @@ export function MyComponent() { } ``` -From this point on, the SDK usage is the same as the [React SDK Guide](/clients/react) +From this point on, the SDK usage is the same as the [React SDK Guide](/integrating-with-flagsmith/sdks/client-side-sdks/react) ### Example: Flagsmith with Next.js middleware @@ -223,4 +222,4 @@ Step 3: Optionally force the client to fetch a fresh set of flags flagsmith.getFlags(); ``` -From that point the SDK usage is the same as the [JavaScript SDK Guide](/clients/javascript) +From that point the SDK usage is the same as the [JavaScript SDK Guide](/integrating-with-flagsmith/sdks/client-side-sdks/javascript) diff --git a/docs/docs/clients/client-side/react.md b/docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/react.md similarity index 92% rename from docs/docs/clients/client-side/react.md rename to docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/react.md index 8238e835c8fd..10a73d0e7139 100644 --- a/docs/docs/clients/client-side/react.md +++ b/docs/docs/integrating-with-flagsmith/sdks/client-side-sdks/react.md @@ -2,7 +2,6 @@ title: Flagsmith React SDK sidebar_label: React and React Native description: Manage your Feature Flags and Remote Config with React and React Native Hooks. -slug: /clients/react --- This library includes React/React Native Hooks allowing you to query individual features and flags that limit @@ -28,7 +27,7 @@ npm i flagsmith --save The React Native SDK shares the exact same implementation of Flagsmith, however, requires an implementation of AsyncStorage to be provided (e.g. @react-native-community/async-storage) in order to utilise analytics and caching. See -[here](/clients/javascript#initialisation-options). +[here](/integrating-with-flagsmith/sdks/client-side-sdks/javascript#initialisation-options). ::: @@ -60,12 +59,12 @@ export function AppRoot() { ``` Providing options to the Flagsmith provider will initialise the client, the API reference for these options can be found -[here](/clients/javascript#initialisation-options). +[here](/integrating-with-flagsmith/sdks/client-side-sdks/javascript#initialisation-options). :::tip Initialising before rendering the FlagsmithProvider If you wish to initialise the Flagsmith client before React rendering (e.g. in redux, or SSR) you can do so by calling -[flagsmith.init](/clients/javascript#example-initialising-the-sdk) and provide no options property to the +[flagsmith.init](/integrating-with-flagsmith/sdks/client-side-sdks/javascript#example-initialising-the-sdk) and provide no options property to the FlagsmithProvider component. ::: @@ -104,7 +103,7 @@ You can find the exact definitions of these types | ------------------------ | :------------------------------------------------------------------------------------------------------------: | -------: | ------------: | | `flagsmith: IFlagsmith` | Defines the flagsmith instance that the provider will use. | **YES** | null | | `options?: ` IInitConfig | Initialisation options to use. If you don't provide this you will have to call flagsmith.init elsewhere. | | null | -| `serverState?: IState` | Used to pass an initial state, in most cases as a result of SSR flagsmith.getState(). See [Next.js and SSR](/) | | null | +| `serverState?: IState` | Used to pass an initial state, in most cases as a result of SSR flagsmith.getState(). See [Next.js and SSR](/integrating-with-flagsmith/sdks/client-side-sdks/nextjs-and-ssr) | | null | ### Step 3: Using useFlagsmith to access the Flagsmith instance diff --git a/docs/docs/clients/index.md b/docs/docs/integrating-with-flagsmith/sdks/index.md similarity index 86% rename from docs/docs/clients/index.md rename to docs/docs/integrating-with-flagsmith/sdks/index.md index bd5f5877216c..7cc38276a2b4 100644 --- a/docs/docs/clients/index.md +++ b/docs/docs/integrating-with-flagsmith/sdks/index.md @@ -1,11 +1,12 @@ --- description: Manage your Feature Flags and Remote Config in your REST APIs. sidebar_position: 1 +sidebar_label: SDKs --- # SDKs Overview -Flagsmith ships with SDKs for a bunch of different programming languages. We also have a [REST API](rest.md) that you +Flagsmith ships with SDKs for a bunch of different programming languages. We also have a [REST API](/integrating-with-flagsmith/flagsmith-api-overview) that you can use if you want to consume the API directly. Our SDKs are split into two different groups. These SDKs have different methods of operation due to the differences in @@ -26,28 +27,28 @@ depending on the SDK you are using. Client-side SDKs run in web browsers or on mobile devices. These runtimes execute within _untrusted environments_. Anyone using the Javascript SDK in a web browser, for example, can find the Client-side SDK, create a new Identity, look at their flags and -[potentially write Traits to the Identity](/system-administration/security#preventing-client-sdks-from-setting-traits). +[potentially write Traits to the Identity](/administration-and-security/governance-and-compliance/security#preventing-client-sdks-from-setting-traits). Client-side SDKs are also limited to the -[types of data that they have access to](/guides-and-examples/integration-approaches#segment-and-targeting-rules-are-not-leaked-to-the-client). +[types of data that they have access to](/best-practices/integration-approaches#segment-and-targeting-rules-are-not-leaked-to-the-client). Client-side Environment keys are designed to be shared publicly, for example in your HTML/JS code that is sent to a web browser. -Client-side SDKs hit our [Edge API](../advanced-use/edge-api.md) directly to retrieve their flags. +Client-side SDKs hit our [Edge API](/deployment-self-hosting/edge-proxy) directly to retrieve their flags. Read more about our Client-side SDKs for your language/platform: -- [Javascript](/clients/javascript) -- [Android/Kotlin](/clients/android) -- [Flutter](/clients/flutter) -- [iOS/Swift](/clients/ios) -- [React & React Native](/clients/react) -- [Next.js, Svelte and SSR](/clients/next-ssr) +- [Javascript](/integrating-with-flagsmith/sdks/client-side-sdks/javascript) +- [Android/Kotlin](/integrating-with-flagsmith/sdks/client-side-sdks/android) +- [iOS/Swift](/integrating-with-flagsmith/sdks/client-side-sdks/ios) +- [React](/integrating-with-flagsmith/sdks/client-side-sdks/react) +- [Next.js and SSR](/integrating-with-flagsmith/sdks/client-side-sdks/nextjs-and-ssr) +- [Flutter](/integrating-with-flagsmith/sdks/client-side-sdks/flutter) ## Server-side SDKs -[Server-side SDKs](/clients/server-side) run within _trusted environments_ - typically the server infrastructure that +[Server-side SDKs](/integrating-with-flagsmith/sdks/server-side) run within _trusted environments_ - typically the server infrastructure that you have control over. Because of this, you should not share your Server-side Environment keys publicly; they should be treated as secret. @@ -82,7 +83,7 @@ the Flag Engine, and the engine runs within your server environment within the F ![Local Evaluation Diagram](/img/sdk-local-evaluation.svg) You have to configure the SDK to run in Local Evaluation mode. See the -[SDK configuration options](/clients/server-side#configuring-the-sdk) for details on how to do that in your particular language. +[SDK configuration options](/integrating-with-flagsmith/sdks/server-side#configuring-the-sdk) for details on how to do that in your particular language. When the SDK is initialised in Local Evaluation mode, it grabs the entire set of details about the Environment from the Flagsmith API. For a given Environment, this includes: @@ -96,7 +97,7 @@ within your server infrastructure. :::info -**[Edge API](advanced-use/edge-api.md) only**: When using identity overrides in local evaluation: +**[Edge API](/deployment-self-hosting/edge-proxy) only**: When using identity overrides in local evaluation: - Keep overrides count under 500. - Make sure your environment settings enable it. @@ -162,7 +163,7 @@ for the relevant language platform for details. network request. If this approach does not work for you (generally for reasons of latency or overly chatty networking) you should -consider Local Evaluation mode (explained below) or the [Edge Proxy](/advanced-use/edge-proxy). +consider Local Evaluation mode (explained below) or the [Edge Proxy](/deployment-self-hosting/edge-proxy). ### Local Evaluation Network Model @@ -298,7 +299,7 @@ situations: - If you want to work with Flagsmith programatically, for example when creating and deleting Environments as part of a CI/CD process. -- When using the [Terraform Provider](/integrations/terraform). +- When using the [Terraform Provider](/third-party-integrations/ci-cd/terraform). These keys are secret and should not be shared. @@ -340,7 +341,7 @@ When running in Local Evaluation mode, our SDKs expect to be run as long-lived p Lambda either break this contract or make it much more complicated. As a result, we do not recommend running our SDKs in Local Evaluation mode on top of platforms like Lambda where you do not have complete control over process lifetimes. -Our [Edge Proxy](/advanced-use/edge-proxy/) is a good candidate if you need to run local evaluation mode alongside +Our [Edge Proxy](/deployment-self-hosting/edge-proxy) is a good candidate if you need to run local evaluation mode alongside serverless platforms. ::: @@ -353,10 +354,10 @@ are all computed locally. - Identities and their Traits are **not** read from or written to the Flagsmith API, and so are not persisted in the datastore. This means that you have to provide the full complement of Traits when requesting the Flags for a particular Identity. Our SDKs all provide relevant methods to achieve this. -- [Analytics-based Integrations](/integrations#analytics-platforms) do not run. - [Flag Analytics](/advanced-use/flag-analytics) do still work, if enabled within the - [SDK setup](/clients/server-side#configuring-the-sdk). -- **[Edge API](advanced-use/edge-api.md) only**: Currently, Flagsmith SDKs support up to ~500 identity overrides, depending on flag value sizes and segment count, when used with +- [Analytics-based Integrations](/third-party-integrations#analytics-platforms) do not run. +- [Flag Analytics](/managing-flags/flag-analytics) do still work, if enabled within the + [SDK setup](/integrating-with-flagsmith/sdks/server-side#configuring-the-sdk). +- **[Edge API](/deployment-self-hosting/edge-proxy) only**: Currently, Flagsmith SDKs support up to ~500 identity overrides, depending on flag value sizes and segment count, when used with Edge API. If you store more than 1MB of environment document data, you will need to query the Edge API endpoint directly and use pagination: diff --git a/docs/docs/clients/server-side.mdx b/docs/docs/integrating-with-flagsmith/sdks/server-side.mdx similarity index 97% rename from docs/docs/clients/server-side.mdx rename to docs/docs/integrating-with-flagsmith/sdks/server-side.mdx index f5c6f3c1d2d7..a70cb49c3f62 100644 --- a/docs/docs/clients/server-side.mdx +++ b/docs/docs/integrating-with-flagsmith/sdks/server-side.mdx @@ -1,7 +1,7 @@ --- description: Manage your Feature Flags and Remote Config in your Server Side Applications. sidebar_label: Server Side -sidebar_position: 2 +sidebar_position: 30 --- import CodeBlock from '@theme/CodeBlock'; @@ -21,7 +21,7 @@ import { :::tip Server Side SDKs can run in 2 different modes: Local Evaluation and Remote Evaluation. We recommend -[reading up about the differences](/clients#server-side-sdks) first before integrating the SDKS into your applications. +[reading up about the differences](/integrating-with-flagsmith/integration-overview) first before integrating the SDKS into your applications. ::: @@ -574,25 +574,25 @@ secret_button_feature_value = Flagsmith.Client.get_feature_value(flags, "secret_
-### When running in [Remote Evaluation mode](/clients#remote-evaluation) +### When running in [Remote Evaluation mode](/integrating-with-flagsmith/integration-overview) -- When requesting Flags for an Identity, all the Traits defined in the SDK will automatically be persisted against the - Identity within the Flagsmith API. -- Traits passed to the SDK will be added to all the other previously persisted Traits associated with that Identity. -- This full set of Traits are then used to evaluate the Flag values for the Identity. +- When requesting flags for an identity, all the traits defined in the SDK will automatically be persisted against the + identity within the Flagsmith API. +- Traits passed to the SDK will be added to all the other previously persisted traits associated with that identity. +- This full set of traits are then used to evaluate the flag values for the identity. - This all happens in a single request/response. -### When running in [Local Evaluation mode](/clients#local-evaluation) +### When running in [Local Evaluation mode](/integrating-with-flagsmith/integration-overview) -- _Only_ the Traits provided to the SDK at runtime will be used. Local Evaluation mode, by design, does not make any - network requests to the Flagsmith API when evaluating Flags for an Identity. +- _Only_ the traits provided to the SDK at runtime will be used. Local Evaluation mode, by design, does not make any + network requests to the Flagsmith API when evaluating flags for an identity. - When running in Local Evaluation Mode, the SDK requests the - [Environment Document](/clients#the-environment-document) from the Flagsmith API. This contains all the - information required to make Flag Evaluations, but it does _not_ contain any Trait data. + [Environment Document](/integrating-with-flagsmith/integration-overview) from the Flagsmith API. This contains all the + information required to make flag evaluations, but it does _not_ contain any trait data. ## Managing Default Flags -Default Flags are configured by passing in a function that is called when a Flag cannot be found or if the network +Default flags are configured by passing in a function that is called when a flag cannot be found or if the network request to the API fails when retrieving flags. @@ -797,7 +797,7 @@ Flagsmith SDKs can be configured to include an offline handler which has 2 funct offline mode. To use it as a default handler, we recommend using the [flagsmith CLI](https://github.com/Flagsmith/flagsmith-cli) to -generate the [Environment Document](/clients#the-environment-document) and use our LocalFileHandler class, but you can +generate the [Environment Document](/integrating-with-flagsmith/integration-overview) and use our LocalFileHandler class, but you can also create your own offline handlers, by extending the base class. @@ -864,7 +864,7 @@ public class MyCustomOfflineHandler: BaseOfflineHandler Use LocalFileHandler -to read an environment file generated by the [Flagsmith CLI](/clients/CLI): +to read an environment file generated by the [Flagsmith CLI](/integrating-with-flagsmith/CLI): ```typescript import { Flagsmith, LocalFileHandler } from 'flagsmith-nodejs'; @@ -1004,27 +1004,27 @@ The Server Side SDKS share the same network behaviour across the different langu ### Remote Evaluation Mode Network Behaviour -- A blocking network request is made every time you make a call to get an Environment Flags. In Python, for example, +- A blocking network request is made every time you make a call to get an environment flags. In Python, for example, `flagsmith.get_environment_flags()` will trigger this request. -- A blocking network request is made every time you make a call to get an Identities Flags. In Python, for example, +- A blocking network request is made every time you make a call to get an identities flags. In Python, for example, `flagsmith.get_identity_flags(identifier=identifier, traits=traits)` will trigger this request. ### Local Evaluation Mode Network Behaviour :::info -When using Local Evaluation, it's important to read up on the [Pros, Cons and Caveats](/clients/#pros-cons-and-caveats). +When using Local Evaluation, it's important to read up on the [Pros, Cons and Caveats](/integrating-with-flagsmith/integration-overview). To use Local Evaluation mode, you must use a Server Side key. ::: -- When the SDK is initialised, it will make an asynchronous network request to retrieve details about the Environment. -- Every 60 seconds (by default), it will repeat this aysnchronous request to ensure that the Environment information it +- When the SDK is initialised, it will make an asynchronous network request to retrieve details about the environment. +- Every 60 seconds (by default), it will repeat this aysnchronous request to ensure that the environment information it has is up to date. To achieve Local Evaluation, in most languages, the SDK spawns a separate thread (or equivalent) to poll the API for -changes to the Environment. In certain languages, you may be required to terminate this thread before cleaning up the +changes to the environment. In certain languages, you may be required to terminate this thread before cleaning up the instance of the Flagsmith client. Languages in which this is necessary are provided below. diff --git a/docs/docs/integrations/apm/_category_.json b/docs/docs/integrations/apm/_category_.json deleted file mode 100644 index 6f54b0997a45..000000000000 --- a/docs/docs/integrations/apm/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Observability", - "collapsed": false, - "position": 20 -} \ No newline at end of file diff --git a/docs/docs/integrations/apm/sentry.md b/docs/docs/integrations/apm/sentry.md deleted file mode 100644 index 68aee0effeb1..000000000000 --- a/docs/docs/integrations/apm/sentry.md +++ /dev/null @@ -1,157 +0,0 @@ ---- -title: Sentry Integration -description: Integrate Flagsmith with Sentry -sidebar_label: Sentry -hide_title: true ---- - -![Sentry logo](/img/integrations/sentry/sentry-logo.svg) - -Integrate Flagsmith with Sentry to enable tracking suspect feature flag changes: - -- [Change Tracking](https://docs.sentry.io/product/issues/issue-details/feature-flags/#change-tracking) logs when - feature flags update in Flagsmith, helping correlate flag changes with new errors or regressions. -- [Evaluation Tracking](https://docs.sentry.io/product/issues/issue-details/feature-flags/#evaluation-tracking) captures - feature flag evaluation in the code, adding their state to Issue details and allowing for flag-based Issue search. - -## Change Tracking setup - -1. **In Sentry** - 1. Visit the - [feature flags settings page](https://sentry.io/orgredirect/organizations/:orgslug/settings/feature-flags/change-tracking/) - in a new tab. - 1. Click the "Add New Provider" button. - 1. Select "Generic" in the dropdown that says "Select a provider". - 1. Copy the provided Sentry webhook URL — we'll use that soon. - 1. **Do not close this page!** We're not done yet. -1. **In Flagsmith** - 1. Go to Integrations > Sentry > Add Integration. - 1. Choose the Environment from which Sentry will receive feature flag change events. - 1. Paste the URL copied above into "Sentry webhook URL". - 1. Insert a secret (10-60 characters) and copy it. - 1. Click "Save". ✅ -1. **Back to Sentry** - 1. Paste the secret copied above. - 1. Click "Add Provider". ✅ - -Flag change events should now be sent to Sentry, and displayed in Issue details. For more information, visit Sentry's -[Issue Details page documentation](https://docs.sentry.io/product/issues/issue-details/#feature-flags). - -## Evaluation Tracking setup - -Flagsmith relies on the OpenFeature SDK and its integration with Sentry in order to add **evaluated** feature flags to a -Sentry issue when it occurs. Learn more by reading OpenFeature's -[SDK docs](https://openfeature.dev/docs/reference/technologies/client/web/) and -[provider docs](https://openfeature.dev/docs/reference/concepts/provider). - -### Python - -Sentry offers [good documentation](https://docs.sentry.io/platforms/python/integrations/openfeature/) on how to -integrate the Sentry SDK with the OpenFeature SDK in Python. We'll extend it a bit adding an example of using it with -Flagsmith. - -You'll need to install the following libraries: - -```sh -pip install "sentry-sdk[openfeature]" openfeature-provider-flagsmith -``` - -The following snippet is a micro-application that reports feature flags to Sentry when an exception is raised. - -```python -import flagsmith -import sentry_sdk -from openfeature import api as of_api -from openfeature_flagsmith.provider import FlagsmithProvider -from sentry_sdk.integrations.openfeature import OpenFeatureIntegration - -app = flask.Flask(__name__) - -flagsmith_client = flagsmith.Flagsmith( - environment_key='', -) - -feature_flag_provider = FlagsmithProvider( - client=flagsmith_client, -) - -of_api.set_provider(feature_flag_provider) - -sentry_sdk.init( - dsn="", - send_default_pii=True, - integrations=[ - OpenFeatureIntegration(), - ] -) - -def apply_discount(price): - of_client = of_api.get_client() - is_discount_enabled = of_client.get_boolean_value('discount_enabled', False) - if is_discount_enabled: - discount = price / 0 # ZeroDivisionError - else: - discount = None - return price * discount # TypeError -``` - -## JavaScript - -Sentry offers [good documentation](https://docs.sentry.io/platforms/javascript/configuration/integrations/featureflags/) -on how to integrate the Sentry SDK with the OpenFeature SDK in JavaScript. We'll extend it a bit adding an example of -using it with Flagsmith. - -You'll need to install the following libraries: - -```sh -npm install @sentry/browser @openfeature/web-sdk @openfeature/flagsmith-client-provider -``` - -The following snippet extends -[Sentry's example](https://docs.sentry.io/platforms/javascript/configuration/integrations/openfeature/) with the -Flagsmith provider: - -```javascript -import * as Sentry from '@sentry/browser'; -import { FlagsmithClientProvider } from '@openfeature/flagsmith-client-provider'; -import { OpenFeature } from '@openfeature/web-sdk'; - -Sentry.init({ - dsn: '___PUBLIC_DSN___', - integrations: [Sentry.openFeatureIntegration()], -}); - -const flagsmithClientProvider = new FlagsmithClientProvider({ - environmentID: '', -}); -OpenFeature.setProvider(flagsmithClientProvider); - -OpenFeature.addHooks(new Sentry.OpenFeatureIntegrationHook()); - -const client = OpenFeature.getClient(); -const result = client.getBooleanValue('test-flag', false); // evaluate with a default value -Sentry.captureException(new Error('Something went wrong!')); -``` - -Alternatively, the Flagsmith SDK can be initialized with Sentry **without** OpenFeature: - -```javascript -import * as Sentry from '@sentry/browser'; -import flagsmith from 'flagsmith'; - -Sentry.init({ - dsn: '___PUBLIC_DSN___', - // ... -}); - -flagsmith.init({ - environmentID: '', - // ... - sentryClient: Sentry.getClient(), -}); -``` - ---- - -You can learn more about feature flags and Sentry issues in their -[Issue Details documentation](https://docs.sentry.io/product/issues/issue-details/#feature-flags). diff --git a/docs/docs/integrations/importers/_category_.json b/docs/docs/integrations/importers/_category_.json deleted file mode 100644 index 32e02144d4c1..000000000000 --- a/docs/docs/integrations/importers/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Importers", - "collapsed": false, - "position": 50 -} diff --git a/docs/docs/integrations/index.md b/docs/docs/integrations/index.md deleted file mode 100644 index 857dfad2e506..000000000000 --- a/docs/docs/integrations/index.md +++ /dev/null @@ -1,158 +0,0 @@ ---- -title: Flagsmith Integrations -description: Flagsmith Integrations -hide_title: true -sidebar_position: 1 ---- - -Flagsmith Integrates with the following providers. If you are interested in an integration with a platform not listed -here, please get in touch to discuss your particular use case. - -## CI/CD Platforms - ---- - - - -You can integrate Flagsmith with Terraform. Use our Terraform provider to drive flag management as part of your -Infrastructure as Code tooling. [Learn more](/integrations/terraform). - -## Analytics Platforms - ---- - - - -You can integrate Flagsmith with Adobe Analytics. You can automate AB tests by connecting the Flagsmith platform with -Adobe Analytics. - ---- - - - -You can integrate Flagsmith with Amplitude. You can automate AB tests by connecting the Flagsmith platform with -Amplitude. [Learn more](/integrations/analytics/amplitude). - ---- - - - -You can integrate Flagsmith with Heap Analytics. The integration automatically sends the flag states for identified -users into Heap for cohort analysis, A/B testing and more. [Learn more](/integrations/analytics/heap). - ---- - - - -You can integrate Flagsmith with Mixpanel. The integration automatically sends the flag states for identified users into -Mixpanel for cohort analysis, A/B testing and more. [Learn more](/integrations/analytics/mixpanel). - ---- - - - -You can integrate Flagsmith with Segment. Send your Identity flag states into segment for further downstream analysis. -[Learn more](/integrations/analytics/segment). - ---- - - - -You can integrate Flagsmith with Rudderstack. Send your Identity flag states into Rudderstack for further downstream -analysis. [Learn more](/integrations/analytics/rudderstack). - ---- - - - -You can integrate Flagsmith with your own Analytics Platform/Data Warehouse. Send your Identity flag states into -Snowflake, RedShift or anywhere else for further downstream analysis! [Learn more](/integrations/webhook). - -## Observability - ---- - - - -You can integrate Flagsmith with Datadog. Send flag change events from Flagsmith into your Datadog event stream. -[Learn more](/integrations/apm/datadog). - ---- - - - -You can integrate Flagsmith with Dynatrace. Send flag change events from Flagsmith into your Dynatrace event stream. -[Learn more](/integrations/apm/dynatrace). - ---- - - - -You can integrate Flagsmith with Grafana. Send flag change events from Flagsmith into Grafana as annotations. -[Learn more](/integrations/apm/grafana). -**Coming soon**: Integrate your Grafana and Prometheus alerts with Feature Health. - ---- - - - -You can integrate Flagsmith with New Relic. Send flag change events from Flagsmith into your Datadog event stream. -[Learn more](/integrations/apm/newrelic). - ---- - - - -You can integrate Flagsmith with AppDynamics. Send flag change events from Flagsmith into your AppDynamics event stream. -[Learn more](/integrations/apm/appdynamics). - -## Project Management - ---- - - - -View your Flagsmith flags inside Jira. [Learn more](/integrations/project-management/jira). - ---- - - - -Manage your Flagsmith Change Requests inside ServiceNow. [Learn more](/integrations/project-management/servicenow). - ---- - - - -You can integrate Flagsmith with Slack. Send flag change events from Flagsmith into your Slack channels. -[Learn more](/integrations/project-management/slack). - ---- - - - -View your Flagsmith flags inside GitHub Issues and Pull Request. [Learn more](/integrations/project-management/github). - ---- - -## Authentication Providers / IDPs - -### OAuth - -You can authenticate Flagsmith with OAuth providers. [Learn more](/system-administration/authentication/OAuth). - -### SAML - -You can authenticate Flagsmith with SAML providers. [Learn more](/system-administration/authentication/SAML). - -### LDAP - -You can authenticate Flagsmith with LDAP providers. [Learn more](/system-administration/authentication/LDAP). - -### Okta - -You can authenticate Flagsmith with Okta. [Learn more](/system-administration/authentication/Okta). - -### ADFS - -You can authenticate Flagsmith with ADFS. [Learn more](/system-administration/authentication/ADFS). diff --git a/docs/docs/integrations/project-management/_category_.json b/docs/docs/integrations/project-management/_category_.json deleted file mode 100644 index b6800cdd9a4e..000000000000 --- a/docs/docs/integrations/project-management/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Project Management", - "collapsed": false, - "position": 70 -} diff --git a/docs/docs/managing-flags/_category_.json b/docs/docs/managing-flags/_category_.json new file mode 100644 index 000000000000..84929cf92ac6 --- /dev/null +++ b/docs/docs/managing-flags/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Managing Flags", + "position": 3 +} \ No newline at end of file diff --git a/docs/docs/managing-flags/core-management.md b/docs/docs/managing-flags/core-management.md new file mode 100644 index 000000000000..14ec0241a5c3 --- /dev/null +++ b/docs/docs/managing-flags/core-management.md @@ -0,0 +1,90 @@ +--- +title: Basic Flag Management +sidebar_label: Basic Flag Management +sidebar_position: 1 +--- + +Feature flags in Flagsmith are used to categorise and monitor user actions or events, such as detecting spam or abuse. This guide covers how to create, edit, clone, and delete flags in your project. + +--- + +## Creating a Feature Flag + +To create a new feature flag: + +1. Go to the **Features** section in your dashboard. +2. Click **Create Feature**. +3. Enter a descriptive name for your flag (e.g., `new_ui_enabled`). +4. Fill in the available fields according to the specifications of your feature. Note that these values are applied to all your [environments]. You can edit each environment individually later. + - **Enabled by default**: determines the initial state of the flag for all environments. This can be edited for each environment later. + - **Value** (optional): additionally to their boolean value, you can choose a format and a value for your flag. + - **Tags** (optional): organise your flags by tags or add `protected` to prevent them from accidentally being deleted. + - **Description** (optional): add a feature flag description. + - **Server-side only**: enabling this prevents your flag from being accessed by client-side SDKs. +5. Click **Create Feature**. + +:::tip + +By clicking the **Create A/B/n Test** button, you can define values for A/B testing. To learn more about this operation, refer to the [A/B Testing guide](/experimentation-ab-testing). + +::: + +--- + +## Editing a Feature Flag + +You create feature flags once per project, but you edit them within each environment. To edit an existing feature flag: + +1. While on the **Environments** tab on the dashboard, use the dropdown to select the environment where you want to apply the changes. +2. Locate the feature flag you want to edit and click on it. You can use the search bar to find the feature flag by its name. + +:::tip + +If you just want to toggle the feature flag *on* or *off*, use the switch under the **View** column in the list view. + +::: + +3. On the **Value** tab, you can set the feature flag to be on or off, as well as edit a value for it. Click **Update Feature Value** to save your changes. +4. Optionally, create segment-specific feature flags and define **segment overrides**. Refer to the documentation to learn more about [segments](/flagsmith-concepts/segments). Save any changes by clicking the **Update Segment Overrides** button. +5. On the **Settings** tab, you can: + - Add tags to your feature flag. + - Assign it to specific *users* and *groups*. + - Update the feature flag's description. + - Mark the feature flag as **Server-side only** to prevent it from being accessed by client-side SDKs. + - Set the **Archived** status to filter the feature flag as no longer relevant. Note that the feature flag will still be returned by the endpoint as before. + Click **Update Settings** to save your changes. + +:::tip + +Changes made in the **Settings** tab affect all environments. Changes made in other tabs while editing a feature flag are valid only for the selected environment. + +::: + +--- + +## Deleting a Feature Flag + +To delete a feature flag: + +1. In the **Features** section, locate the feature flag you want to remove and click the **three dots** icon on the right. +2. Select the **Remove feature** option. +3. Type in the name of the feature flag to confirm the deletion. + +:::caution + +Deleting a feature flag is permanent and cannot be undone. Make sure your applications do not contain any reference to this feature flag before confirming deleting it. + +::: + +--- + +## Troubleshooting + +### Feature Flags Not Updating + +- Make sure you have saved your changes in each tab of the edit feature flag panel. +- Check that you are in the correct project and environment. + +### Permission Issues + +- You may need additional permissions to create, edit, clone, or delete feature flags. If you see permission errors or options are disabled, contact your Flagsmith administrator to review your access rights. For more information, see the [Permissions and Roles](/administration-and-security/access-control/rbac) page. diff --git a/docs/docs/advanced-use/feature-health.md b/docs/docs/managing-flags/feature-health-metrics.md similarity index 50% rename from docs/docs/advanced-use/feature-health.md rename to docs/docs/managing-flags/feature-health-metrics.md index d83631684de0..a185399866a5 100644 --- a/docs/docs/advanced-use/feature-health.md +++ b/docs/docs/managing-flags/feature-health-metrics.md @@ -1,22 +1,38 @@ --- -title: Feature Health +title: Feature Health Metrics +sidebar_label: Feature Health Metrics +sidebar_position: 4 --- +Feature health enables you to monitor observability metrics within Flagsmith, specifically in relation to your features and environments. When your observability provider sends alert notifications, Flagsmith can mark features (and optionally environments) as **unhealthy**, providing details about the alerts. This assists your team in responding quickly and making informed decisions. + :::info -Feature Health is in Beta. +Feature Health is in Beta, please email support@flagsmith.com or chat with us here if you'd like to join. ::: -Feature Health enables users to monitor observability metrics within Flagsmith, specifically in relation to Flagsmith's Features and Environments. Flagsmith receives alert notifications from your observability provider and, based on this data, marks your Features and optionally Environments with an **Unhealthy** status, providing details about the alerts. This enhances your team's observability, allowing for quicker, more informed decisions. +## Prerequisites + +- You must have a supported observability provider (see below). +- You need admin access to your Flagsmith project settings. + +--- + +## How to enable Feature Health -## Integrations +1. Go to your **Project Settings** in Flagsmith. +2. Navigate to the **Feature Health** section. +3. Choose your desired provider from the **Provider Name** drop-down menu (e.g., Grafana/Prometheus Alertmanager or Sample). +4. Click **Create** and copy the webhook URL. -The following is an overview of the Feature Health providers currently supported. +--- + +## How to integrate with Feature Health providers ### Grafana / Prometheus Alertmanager -[Learn more](/integrations/apm/grafana/#feature-health-provider-setup) about configuring Grafana / Prometheus Alertmanager Feature Health provider. +[Learn more](/third-party-integrations/observability-and-monitoring/grafana) about configuring Grafana / Prometheus Alertmanager feature health provider. ### Sample Provider @@ -24,7 +40,7 @@ We provide a Sample Provider for your custom integrations. To create a Sample Fe 1. Go to Project Settings > Feature Health. 2. Select "Sample" from the Provider Name drop-down menu. -3. Click Create and copy the Webhook URL. +3. Click Create and copy the webhook URL. You can use the webhook in your custom integration. Refer to the payload schema below: @@ -94,3 +110,10 @@ You can use the webhook in your custom integration. Refer to the payload schema } } ``` + +--- + +## What's next? + +- For more on observability integrations, see the [observability integrations](/third-party-integrations/observability-and-monitoring/grafana). +- Need help or want to join the Beta? Contact [support@flagsmith.com](mailto:support@flagsmith.com) or chat with us in-app. diff --git a/docs/docs/managing-flags/flag-analytics.md b/docs/docs/managing-flags/flag-analytics.md new file mode 100644 index 000000000000..2fdb9f785e99 --- /dev/null +++ b/docs/docs/managing-flags/flag-analytics.md @@ -0,0 +1,35 @@ +--- +title: Flag Analytics +sidebar_label: Flag Analytics +sidebar_position: 20 +--- + +## Overview + +Flag analytics allow you to track how often individual flags are evaluated within the Flagsmith SDK. + +To view Analytics for a particular flag, browse to the relevant environment and click on a single flag to edit that flag. + +![Image](/img/flag-analytics.png) + +Flag analytics can be really useful when removing flags from Flagsmith. More often than not, flags can be removed from your codebase and platform once they have been rolled out and everyone is comfortable with them running in production. + +Once you have removed the evaluation code from your code base, its nice to be sure that all references to that flag have been removed, and that removing the flag itself from Flagsmith will not cause any unforeseen issues. Flag analytics help with this. + +Flag analytics can also be helpful when identifying integration issues. Occasionally errors can creep into your code that cause multiple needless evaluations of a flag. Again, these analytics can help isolate these situations. + +## Enabling Flag Analytics? + +:::info + +The Flag Analytics data will be visible in the Dashboard between 30 minutes and 1 hour after it has been collected. + +::: + +Flag analytics are disabled by default in our SDKs. You need to explicitly enable it when you initialize the Flagsmith client. Please refer to the corresponding SDK documentation for more details. For the Javascript family SDKs please refer to [Initialisation options](/integrating-with-flagsmith/sdks/client-side-sdks/javascript#initialisation-options). + +## How does it work? + +Every time a flag is evaluated within the SDK (generally a call to a method like `flagsmith.hasFeature("myCoolFeature")`), the SDK keeps a track of the flag name along with an evaluation count. + +Every `n` seconds (currently set to 10 seconds in the JS SDK) the SDK sends a message to the Flagsmith API with the list of flags that have been evaluated and their count. If no flags have been evaluated in that time window, no message is sent. diff --git a/docs/docs/advanced-use/release-pipelines.md b/docs/docs/managing-flags/release-pipeline.md similarity index 98% rename from docs/docs/advanced-use/release-pipelines.md rename to docs/docs/managing-flags/release-pipeline.md index 6ed7b7969e4c..a3566fb6f3f5 100644 --- a/docs/docs/advanced-use/release-pipelines.md +++ b/docs/docs/managing-flags/release-pipeline.md @@ -1,4 +1,8 @@ -# Release Pipelines +--- +title: Release Pipelines +sidebar_label: Release Pipelines +sidebar_position: 4 +--- :::info @@ -176,5 +180,4 @@ Release Pipelines generate audit log events to track pipeline-related activities You can view these audit events in your project's audit log: 1. Navigate to your project. -2. Go to the **Audit Log** tab. - +2. Go to the **Audit Log** tab. \ No newline at end of file diff --git a/docs/docs/managing-flags/rollout/_category_.json b/docs/docs/managing-flags/rollout/_category_.json new file mode 100644 index 000000000000..53322d2ee977 --- /dev/null +++ b/docs/docs/managing-flags/rollout/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Rollout", + "position": 10 +} \ No newline at end of file diff --git a/docs/docs/managing-flags/rollout/rollout-by-attribute.md b/docs/docs/managing-flags/rollout/rollout-by-attribute.md new file mode 100644 index 000000000000..577c22d634d5 --- /dev/null +++ b/docs/docs/managing-flags/rollout/rollout-by-attribute.md @@ -0,0 +1,58 @@ +--- +title: Rollout by Attribute +sidebar_label: Rollout by Attribute +sidebar_position: 1 +--- + +This guide explains how to enable a feature for specific users based on their attributes (traits) in Flagsmith using segments and segment overrides. Attributes can include user role, subscription plan, application version, or device type. + +## Prerequisites + +- A Flagsmith project and environment. +- A feature flag created in your project. +- Your application's [Flagsmith SDK](/integrating-with-flagsmith/integration-overview) integrated and configured. + +## Steps + +### 1. Identify and Send User Traits + +Ensure your application is identifying users and sending their relevant attributes (traits) to Flagsmith. For example, you might send traits like `plan`, `email`, `platform`, or `version` using your SDK: + +```javascript +flagsmith.identify('user_123'); +flagsmith.setTrait('plan', 'pro'); +flagsmith.setTrait('version', '5.4.1'); +``` + +### 2. Create a Segment Based on Attribute(s) + +1. Go to the **Segments** section in the Flagsmith dashboard. +2. Create a new segment and add rules that match the attribute(s) you want to target. For example: + - `plan = pro` + - `email Contains @yourcompany.com` + - `version SemVer >= 5.4.0` +3. You can combine multiple rules for more precise targeting. + +### 3. Apply a Segment Override to Your Feature Flag + +1. In the environment where you want to apply the rollout, go to the **Features** section and select the feature flag you want to roll out. +2. Navigate to the **Segment Overrides** tab. +- Select the segment you created in the dropdown and set the desired feature flag state or value for users in that segment. +- Save your changes. + +Done! Now you can test with users who match (and don’t match) the segment to ensure the feature is enabled/disabled as expected. + +--- + +## Advanced Use Cases + +- You can combine attribute rules with a **% split** rule for staged rollouts (e.g., only 10% of "pro" users). +- You can use operators such as `In`, `SemVer`, `Modulo`, etc., for more complex targeting. + +--- + +## What's next + +- Read the [segments documentation](/flagsmith-concepts/segments) to understand how they work and their relationship with overrides. +- See the [staged feature rollouts guide](./rollout-by-percentage.md) to combine attribute rules with percentage-based rollouts for gradual releases. +- Understand [managing identities](/flagsmith-concepts/identities) to ensure consistent user identification and attribute management across your application. diff --git a/docs/docs/managing-flags/rollout/rollout-by-percentage.md b/docs/docs/managing-flags/rollout/rollout-by-percentage.md new file mode 100644 index 000000000000..940e865e6aae --- /dev/null +++ b/docs/docs/managing-flags/rollout/rollout-by-percentage.md @@ -0,0 +1,79 @@ +--- +title: Rollout by Percentage +sidebar_label: Rollout by Percentage +sidebar_position: 2 +--- + +**Rollouts by percentage** (also known as staged feature rollouts) allow you to test a new feature with a small subset of your user base. If you are happy with the feature, you can increase the percentage of users that see the feature until it is available to your entire user base. + +This method increases confidence in rolling out a new feature. If issues arise, you can disable the feature flag, thus hiding the feature flag within your application. + +## Prerequisites + +:::important + +Staged rollouts **_only_** come into effect if you are getting the flags for a particular identity. If you are just retrieving the flags for an environment without passing in an identity, your user will never be included in the "% split" segment. + +::: + +Before you begin, make sure you have: + +- A feature flag created in your project. +- Your application's [Flagsmith SDK](/integrating-with-flagsmith/integration-overview) integrated and configured. +- **Identifying users in your application:** You must identify users so that percentage rollouts are evaluated per user. For example: + + ```javascript + flagsmith.identify('user_123'); + ``` + +- (Optional) Any user traits you want to use for more advanced segment rules. + +--- + +## How to Rollout by Percentage + +### 1. Create a segment with a percentage split rule + +- Go to the **Segments** section in the Flagsmith dashboard. +- Create a new segment. +- Add a rule defined with the **% Split** condition. Specify a value between 1 and 100 to define what percentage of your user base is included within this segment. +- You can optionally use the **% Split** rule alongside other segment rules. + +### 2. Connect the Segment to a Feature Flag + +- Go to the **Features** section and select the feature you want to roll out. +- In the environment where you want to apply the rollout, go to the **Segment Overrides** tab. +- Add the segment you created and set the desired feature flag state or value for users in that segment. + +### 3. Save and Monitor + +- Save your changes. +- Monitor the rollout. If all goes well, gradually increase the **% Split** value to roll out the feature to more users over time. +- If issues arise, you can quickly disable the feature flag for all users by removing the override or setting the feature flag to disabled. + +--- + +## How it works + +Each identity and segment has a unique identifier. These two pieces of data are merged, then hashed, and a floating point value between 0.0 and 1.0 is generated from this hash. This value is then evaluated against the "% split" rule. + +### An Example + +For a single identity, the following steps are performed: + +1. Take the internal segment ID and their internal identity ID and combine them into a single string +2. Hash that string +3. Generate a float value between 0 and 1 based on that hash + +For every segment/identity combination, a value between 0 and 1 is generated. Due to the hashing algorithm used, there is a consistent spread of values from 0 to 1. + +- If the number comes out at `0.351` for a particular identity, and you create a segment % split to be 30%, that identity will **not** be included in that segment because `0.351` is greater than `0.3` (30%). +- If you then modify the segment to be a 40% split, the identity **will** be in that segment because `0.4 > 0.351`. That way you get a consistent experience as an end-user. This works because the ID of a segment doesn't change after it has been created. +- A second identity might have their value hash be equal to `0.94`. In that case, they would not be in the segment with the split at either 30% or 40%. + +--- + +## What's next + +- Read the [segments documentation](/flagsmith-concepts/segments) to understand how segments work and how to combine them with percentage splits. +- See how to [roll out features by user attribute](./rollout-by-attribute.md) for targeted releases. diff --git a/docs/docs/managing-flags/scheduled-flags.md b/docs/docs/managing-flags/scheduled-flags.md new file mode 100644 index 000000000000..97b256c16e46 --- /dev/null +++ b/docs/docs/managing-flags/scheduled-flags.md @@ -0,0 +1,55 @@ +--- +title: Scheduled Flags +sidebar_label: Scheduled Flags +sidebar_position: 3 +--- + +Scheduled flags allow you to queue and automatically apply changes to feature flags at a specified future time, eliminating the need for manual intervention at the exact moment of change. This page shows you how to schedule flag changes. + +There are two methods for creating a scheduled feature flag change: + +- As part of a change request. +- Directly while editing a feature flag, if change requests are not enforced. + +## Prerequisites + +- To schedule feature flag changes with a change request, you must [have change requests enabled](/administration-and-security/governance-and-compliance/change-requests) in your environment. This is not necessary to directly schedule changes when editing a flag. +- Scheduled flags are available only for our **Scale-up** and **Enterprise** plans. + +--- + +## How to create a Scheduled Flag change as part of a Change Request + +1. Ensure your environment has change requests enabled. +2. Attempt to change a feature flag value within the environment. You will be prompted to create a new change request. +3. Fill in the following details: + - The title of the change request + - (Optional) A description of the reason for the change request + - The date and time at which you want the feature flag change to take effect +4. Submit the change request. The scheduled change will appear in the change request area as 'Pending'. + +--- + +## How to create a stand-alone scheduled feature flag change + +If change requests are not enabled for your environment, you can schedule a flag change directly: + +1. In the Features list view, go to the feature flag you want to edit. +2. Choose the new value or state for the feature flag. +3. Click the **Schedule Update** button to set the date and time for the change to take effect. +4. Save your changes. The scheduled change will be queued and applied automatically at the specified time. + +--- + +## Scheduled Flags and Change Requests + +Scheduled feature flags awaiting application will be listed in the change request area as 'Pending'. + +Once the scheduled date and time have passed and the feature flag change has been applied, the scheduled feature flag will automatically move to the 'Closed' list in the change request area. + +--- + +## What's next? + +- To learn more about managing and approving changes to your feature flags, see the [change requests](/administration-and-security/governance-and-compliance/change-requests) page. +- To learn how to monitor the performance and health of your feature flags, see the [feature health metrics](./feature-health-metrics.md) page. diff --git a/docs/docs/managing-flags/tagging.md b/docs/docs/managing-flags/tagging.md new file mode 100644 index 000000000000..c106639699da --- /dev/null +++ b/docs/docs/managing-flags/tagging.md @@ -0,0 +1,66 @@ +--- +title: Tagging +sidebar_label: Tagging +sidebar_position: 2 +--- + +You can create tags within Flagsmith and tag feature flags in order to organise them. Tags can also be used to filter the list of feature flags in the event that you have a large number of them. + +This guide explains how to create and apply tags to feature flags within Flagsmith, as well as some tagging good practices and conventions. + +--- + +## Creating and Applying Tags + +You can add tags to feature flags during creation or when editing an existing flag. + +### When Creating a Feature Flag + +1. Go to the **Features** section in your dashboard. +2. Click **Create Feature**. +3. Fill in the feature flag details. In the **Tags** field (optional), enter one or more tags to organise your feature flag. You can also add special tags such as `protected` to prevent accidental deletion. +4. Click **Create Feature** to save your changes. + +### When Editing a Feature Flag + +1. In the **Features** section, select the feature flag you want to edit. +2. Go to the **Settings** tab. +3. In the **Tags** field, add or remove tags as needed. +4. Click **Update Settings** to save your changes. + +You can quickly find and manage feature flags by using the **tag filter** in the Features list to display only feature flags with specific tags. This is especially useful for large projects with many feature flags. + +--- + +## Tag Conventions + +- **General Recommendations:** + - Use clear, descriptive tag names (e.g., `beta`, `deprecated`, `marketing`). + - Establish a naming convention for your team. For example, use all lowercase, hyphen-separated tags. + - Avoid using case-sensitive tags unless necessary. By default, tags are not case-sensitive. + +:::info Protected Tags + +Tags with the following names will prevent users from being able to delete tagged feature flags via the dashboard: + +- `protected`. +- `donotdelete`. +- `permanent`. + +::: + + +--- + +## Best Practices for Tag Management +- Use tags to group related feature flags (e.g., by feature, team, or release). +- Apply protected tags (`protected`, `donotdelete`, `permanent`) to critical or long-lived feature flags to prevent accidental deletion. +- Regularly review and update tags to keep your feature flag management organised. + +--- + +## What's Next + +- Learn how to configure [tag-based permissions for roles](/administration-and-security/access-control/rbac). +- See more advanced [feature flag management](/managing-flags/core-management) techniques. + diff --git a/docs/docs/performance/_category_.json b/docs/docs/performance/_category_.json new file mode 100644 index 000000000000..fef6c7e41ac3 --- /dev/null +++ b/docs/docs/performance/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Performance", + "position": 7, + "collapsed": true +} diff --git a/docs/docs/advanced-use/edge-api.md b/docs/docs/performance/edge-api.md similarity index 52% rename from docs/docs/advanced-use/edge-api.md rename to docs/docs/performance/edge-api.md index f3cdce70d719..85c1056db27b 100644 --- a/docs/docs/advanced-use/edge-api.md +++ b/docs/docs/performance/edge-api.md @@ -1,12 +1,12 @@ -# Edge API +--- +title: Edge API +sidebar_label: Edge API +sidebar_position: 30 +--- -:::info +If you are a Flagsmith SaaS customer, you are using our Edge API by default. -The Edge API is only available with on our SaaS platform. It does not form part of our Open Source project. - -::: - -[The Flagsmith Architecture](/clients#remote-evaluation) is based around a server-side flag engine. This comes with a +[The Flagsmith Architecture](/integrating-with-flagsmith/integration-overview) is based around a server-side flag engine. This comes with a number of benefits, but it can increase latency, especially when the calls are being made from a location that is far from the EU; the location of our current API. It also provides a single point of failure in the event of an AWS region-wide outage. @@ -29,16 +29,14 @@ The Edge API provides API service from the following AWS regions: :::tip -Existing Organisations created before **7th June 2022** have their Projects deployed to the Core API. You can migrate -your Project over to our Edge API by going to the Project Settings page and hitting the "Start Migration" button. The -migration will take between 1 minute and an hour depending on how many Identities your Project has. +Existing organisations created before **7th June 2022** have their projects deployed to the Core API. You can migrate your project over to our Edge API by going to the **Project Settings** page and hitting the **Start Migration** button. The migration will take between 1 minute and an hour depending on how many identities your project has. The Core API will continue to work normally during and following the migration. See the [Migration Steps](#migration-steps) for more info. ::: -Once you have had your Projects migrated to Edge, all you will need to do is point your SDK to a new Flagsmith Edge API +Once you have had your projects migrated to Edge, all you will need to do is point your SDK to a new Flagsmith Edge API URL at `edge.api.flagsmith.com`. This domain points to our Edge CDN. That's it! The easiest way to do this is to upgrade to the latest version of the Flagsmith SDK for your language. @@ -59,62 +57,45 @@ Check the docs for your language SDK on how to override the endpoint URL prefix. ## Migration Steps -The migration process will carry out a one-way sync of your `Identity` data, from the Core API to the Edge API. All your -`Identity` data will continue to exist within the Core API, and you can continue to write `Identities` to the Core API -if you wish. +The migration process will carry out a one-way sync of your `identity` data, from the Core API to the Edge API. All your `identity` data will continue to exist within the Core API, and you can continue to write `identities` to the Core API if you wish. -The goal is to get all of your applications running against the Edge API, where you will benefit from global low latency -as well as multi-region failover and fault tolerance. +The goal is to get all of your applications running against the Edge API, where you will benefit from global low latency as well as multi-region failover and fault tolerance. ### Step 1 - Prepare your applications -Set your applications up to point to the Flagsmith Edge API. This means going from `api.flagsmith.com` to -`edge.api.flagsmith.com`. You can either set this explicitly in our SDK or just ensure you are running the latest -version of the SDK; by default the _latest version_ of the SDKs will point to `edge.api.flagsmith.com`. +Set your applications up to point to the Flagsmith Edge API. This means going from `api.flagsmith.com` to `edge.api.flagsmith.com`. You can either set this explicitly in our SDK or just ensure you are running the latest version of the SDK; by default the _latest version_ of the SDKs will point to `edge.api.flagsmith.com`. ### Step 2 - Migrate your data -You can now trigger a one-way sync of data for each of your Flagsmith Projects within the Flagsmith Dashboard. You can -migrate your Project over to our Edge API by going to the Project Settings page and hitting the "Start Migration" -button. This will start a job that can take between 1 minute and 1 hour, depending on how much data you have. Once the -job is complete, all the Identities that were present in your Core API will be present in the Edge API. +You can now trigger a one-way sync of data for each of your Flagsmith projects within the Flagsmith Dashboard. You can migrate your project over to our edge API by going to the **Project Settings** page and hitting the **Start Migration** button. This will start a job that can take between 1 minute and 1 hour, depending on how much data you have. Once the job is complete, all the identities that were present in your Core API will be present in the Edge API. The Core API will continue to work normally during and following the migration. :::caution -As of Dec 1st, 2023, we will no longer be replicating Identities from the Core API to our Edge API. Please ensure your -applications are fully migrated before this point in time. +As of Dec 1st, 2023, we will no longer be replicating identities from the Core API to our Edge API. Please ensure your applications are fully migrated before this point in time. ::: -If you have a product like a mobile app, where you cannot immediately force your users to upgrade (as opposed to a web -app, for example), you will likely generate Identity writes to the old Core API. +If you have a product like a mobile app, where you cannot immediately force your users to upgrade (as opposed to a web app, for example), you will likely generate identity writes to the old Core API. -Following and during the migration, if we receive a request to an `Identity` endpoint that results in a write to the -core API, we will persist the data in the Core API _and replay the request into the Edge API_. You can then update your -API endpoints/SDKs in your own time to gradually move over the Edge API. This will give you time to migrate your users -over to the new version of your application. +Following and during the migration, if we receive a request to an `identity` endpoint that results in a write to the Core API, we will persist the data in the Core API _and replay the request into the Edge API_. You can then update your API endpoints/SDKs in your own time to gradually move over the Edge API. This will give you time to migrate your users over to the new version of your application. -Note that writes to the Core API will still work into the future, but the data will not be synchronised across the two -platforms (Core and Edge). +Note that writes to the Core API will still work into the future, but the data will not be synchronised across the two platforms (Core and Edge). ### Step 3 - Deploy your applications -Once your data has been copied onto the Edge API datastore, you can now deploy your applications that point to the new -endpoint `edge.api.flagsmith.com` and benefit from global low latency! +Once your data has been copied onto the Edge API datastore, you can now deploy your applications that point to the new endpoint `edge.api.flagsmith.com` and benefit from global low latency! ## Things you should know -### Some of the secure Identity endpoints have changed +### Some of the Secure Identity Endpoints Have Changed -If you are using our REST API to manipulate/update/list your Identities, some of these endpoints have changed. Please -get in touch if you need any help related to this. +If you are using our REST API to manipulate/update/list your identities, some of these endpoints have changed. Please get in touch if you need any help related to this. -### New Identity Overrides will only apply to the Edge API +### New Identity Overrides Will Only Apply to the Edge API -After the migration, any new Identity Overrides you apply to flag values for specific Identities will only be applied to -the Edge API. +After the migration, any new identity overrides you apply to feature flag values for specific identities will only be applied to the Edge API. ### Increment and Decrement endpoints are deprecated @@ -143,7 +124,7 @@ flag.feature_segment ## Architecture -You can see our SaaS architecture [here](/system-administration/architecture.md#saas). +You can see our SaaS architecture [here](/flagsmith-concepts/platform-architecture). ## How It Works @@ -156,7 +137,4 @@ request using a Lambda function running in an AWS data-centre near your client. ### DynamoDB Global Tables -We store state within our API - both related to the Environments for your Projects, but also for the Identities within -those Environments. Our Edge design sees us write this data through to DynamoDB global tables, which are replicated -globally. Our Lambda functions then connect to the nearest DynamoDB table to retrieve both Environment and Identity -data. +We store state within our API - both related to the environments for your projects, but also for the identities within those environments. Our Edge design sees us write this data through to DynamoDB global tables, which are replicated globally. Our Lambda functions then connect to the nearest DynamoDB table to retrieve both environment and identity data. diff --git a/docs/docs/performance/edge-proxy.md b/docs/docs/performance/edge-proxy.md new file mode 100644 index 000000000000..c25159a70f74 --- /dev/null +++ b/docs/docs/performance/edge-proxy.md @@ -0,0 +1,73 @@ +--- +title: Edge Proxy +sidebar_label: Edge Proxy +sidebar_position: 10 +--- + +The Flagsmith Edge Proxy is a service that you host yourself, that allows you to run an instance of the Flagsmith Engine close to your servers. If you are running Flagsmith within a server-side environment and you want to have very low latency flags, you have two options: + +1. Run the Edge Proxy within in your own infrastructure and connect to it from your server-side SDKs +2. Run your server-side SDKs in [Local Evaluation Mode](/integrating-with-flagsmith/integration-overview). + +The main benefit to running the Edge Proxy is that you reduce your polling requests against the Flagsmith API itself. + +The main benefit to running server side SDKs in [Local Evaluation Mode](/integrating-with-flagsmith/integration-overview) is that you get the lowest possible latency. + +## How does it work + +:::info + +The Edge Proxy has the same [caveats as running our SDK in Local Evaluation mode.](/integrating-with-flagsmith/integration-overview). + +::: + +You can think of the Edge Proxy as a copy of our Python Server Side SDK, running in [Local Evaluation Mode](/integrating-with-flagsmith/integration-overview), with an API interface that is compatible with the Flagsmith SDK API. + +The Edge Proxy runs as a lightweight Docker container. It connects to the Flagsmith API (either powered by us at `api.flagsmith.com` or self hosted by you) to get environment flags and segment rules. You can then point the Flagsmith SDKs to your Edge Proxy; it implements all the current SDK endpoints. This means you can serve a very large number of requests close to your infrastructure and users, at very low latency. Check out the [architecture below](#architecture). + +The proxy also acts as a local cache, allowing you to make requests to the proxy without hitting the Core API. + +## Performance + +The edge proxy can currently serve ~2,000 requests per second (RPS) at a mean latency of ~7ms on an M1 MacBook Pro with a simple set of feature flags. Working with more complex environments with many segment rules will bring this RPS number down. + +It is stateless and hence close to perfectly scalable being deployed behind a load balancer. + +## Managing Traits + +There is one caveat with the Edge Proxy. Because it is entirely stateless, it is not able to persist trait data into any sort of datastore. This means that you _have_ to provide the full complement of traits when requesting the flags for a particular identity. Our SDKs all provide relevant methods to achieve this. An example using `curl` would read as follows: + +```bash +curl -X "POST" "http://localhost:8000/api/v1/identities/?identifier=do_it_all_in_one_go_identity" \ + -H 'X-Environment-Key: n9fbf9h3v4fFgH3U3ngWhb' \ + -H 'Content-Type: application/json; charset=utf-8' \ + -d $'{ + "traits": [ + { + "trait_value": 123.5, + "trait_key": "my_trait_key" + }, + { + "trait_value": true, + "trait_key": "my_other_key" + } + ], + "identifier": "do_it_all_in_one_go_identity" +}' +``` + +Note that the Edge Proxy will currently _not_ send the Trait data back to the Core API. + +## Deployment and Configuration + +Please see the [hosting documentation](/deployment-self-hosting/edge-proxy). + +## Architecture + +The standard Flagsmith architecture: + +![Image](/img/edge-proxy-existing.svg) + +With the proxy added to the mix: + +![Image](/img/edge-proxy-proxy.svg) diff --git a/docs/docs/performance/real-time-flags.md b/docs/docs/performance/real-time-flags.md new file mode 100644 index 000000000000..7f667804be1e --- /dev/null +++ b/docs/docs/performance/real-time-flags.md @@ -0,0 +1,101 @@ +--- +title: Real-time flag updates +sidebar_label: Real-time Flags +sidebar_position: 20 +--- + +When an application fetches its current feature flags, it usually caches the flags for a certain amount of time to make [efficient use](/best-practices/efficient-api-usage) of the Flagsmith API and network resources. In some cases, you may want an application to be notified about feature flag updates without needing to repeatedly call the Flagsmith API. This guide explains how to achieve this by subscribing to real-time flag updates. + +## Prerequisites + +- Real-time flag updates require an Enterprise subscription. +- Real-time flag updates are only available on the public SaaS Flagsmith instance. Self-hosted and private cloud Flagsmith installations do not support real-time flag updates. + +## Setup + +To enable real-time flag updates for your Flagsmith project: + +1. Log in to the Flagsmith dashboard as a user with project administrator permissions. +2. Navigate to **Project Settings > SDK Settings**. +3. Enable **Real-time updates**. + +By default, applications using a supported Flagsmith SDK do not subscribe to real-time flag updates. Refer to your SDK's documentation for subscribing to real-time flag updates. + +## How it works + +The following sequence diagram shows how a typical application would use real-time flag updates. [Billable API requests](/administration-and-security/billing-api-usage) are highlighted in yellow. + +```mermaid +sequenceDiagram + rect rgb(255,245,173) + Application->>Flagsmith: Fetch initial flags + Flagsmith->>Application: #nbsp + end + Application->>Flagsmith: Connect to update stream + Flagsmith->>Application: #nbsp + Flagsmith Administrator->>Flagsmith: Update flag state + Flagsmith->>Flagsmith Administrator: #nbsp + Flagsmith-->>Application: Flag update event + rect rgb(255,245,173) + Application->>Flagsmith: Fetch latest flags + Flagsmith->>Application: #nbsp + end + Application-->Application: Store latest update timestamp +``` + +Your application subscribes to real-time flag updates by opening a long-lived [server-sent events (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) connection to Flagsmith, which is specific to its current environment. + +When the environment is updated in some way, either via the Flagsmith dashboard or the [Admin API](/integrating-with-flagsmith/flagsmith-api-overview/admin-api), all clients connected to that environment's real-time stream will receive a message containing the latest update's timestamp. If your application's latest flags are older than the received timestamp, it requests the latest flags from Flagsmith. When your application receives the latest flags, you must propagate the latest flag state throughout your application as necessary. + +## Limitations + +Real-time flag update events only contain a timestamp indicating when any flag in the environment was last updated. Applications must still call the Flagsmith API to get the actual flags for their current environment or user. + +Only changes made to environments or projects result in flag update events. For example, the following operations will cause updates to be sent: + +- Manually toggling a flag on or off, or changing its value. +- A [scheduled Change Request](/managing-flags/scheduled-flags) for a feature goes live. +- Creating or updating segment overrides for a feature. +- Changing a segment definition. + +Identity-level operations _will not_ cause updates to be sent: + +- Updating an identity's traits. +- Creating or updating an identity override. + +The following SDK clients support subscribing to real-time flag updates: + +- JavaScript +- Android +- iOS +- Flutter +- Python +- Ruby + +## Implementation details + +The event source URL used by Flagsmith SDKs is: + +``` +https://realtime.flagsmith.com/sse/environments/ENVIRONMENT_ID/stream +``` + +Each real-time flag event message is a JSON object containing a Unix epoch timestamp of the environment's last update: + +```json +{ + "updated_at": 3133690620000 +} +``` + +You can test real-time flag updates by using cURL to connect to the event source URL: + +``` +curl -H 'Accept: text/event-stream' -N -i https://realtime.flagsmith.com/sse/environments/ENVIRONMENT_ID/stream +``` + +## What's next? + +- [Efficient API Usage](/best-practices/efficient-api-usage) – Tips for reducing API calls and making the most of your integration. +- [Edge Proxy](/performance/edge-proxy) – Run the Flagsmith engine closer to your infrastructure for even lower latency. +- [Integration Approaches](/best-practices/integration-approaches) – Explore different ways to integrate Flagsmith into your applications. diff --git a/docs/docs/platform/_category_.json b/docs/docs/platform/_category_.json deleted file mode 100644 index 2eaa8db38e5e..000000000000 --- a/docs/docs/platform/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Platform", - "position": 90, - "collapsed": true -} diff --git a/docs/docs/platform/releases.md b/docs/docs/platform/releases.md deleted file mode 100644 index d533693f5eae..000000000000 --- a/docs/docs/platform/releases.md +++ /dev/null @@ -1,475 +0,0 @@ -# Releases - -We follow the [SemVer](https://semver.org/) version naming strategy strategy. - -Please follow our [GitHub Releases](https://github.com/Flagsmith/flagsmith/releases) page for detailed change logs. - -:::important - -This page is no longer being updated. Please refer to our -[GitHub Releases](https://github.com/Flagsmith/flagsmith/releases) and -[Changelog](https://github.com/Flagsmith/flagsmith/blob/main/CHANGELOG.md) pages for detailed change logs. - -::: - -## v2.48 - -Released **14 March 2023** - -- Feature name validation -- Show 'last logged in' on users area -- Hide disabled Flags per Environment -- Rename Users > Identities in the nav for consistency -- Various bug fixes - -## v2.47 - -Released **6 March 2023** - -- Upgrade to Python 3.11 - -## v2.46 - -Released **3 March 2023** - -- Add ability to store usage data in Postgres - -## v2.45 - -Released **2 March 2023** - -- Real time flags -- Scheduled Change requests split out from regular Change Requests -- Generic meta-data to entity models - backend implemented - -## v2.44 - -Released **1 March 2023** - -- Bug fixes for multivariate flag values -- Permission tweaks -- Bug fixes soft-delete edge cases - -## v2.43 - -Released **20 February 2023** - -- Improve caching -- Security patches - -## v2.42 - -Released **20 February 2023** - -- Big Audit Log refactor -- Datadog Dashboard Widget -- Add soft delete to core model entities - -## v2.41 - -Released **4 January 2023** - -- Add ability to validate feature names using regex -- Add View identities permission -- Fix security issue creating unauthorised Terraform API keys - -## v2.40 - -Released **20 December 2022** - -- Add 'Manage user groups' permission -- Add setting to allow upper case characters to be used in feature names -- Display traits with a float value correctly in UI - -## v2.39 - -Released **6 December 2022** - -- Add option to add DB read replicas -- Add description field to segment condition -- Expire user cookies after 30 days of inactivity - -## v2.38 - -Released **29 November 2022** - -- Fix missing invite users button - -## v2.37 - -Released **22 November 2022** - -- Add segment support for Terraform integration -- Add ability to rotate personal API tokens -- Performance optimisations for retrieving segments for an identity -- UI improvements for settings - -## v2.36 - -Released **11 November 2022** - -- Add read only view for features -- Performance optimisations for updating identity traits -- Prevent invite links creation in the API -- Make master API keys read only - -## v2.35 - -Released **8 November 2022** - -- Add option to hide invite links -- Add SAML group sync (SaaS / Private cloud only) -- Multivariate feature management fixes -- Add more tag colours - -## v2.34 - -Released **25 October 2022** - -- Add functionality to include a permission group when inviting users via email -- Performance optimisations for update-traits endpoint -- Remove user traits panel from dashboard -- Add ability to invalidate / regenerate invite links -- Performance optimisations for listing segments -- Performance optimisations for /traits/bulk endpoint -- Add expiring cache options for flags and identities endpoints - -## v2.33 - -Released **20 October 2022** - -- Add functionality to support adding users to a default user group on joining an organisation -- Add new index to identities to improve dashboard performance -- IS_SET and IS_NOT_SET segment operators -- Add statsd metrics - -## v2.32 - -Released **6 October 2022** - -- Mixpanel integration payload fix -- Add fix for testing webhooks in UI when webhook has basic authentication -- Add MV options support for Terraform - -## v2.31 - -Released **29 September 2022** - -- Permissions fix for identity feature states view -- Percentage split operator fix for 0 value -- Added modulo operator for segment engine -- create / approve change request permissions - -## v2.30 - -Released **13 September 2022** - -- Add support for managing project features in Terraform -- Feature specific segments -- Add audit log records for Change Requests -- Add setting to prevent flag defaults being set in all environments when creating a feature - -## v2.29 - -Released **8 August 2022** - -- Async processor core engine -- Allow Segment overrides when viewing associated Features for a Segment -- Allow users with CREATE_FEATURE permission to manage multivariate options - -## v2.28 - -Released **8 July 2022** - -- Import/Export data improvements -- Export organisation to AWS S3 -- Show associated Segments in Features -- Add management command to import organisation -- Add manage Segments permissions -- Show Segment Identity Sampling -- Prevent null feature type -- (Enterprise only) Add LDAP username/password -- (Enterprise only) LDAP permission group syncing fixes - -## v2.27 - -Released **1 July 2022** - -- Add ability to export and import data for an organisation -- Allow Environments to prevent clients from setting Traits - -## v2.26 - -Released **22 June 2022** - -- Bug Fixes - -## v2.25 - -Released **17 June 2022** - -- Filter tags -- Added user-managed "Beta Features" -- Prevent users on free plans from setting minimum_change_request_approvals - -## v2.24 - -Released **1 June 2022** - -- Segments overrides can now take Multi-Variate percentages -- RBAC updates -- Allow custom headers in CORS restrictions - -## v2.23 - -Released **17 May 2022** - -- Security Audit Fixes -- Add cache for returning 403 for unknown Environments in header -- Semver and regex validation - -## v2.22 - -Released **25 April 2022** - -- Semantic Version Operators -- Permissions Improvements -- UI improvements - -## v2.21 - -Released **12 April 2022** - -- Dropped Python 3.6 support -- Change Requests - -## v2.20 - -Released **7 April 2022** - -- Dashboard performance improvements -- Segment Identity Sampling - -## v2.19 - -Released **24 March 2022** - -- Dashboard performance improvements -- UI tweaks -- ARM Docker images -- Dynatrace Integration - -## v2.18 - -Released **15 February 2022** - -- Server-side SDK keys Frontend developed -- Improved Segment Rules evaluation -- Multiple Dependency Updates - -## v2.17 - -Released **8 February 2022** - -- Webhook Analytics Integration -- Audit Log Search is now Server-side -- Improvements to Flag Value Text Editor - -## v2.16 - -Released **31 January 2022** - -- Segment Override Permissions Upgrade -- Added lots of 1-click installers (fly.io, render, digital ocean) -- Rudderstack Integration -- New SDK tokens in preparation for SDKv2 - -## v2.15 - -Released **22 January 2022** - -- Added hostnames to InfluxDB -- Better Env Var handling of SSL and `USE_X_FORWARDED_HOST` - -## v2.14 - -Released **4 January 2022** - -- Slack Integration -- Additional Permission Options -- E2E tests moved to TestCafe - -## v2.13 - -Released **8 December 2021** - -- Protected Flags -- Compare Environments -- Force 2fa - -## v2.12 - -Released **2 December 2021** - -- Environment Variable renaming - -## v2.11 - -Released **18 November 2021** - -- Improved Docker Compose -- Heap Integration -- Multiple Dependency Updates - -## v2.10 - -Released **2 November 2021** - -- Elixir SDK -- Archive Flags -- Specify Flag Owners -- Database performance improvements -- Permission improvements - -## v2.9 - -Released **23 August 2021** - -- Archive flags -- Optionally GZIP responses -- Large number of small bug fixes - -## v2.8 - -Released **28 July 2021** - -- SAML Integration (Enterprise version only) - -## v2.7 - -Released **5 May 2021** - -- [Multivariate Flags](https://flagsmith.com/blog/introducing-multivariate-flags/) -- Various minor bug fixes - -## v2.6 - -Released **9 Apr 2021** - -- Combine Feature Flags and Remote Config -- Dark mode -- Formatter for remote config supporting xml, json, toml and yaml -- Fix Segment override incorrect date -- Add sorting to identity feature list -- Enable SDK analytics based on FLAGSMITH_ANALYTICS env var -- Configurable butter-bar -- Show segment descriptions -- New integrations - - Mixpanel - - Heap Analytics - - New Relic - -Breaking changes: - -- Segment overrides use new API - -## v2.5 - -Released **20 Jan 2021** - -- Add invite via link functionality -- More third party integrations - -## v2.4 - -Released **7 Dec 2020** - -This release is the first under our new brand, Flagsmith. - -The rebrand comes with no breaking changes, mainly just a refactor to urls and wording, however we have built several -new features and bug fixes since our last release: - -- If your api is using influx, we now show usage data in a graph format - ![image](https://user-images.githubusercontent.com/8608314/101258233-16227880-3719-11eb-86c0-738c43d2ec0f.png) -- New look and feel -- Ability to bulk enable / disable all segment overrides and user overrides for a flag - ![image](https://user-images.githubusercontent.com/8608314/101258463-87aef680-371a-11eb-9e98-35c976612962.png) -- Sensible page size for the users page -- Improved E2E stability -- Integration page, this page is almost fully managed by remote config. It will allow users t enhance Flagsmith with - your favourite tools. We currently support data dog and will shortly support amplitude. - ![image](https://user-images.githubusercontent.com/8608314/101258436-6ea64580-371a-11eb-8afe-3626eb36bbbe.png) - -The remote config to use this is as follows: - -```json -integration_data -{ - "perEnvironment": false, - "image": "https://xyz", - "fields": [ - { - "key": "base_url", - "label": "Base URL" - }, - { - "key": "api_key", - "label": "API Key" - } - ], - "tags": [ - "logging" - ], - "title": "Data dog", - "description": "Sends events to Data dog for when flags are created, updated and removed. Logs are tagged with the environment they came from e.g. production." -} -``` - -## v2.3 - -Released **9 Nov 2020** - -We've added a bunch of new features and bug fixes. - -- You can now tag flags with user-defined tags. You can use these tags to manage flags and organise them. -- Beta release of both [Data Dog](/integrations/apm/datadog) and [Amplitude](/integrations/analytics/amplitude) - integrations. -- You can now set multiple traits in a single call -- For a given feature, show which Identities have it individually overridden -- When viewing an Identity, show the segments and test whether the identity is a member of each segment - -## v2.2 - -Released **12th June 2020** - -- Flags that are defined with Segment overrides are now based on an Environment level, as opposed to a Project level. So - you can now define Segment overrides differently between Environments -- Redesigned the left hand navigation area -- You can now filter the audit log per flag -- You can jump to a flags audit log from the main features page - -## v2.1 - -Released **28th May 2020** - -- Google OAuth2 integration -- 2 Factor authentication -- Influx DB integration for API call statistics -- The API can now set multiple traits in a single API request -- Fixed an issue where webhooks could miss data in a certain scenarios -- Mainly CSS design Tweaks - -## v2.0 - -Released **19th December 2019** - -- Removed Flagsmith homepage from Front End App -- Added Webhooks - -## v1.9 - -Released **22nd September 2019** - -- Added Segments -- Added Auditing Logs -- Better E2E testing diff --git a/docs/docs/project-and-community/_category_.json b/docs/docs/project-and-community/_category_.json new file mode 100644 index 000000000000..2d379f05b925 --- /dev/null +++ b/docs/docs/project-and-community/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Project & Community", + "position": 11, + "collapsed": true +} diff --git a/docs/docs/platform/contributing.md b/docs/docs/project-and-community/contributing.md similarity index 64% rename from docs/docs/platform/contributing.md rename to docs/docs/project-and-community/contributing.md index 111797e8a746..39bed9a895e5 100644 --- a/docs/docs/platform/contributing.md +++ b/docs/docs/project-and-community/contributing.md @@ -1,13 +1,13 @@ -# Contributing +--- +title: Contributing Guide +sidebar_position: 2 +--- -We're always looking to improve this project! Open source contribution is encouraged so long as they adhere to these -guidelines. +We're always looking to improve this project! Open source contribution is encouraged so long as they adhere to these guidelines. ## Pull Requests -The Flagsmith team will be monitoring for pull requests. When we get one, a member of team will test the work against -our internal uses and sign off on the changes. From here, we'll either merge the pull request or provide feedback -suggesting the next steps. +The Flagsmith team will be monitoring for pull requests. When we get one, a member of team will test the work against our internal uses and sign off on the changes. From here, we'll either merge the pull request or provide feedback suggesting the next steps. ### A couple things to keep in mind @@ -18,8 +18,7 @@ suggesting the next steps. ### Conventional Commits -Please prefix the title of your PRs with one of the following -[Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/#summary) labels. E.g. `feat: My great feature`: +Please prefix the title of your PRs with one of the following [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/#summary) labels. E.g. `feat: My great feature`: - **feat**: A new feature or improvement to an existing feature - **fix**: A bug fix @@ -34,8 +33,7 @@ Please prefix the title of your PRs with one of the following ## Pre-commit -The application uses pre-commit configuration ( `.pre-commit-config.yaml` ) to run `black`, `flake8` and `isort` -formatting before commits. +The application uses pre-commit configuration ( `.pre-commit-config.yaml` ) to run `black`, `flake8` and `isort` formatting before commits. To install pre-commit: @@ -53,8 +51,7 @@ pre-commit run --all-files ## Running Tests -The application uses pytest for writing (appropriate use of fixtures) and running tests. Before running tests please -make sure that `DJANGO_SETTINGS_MODULE` env var is pointing to the right module, e.g. `app.settings.test`. +The application uses pytest for writing (appropriate use of fixtures) and running tests. Before running tests please make sure that `DJANGO_SETTINGS_MODULE` env var is pointing to the right module, e.g. `app.settings.test`. To run tests: @@ -64,5 +61,4 @@ DJANGO_SETTINGS_MODULE=app.settings.test pytest ## Adding Dependencies -We use [Poetry](https://python-poetry.org/) for dependency management - please follow -[their docs on adding dependencies](https://python-poetry.org/docs/basic-usage/#specifying-dependencies). +We use [Poetry](https://python-poetry.org/) for dependency management - please follow [their docs on adding dependencies](https://python-poetry.org/docs/basic-usage/#specifying-dependencies). diff --git a/docs/docs/project-and-community/release-notes.md b/docs/docs/project-and-community/release-notes.md new file mode 100644 index 000000000000..a2868832dc88 --- /dev/null +++ b/docs/docs/project-and-community/release-notes.md @@ -0,0 +1,128 @@ +--- +title: Release Notes +sidebar_position: 3 +--- + +We follow the [SemVer](https://semver.org/) version naming strategy. + +Please follow our [GitHub Releases](https://github.com/Flagsmith/flagsmith/releases) page for detailed change logs. + +:::important + +This page provides an overview of recent releases. For the most up-to-date and detailed information, please refer to our [GitHub Releases](https://github.com/Flagsmith/flagsmith/releases) and [Changelog](https://github.com/Flagsmith/flagsmith/blob/main/CHANGELOG.md) pages. + +::: + +## Recent Releases + +### v2.184.0 (18 June 2025) + +**Features:** +- Add `make` shortcut to run both API and worker +- Add wait for trigger to release pipelines +- Frontend report environment metrics +- Integrate with Sentry feature flag Change Tracking +- Release Pipelines UI + +**Bug Fixes:** +- Deduplicate metrics live features states +- Fix Sentry Change Tracking response handling +- Handle (ignore) unrecognised analytics data +- Handle bad input before checking permission +- Use query from workflow list change request + +### v2.183.0 (10 June 2025) + +**Features:** +- Report environment metrics API + +**Bug Fixes:** +- Disable e2e tests in draft PRs +- Silence warning when receiving analytics for unknown flags + +### v2.182.0 (4 June 2025) + +**Features:** +- Track welcome page tasks and integrations + +**Bug Fixes:** +- Fix Launch Darkly importer rate limit handling + +### v2.181.0 (4 June 2025) + +**Features:** +- Welcome page + +**Bug Fixes:** +- Adjust Flagsmith client URL +- Fix identities endpoint not respecting deleted segment overrides +- Fix Launch Darkly importer rate limit responses + +### v2.180.0 (3 June 2025) + +**Features:** +- Launch Darkly importer: Support large segments import and other improvements + +**Bug Fixes:** +- Hide billing periods if subscription has no periods +- Hide dropdown caret if no text content +- Improve audit logs when postponing scheduled Change Requests +- Raise 404 if org does not have billing periods +- Update prevent signups and invitation documentation + +## Release Schedule + +We typically release new versions every 1-2 weeks. Major releases are planned quarterly and include significant new features or breaking changes. + +### Release Types + +- **Patch releases** (e.g., 2.184.1): Bug fixes and minor improvements +- **Minor releases** (e.g., 2.185.0): New features and improvements +- **Major releases** (e.g., 3.0.0): Breaking changes and major new features + +## Breaking Changes + +When we introduce breaking changes, we'll: + +1. Announce them in advance through our blog and GitHub issues +2. Provide migration guides +3. Maintain backward compatibility where possible +4. Give plenty of notice before removing deprecated features + +## Release Process + +1. **Development**: Features are developed in feature branches +2. **Testing**: All changes go through automated and manual testing +3. **Review**: Code is reviewed by the team +4. **Release**: Releases are tagged and published to GitHub +5. **Documentation**: Release notes are updated and documentation is revised + +## Getting Notified + +- **GitHub**: Watch the [Flagsmith repository](https://github.com/Flagsmith/flagsmith) for release notifications +- **Discord**: Join our [Discord community](https://discord.gg/hFhxNtXzgm) for announcements +- **Blog**: Follow our [blog](https://flagsmith.com/blog/) for major release announcements +- **Email**: Sign up for our newsletter for important updates + +## Previous Releases + +For a complete history of all releases, please see our [GitHub Releases page](https://github.com/Flagsmith/flagsmith/releases) or the [full changelog](https://github.com/Flagsmith/flagsmith/blob/main/CHANGELOG.md). + +### Notable Past Releases + +- **v2.48** (14 March 2023): Feature name validation, user improvements +- **v2.47** (6 March 2023): Upgrade to Python 3.11 +- **v2.46** (3 March 2023): Add ability to store usage data in Postgres +- **v2.45** (2 March 2023): Real-time flags, scheduled change requests +- **v2.44** (1 March 2023): Bug fixes for multivariate flag values + +## Support + +If you encounter issues with a specific release: + +1. Check the [GitHub issues](https://github.com/Flagsmith/flagsmith/issues) for known problems +2. Search our [Discord community](https://discord.gg/hFhxNtXzgm) for solutions +3. Open a new issue if the problem hasn't been reported +4. Consider downgrading to a previous stable release if needed + +We're committed to maintaining stable releases and providing timely bug fixes. Thank you for using Flagsmith! 🚀 diff --git a/docs/docs/platform/roadmap.md b/docs/docs/project-and-community/roadmap.md similarity index 64% rename from docs/docs/platform/roadmap.md rename to docs/docs/project-and-community/roadmap.md index 5c8c69d605a9..a1dcfc060c06 100644 --- a/docs/docs/platform/roadmap.md +++ b/docs/docs/project-and-community/roadmap.md @@ -1,3 +1,6 @@ -# Roadmap +--- +title: Public Roadmap +sidebar_position: 4 +--- You can view our public roadmap on [GitHub](https://github.com/orgs/Flagsmith/projects/7). diff --git a/docs/docs/support/index.mdx b/docs/docs/support/index.mdx deleted file mode 100644 index 7d6d322a709d..000000000000 --- a/docs/docs/support/index.mdx +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: Help and Support -sidebar_position: 130 -sidebar_label: Help and Support ---- - -We are here to help! You can contact us for support through any of these channels: - -* Click the chat bubble (💬) at the bottom right of this page, or from the Flagsmith dashboard. -* Email [support@flagsmith.com](mailto:support@flagsmith.com). - -## Enterprise support - -[Flagsmith Enterprise](https://www.flagsmith.com/pricing) customers can also use these support channels: - -* Dedicated Customer Success manager for personalised assistance and training. -* Shared Slack channel for real-time group support between your organisation and the Flagsmith team. - -## Bug reports and pull requests - -Flagsmith is open source, and we encourage contributions from the community. If you want to submit a specific bug -report or code change, you can open an issue or pull request directly in the relevant GitHub repository: - -* [Flagsmith API, dashboard and documentation](https://github.com/Flagsmith/flagsmith) -* Client-side SDKs: - - [JavaScript, React](https://github.com/Flagsmith/flagsmith-js-client) - - [iOS](https://github.com/Flagsmith/flagsmith-ios-client) - - [Android](https://github.com/Flagsmith/flagsmith-kotlin-android-client) - - [Flutter](https://github.com/flagsmith/flagsmith-flutter-client) -* [Server-side SDKs](/clients/server-side) -* [Edge Proxy](https://github.com/Flagsmith/edge-proxy) -* [Terraform provider](https://github.com/Flagsmith/terraform-provider-flagsmith) -* [Flagsmith CLI](https://github.com/Flagsmith/flagsmith-cli) -* [Kubernetes Helm charts](https://github.com/Flagsmith/flagsmith-charts) diff --git a/docs/docs/system-administration/_category_.json b/docs/docs/system-administration/_category_.json deleted file mode 100644 index 5cbcb4e9b35c..000000000000 --- a/docs/docs/system-administration/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "System Administration", - "position": 70, - "collapsed": true -} diff --git a/docs/docs/system-administration/api-usage.md b/docs/docs/system-administration/api-usage.md deleted file mode 100644 index 27ea4d53559f..000000000000 --- a/docs/docs/system-administration/api-usage.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: API Usage ---- - -Flagsmith will track API calls made by the SDKS and store them in its data-store. You can view this data by going to the -Organisation settings page and clicking on **Usage**. You can also drill down into Projects and Environments. Flagsmith -tracks the following request types: - -1. Get Flags -2. Get Identity Flags -3. Set Identity Traits -4. Get [Environment Document](/clients#the-environment-document) (for local evaluation SDKs) - -![API Usage](/img/api-usage.png) - -## Flag Usage - -Flagsmith also tracks Flag Evaluations with [Flag Analytics](/advanced-use/flag-analytics). diff --git a/docs/docs/system-administration/architecture.md b/docs/docs/system-administration/architecture.md deleted file mode 100644 index 49008f81b8a1..000000000000 --- a/docs/docs/system-administration/architecture.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: Architecture ---- - -## Self Hosted / On Prem - -![Image](/img/architecture.svg) - -## SaaS - -![Image](/img/saas-architecture.svg) diff --git a/docs/docs/system-administration/authentication/01-SAML/_category_.json b/docs/docs/system-administration/authentication/01-SAML/_category_.json deleted file mode 100644 index 9986a71b67bf..000000000000 --- a/docs/docs/system-administration/authentication/01-SAML/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "SAML", - "collapsed": true -} diff --git a/docs/docs/system-administration/authentication/01-SAML/index.mdx b/docs/docs/system-administration/authentication/01-SAML/index.mdx deleted file mode 100644 index ccd658356f97..000000000000 --- a/docs/docs/system-administration/authentication/01-SAML/index.mdx +++ /dev/null @@ -1,194 +0,0 @@ ---- -title: SAML single sign-on (SSO) ---- - -:::info - -SAML single sign-on requires an [Enterprise subscription](https://flagsmith.com/pricing). - -::: - -SAML (Security Assertion Markup Language) is a standard authentication and authorisation system. You can connect -your existing identity provider to Flagsmith using SAML 2.0, which lets your users log in to your Flagsmith organisation -using their existing credentials. - -SAML single sign-on also lets Flagsmith use the existing user groups from your identity provider. This lets you assign -permissions to your Flagsmith users directly from their identity provider groups, and not have to manage group -membership from Flagsmith. - -## Prerequisites and limitations - -Make sure your Flagsmith organisation has an active Enterprise licence. - -If you have preexisting Flagsmith users that use other authentication methods such as email and password, GitHub or -Google, and you later set up SSO for these users, Flagsmith will see them as the same user as long as their email is -the same (case-insensitive). All Flagsmith data including permissions are preserved as long as the email addresses match -between SSO and non-SSO users. - -Users that log in using SSO can only belong to one Flagsmith organisation. If a user tries to log in with SSO, and they -belong to more than one Flagsmith organisation, they will not be able to log in using SSO until they are removed from -all other organisations. These users can still log in using a non-SSO method and remove themselves from the other -organisations if they choose. - -If you are self-hosting Flagsmith, TLS is required. Encrypted SAML assertions are not supported. - -## Setup - -:::tip - -If your identity provider is Okta, use the [Flagsmith Okta application](/system-administration/authentication/Okta) -instead of following these steps. - -::: - -This is an overview of the steps required to configure SAML SSO: - -1. Create a Flagsmith SAML configuration. -2. Add your identity provider's SAML metadata to this Flagsmith SAML configuration. -3. Add your Flagsmith SAML configuration's service provider metadata to your identity provider. -4. Optional: Add external IDs to your Flagsmith groups to map your identity provider groups to Flagsmith groups. - -You can manage your Flagsmith SAML configurations from the Flagsmith dashboard. Click on your Flagsmith organisation -name in the top left, then go to **Organisation Settings** > **SAML**. - -You can create multiple SAML configurations if you have multiple identity providers. In most cases, you will only -need one. - -When creating a SAML configuration, the following options are available: - -**Name:** (**Required**) A unique, URL-friendly name for the SAML configuration. This name must be unique across all -Flagsmith organisations and forms part of the URL that your identity provider will post SAML messages to during -authentication. Users must type this name when clicking "Single Sign-On" at the login screen. It cannot be changed after -the SAML configuration is created. - -**Allow IdP-initiated**: If enabled, users will be able to log in directly from your identity provider without needing -to visit the Flagsmith login page. - -**IdP metadata XML**: The SAML metadata from your identity provider. This typically includes information such as your -identity provider's public key for SAML assertions. If you do not have this metadata yet, you can create the SAML -configuration without it and come back to this step later. - -Once your Flagsmith SAML configuration is created, you can download its SAML metadata by clicking "Download Service -Provider Metadata". Add this file to your identity provider to establish a trust relationship between it and Flagsmith. - -
- - Additional options when self-hosting Flagsmith - - **Frontend URL** should point to the base URL of the Flagsmith dashboard. It is automatically prefilled with the - URL of the dashboard you are currently using. You only need to change this if your users will access the Flagsmith - dashboard using a different URL than the one you are currently using—for example, if you are connecting to Flagsmith - via port forwarding or a VPN that your users do not typically use. - -
- -### Assertion consumer service URL - -Each Flagsmith SAML configuration has its own Assertion Consumer Service (ACS) URL, also known as single sign-on URL. -Your identity provider will post SAML messages to this URL when a user logs in using SSO. The ACS URL for any -SAML configuration is as follows, replacing `flagsmith.example.com` with your Flagsmith API's domain: - -``` -https://flagsmith.example.com/api/v1/auth/saml/YOUR_SAML_CONFIGURATION_NAME/response/ -``` - -## Attribute mapping - -Flagsmith will look for the following SAML attributes, in order, to uniquely identify a SAML user: - -- `subject-id` -- `uid` -- `NameID` - -Flagsmith also maps user attributes from the following claims in the SAML assertion: - -| Flagsmith attribute | Identity provider claim names | -|---------------------|------------------------------------------------------| -| Email | `mail`, `email` or `emailAddress` | -| First name | `gn`, `givenName` or the first part of `displayName` | -| Last name | `sn`, `surname` or the second part of `displayName` | -| Groups | `groups` | - -To add custom attribute mappings, edit your SAML configuration and open the **Attribute Mappings** tab. For example, -this mapping tells Flagsmith to look for user groups in a claim other than the default `groups` claim: - -
- -## Permissions for SAML users - -By default, users logging in via SAML will have no permissions to view or modify anything in the Flagsmith dashboard. -You can customise this by creating a [group](/system-administration/rbac) with the "Add new users by default" option -enabled, and assigning your desired default permissions to that group. - -### Using groups from your identity provider - -Flagsmith can add or remove a user from groups based on your identity provider's SAML response when logging in. - -When a user logs in, Flagsmith will make them a member of all the groups listed in the `groups` claim from your identity -provider's SAML assertion. Each value of the `groups` claim should correspond to the "External ID" of a Flagsmith group: - -
- -For example, a SAML assertion with the following `groups` claim would assign the user to the Flagsmith groups with -external IDs of `my_group` and `my_other_group`: - -```xml - - - my_group - - - my_other_group - - -``` - -By default, this claim must be named exactly `groups`. Some identity providers such as Microsoft Entra ID add a -namespace to their claims such as `http://schemas.microsoft.com/ws/2008/06/identity/claims/groups`. If this is the case, -or your groups claim has a different name, you must tell Flagsmith which claim it should look at by [creating an -attribute mapping](#attribute-mapping). - -## Force users to log in with SSO - -Once you have confirmed your SAML configuration is working, you can prevent users logging in using other authentication -methods with any of these options: - -* [Restrict authentication methods per email domain](/system-administration/authentication#domain-auth). -* If you have a private Flagsmith instance, -[disable password authentication](/system-administration/authentication#disable-password). - -## Always use a specific SAML configuration - -When a user clicks on "Single Sign-On" at the Flagsmith login screen, they will be prompted to enter the name of a -SAML configuration. If you are self-hosting Flagsmith, you can skip this step and always use a specific SAML -configuration by setting up the `sso_idp` -[Flagsmith-on-Flagsmith](https://docs.flagsmith.com/deployment#running-flagsmith-on-flagsmith) flag. The text value of -this flag should be the name of the SAML configuration to use. - -If you are using Flagsmith private cloud, [contact Flagsmith support](/support) once you have created your SAML -configuration and validated it works correctly. - -## Canonicalization methods - -Some identity providers require the service provider to support canonicalization methods that are not allowed by -default. You can see the methods that are enabled by default -[here](https://github.com/IdentityPython/pysaml2/blob/88feeba03c2f891a31a86cbb24b210070aab1fdc/src/saml2/xmldsig/__init__.py#L67-L70). - -You can enable additional canonicalization methods by setting the `EXTRA_ALLOWED_CANONICALIZATIONS` environment variable -to a comma-separated list of canonicalization method URIs. For example: - -```sh -EXTRA_ALLOWED_CANONICALIZATIONS=http://www.w3.org/TR/2001/REC-xml-c14n-20010315#,http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments -``` - -## Force SSL after authentication - -You can configure Flagsmith to ignore the `X-Forwarded-Proto` HTTP header and always use HTTPS for the ACS URL by -setting the `SAML_FORCE_SSL` environment variable to `True`. - -## Troubleshooting - -If you need to [contact Flagsmith support](/support) or an administrator for help with SSO logins, the best way is to -record and share a -[HAR file](https://support.zendesk.com/hc/en-us/articles/4408828867098-Generating-a-HAR-file-for-troubleshooting) -from your web browser where you try to log in to Flagsmith using your SAML identity provider. diff --git a/docs/docs/system-administration/authentication/02-Okta.md b/docs/docs/system-administration/authentication/02-Okta.md deleted file mode 100644 index 4500b9e740f8..000000000000 --- a/docs/docs/system-administration/authentication/02-Okta.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: Okta ---- - -Flagsmith can integrate with Okta single sign-on (SSO) by using SAML. We provide a first-party Okta integration to -simplify the setup. - -## Setup - -1. Create a [Flagsmith SAML configuration](/system-administration/authentication/SAML/#setup). You can leave the - identity provider metadata blank for now. -2. Add the [Flagsmith Okta integration](https://www.okta.com/integrations/flagsmith/) to your Okta account, and open - it in the Okta dashboard. -3. Select the "Sign On" tab, and click "Edit". -4. Under "Advanced Sign-on Settings", fill out these fields and then click Save: - - **API Base URL** should be `https://api.flagsmith.com` on SaaS, or your API root URL otherwise. - - **SAML Organisation** should be the name of the SAML configuration you previously created. -5. Staying on the "Sign On" tab, find the "Metadata URL" in the "Sign on methods" section. Save this metadata to a file - and upload it to the "IdP Metadata XML" of your Flagsmith SAML configuration. - -Once your Flagsmith SAML configuration has your Okta IdP metadata set, your users can log in to Flagsmith with Okta by -clicking "Single Sign-On" at the login page, and typing the name of the SAML configuration you created. - -## User attributes - -By default, the Flagsmith Okta integration will map your users' Okta email address, given name and surname so that they -are visible within Flagsmith. If you need to map different attributes, you can -[customise the attribute mappings](/system-administration/authentication/SAML/#attribute-mapping) on your SAML -configuration. diff --git a/docs/docs/system-administration/authentication/03-OAuth.md b/docs/docs/system-administration/authentication/03-OAuth.md deleted file mode 100644 index 897cc5c7874b..000000000000 --- a/docs/docs/system-administration/authentication/03-OAuth.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: OAuth ---- - -### Google - -To configure OAuth for Google: - -- [Setting up OAuth 2.0](https://support.google.com/cloud/answer/6158849?hl=en) -- Create the Flagsmith on Flagsmith flag as it shows [here](/deployment#oauth_google). - -### GitHub - -As a pre-requisite for this configuration make sure to have -[Flagsmith on Flagsmith](/deployment#running-flagsmith-on-flagsmith) set up. - -Configure the following environment variables: - -- `GITHUB_CLIENT_ID` -- `GITHUB_CLIENT_SECRET` - -To configure OAuth for GitHub: - -- [Create an OAuth GitHub application](https://docs.github.com/en/developers/apps/building-oauth-apps/creating-an-oauth-app) -- For the Authorization callback URL use: `https:///oauth/github` -- Create the Flagsmith on Flagsmith flag as it shows [here](/deployment#oauth_github). - -Now you would be able to see the GitHub SSO option. - -
diff --git a/docs/docs/system-administration/authentication/_category_.json b/docs/docs/system-administration/authentication/_category_.json deleted file mode 100644 index c912fef87610..000000000000 --- a/docs/docs/system-administration/authentication/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Authentication", - "collapsed": true -} diff --git a/docs/docs/system-administration/importing-and-exporting/_category_.json b/docs/docs/system-administration/importing-and-exporting/_category_.json deleted file mode 100644 index c627267d3401..000000000000 --- a/docs/docs/system-administration/importing-and-exporting/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Importing and Exporting", - "position": 70, - "collapsed": true -} diff --git a/docs/docs/system-administration/importing-and-exporting/features.md b/docs/docs/system-administration/importing-and-exporting/features.md deleted file mode 100644 index 7769b77aa806..000000000000 --- a/docs/docs/system-administration/importing-and-exporting/features.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -title: Features -sidebar_position: 110 ---- - -The import and export of feature data associated with a given environment is possible on Flagsmith. The feature data -that's exported includes multivariate features, but does not include other data that's associated with tags, owners, -group owners, etc. It's useful for transferring features between any running instances of Flagsmith, even when only a -subset of features (e.g., importing features of a given tag) are needed. - -## What is exported? - -We **will** export the following data: - -- Flags -- Flag States (both boolean and text values) -- Multivariate values and weights - -We **will not** export the following data: - -- Feature-based Segments -- Segment overrides -- Flag [custom fields](/advanced-use/custom-fields.md) -- Flag Schedules -- Tags associated with Flags -- Individual and group owners associated with Flags - -## Exporting - -On the Export tab of the project settings page it is possible to export the project's features using the environment's -values. Simply select the source environment from the drop down list and, if required, select one or more feature tags -to filter the features. - -By tapping the Export Features button the feature export should quickly process and a list of processed features exports -is available at the bottom of the page. - -:::tip - -Feature exports are available for only two weeks, so if an export is needed for a longer period of time be sure to make -a local backup. - -::: - -## Importing - -On the Import tab of the project settings page you'll find the feature import functionality complete with file upload at -the bottom of the page. - -:::caution - -The target environment is the environment to inherit the features of the exported environment. All other environments -will be set to the values defined when the feature was initially created. Use with caution, especially when using with -the Overwrite Destructive merge strategy. - -::: - -### Merge Strategy - -Since a feature may have an identical name it's important to carefully select a merge strategy during a feature import. - -The first option is the Skip strategy which allows an import to process a feature export and at any time a feature has a -pre-existing feature present it skips the import for that given feature. For example, if a user is importing ten -features but two of them were already there, only eight features will be added to the project. This strategy is best for -organisations that want to hold onto their existing data as close as possible. - -The second option is the Overwrite Destructive strategy. In contrast to the Skip strategy the Overwrite Destructive -method clobbers over your existing features and it's important to remember that this is across all environments. This -strategy is most useful only when every feature that was included in the export was vetted to be authoritative in the -target project. diff --git a/docs/docs/system-administration/importing-and-exporting/index.md b/docs/docs/system-administration/importing-and-exporting/index.md deleted file mode 100644 index b947da3457f4..000000000000 --- a/docs/docs/system-administration/importing-and-exporting/index.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Importing and exporting ---- - -You can import and export your data between Flagsmith organisations, instances, or other feature flagging services. - -We recommend understanding the [Flagsmith data model](/basic-features/#flagsmith-model) before importing or -exporting any data. - -## Flagsmith import/export - -There are several ways of importing or exporting Flagsmith data: - -* [Feature import/export](/system-administration/importing-and-exporting/features). Copy all or a subset of features - between Flagsmith projects. -* [Organisation import/export](/system-administration/importing-and-exporting/organisations). Export an entire - Flagsmith organisation and copy it to a different Flagsmith instance. -* Use the [Admin API](/clients/rest#private-admin-api-endpoints) to manually manage your Flagsmith data. - -## Import from third-party services - -You can [import LaunchDarkly flags and segments](launchdarkly) into Flagsmith. diff --git a/docs/docs/system-administration/webhooks.md b/docs/docs/system-administration/webhooks.md deleted file mode 100644 index 9c2808a9a146..000000000000 --- a/docs/docs/system-administration/webhooks.md +++ /dev/null @@ -1,132 +0,0 @@ ---- -title: Web Hooks ---- - -## Audit Log Web Hooks - -You can use Audit Log Web Hooks to stream your Organisation's Audit Log into your own infrastructure. This can be useful -for compliance or to reference against local CI/CD infrastructure. - -```json -{ - "created_date": "2020-02-23T17:30:57.006318Z", - "log": "New Flag / Remote Config created: my_feature", - "author": { - "id": 3, - "email": "user@domain.com", - "first_name": "Kyle", - "last_name": "Johnson" - }, - "environment": null, - "project": { - "id": 6, - "name": "Project name", - "organisation": 1 - }, - "related_object_id": 6, - "related_object_type": "FEATURE" -} -``` - -## Environment Web Hooks - -You can use the Web Hooks to send events from Flagsmith into your own infrastructure. Web Hooks are managed at an -Environment level, and can be configured in the Environment settings page. - -Currently the following events will generate a Web Hook action: - -- Creating Features (Sent as event_type `FLAG_UPDATED`) -- Updating Feature value / state in an Environment (Sent as event_type `FLAG_UPDATED`) -- Overriding a Feature for an Identity (Sent as event_type `FLAG_UPDATED`) -- Overriding a Feature for a Segment (Sent as event_type `FLAG_UPDATED`) - -You can define any number of Web Hook endpoints per Environment. Web Hooks can be managed from the Environment settings -page. - -A typical use case for Web Hooks is if you want to cache flag state locally within your server environment. - -Each event generates an HTTP POST with the following body payload to each of the Web Hooks defined within that -Environment: - -```json -{ - "data": { - "changed_by": "user@domain.com"(or the name of the Organisation API Key), - "new_state": { - "enabled": true, - "environment": { - "id": 23, - "name": "Development" - }, - "feature": { - "created_date": "2021-02-10T20:03:43.348556Z", - "default_enabled": false, - "description": "Show html in a butter bar for certain users", - "id": 7168, - "initial_value": null, - "name": "butter_bar", - "project": { - "id": 12, - "name": "Flagsmith Website" - }, - "type": "CONFIG" - }, - "feature_segment": null, - "feature_state_value": "\nYou are using the develop environment.\n", - "identity": null, - "identity_identifier": null - }, - "previous_state": { - "enabled": false, - "environment": { - "id": 23, - "name": "Development" - }, - "feature": { - "created_date": "2021-02-10T20:03:43.348556Z", - "default_enabled": false, - "description": "Show html in a butter bar for certain users", - "id": 7168, - "initial_value": null, - "name": "butter_bar", - "project": { - "id": 12, - "name": "Flagsmith Website" - }, - "type": "CONFIG" - }, - "feature_segment": null, - "feature_state_value": "\nYou are using the develop environment.\n", - "identity": null, - "identity_identifier": null - }, - "timestamp": "2021-06-18T07:50:26.595298Z" - }, - "event_type": "FLAG_UPDATED" -} -``` - -## Web Hook Signature - -When your Web Hook secret is set, Flagsmith uses it to create a hash signature with each payload. This hash signature is -passed with each request under the X-Flagsmith-Signature header that you need to validate at your end - -### Validating Signature - -Compute an HMAC with the SHA256 hash function. Use request body (raw utf-8 encoded string) as the message and secret -(utf8 encoded) as the Key. Here is one example in Python: - -```python -import hmac - -secret = "my shared secret" - -expected_signature = hmac.new( - key=secret.encode(), - msg=request_body, - digestmod=hashlib.sha256, -).hexdigest() - -received_signature = request["headers"]["x-flagsmith-signature"] -hmac.compare_digest(expected_signature, received_signature) is True -``` diff --git a/docs/docs/third-party-integrations/_category_.json b/docs/docs/third-party-integrations/_category_.json new file mode 100644 index 000000000000..78ebbaf855a8 --- /dev/null +++ b/docs/docs/third-party-integrations/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Third-Party Integrations", + "position": 9 +} \ No newline at end of file diff --git a/docs/docs/integrations/analytics/_category_.json b/docs/docs/third-party-integrations/analytics/_category_.json similarity index 64% rename from docs/docs/integrations/analytics/_category_.json rename to docs/docs/third-party-integrations/analytics/_category_.json index 99ec7bb33e1e..2a744145d537 100644 --- a/docs/docs/integrations/analytics/_category_.json +++ b/docs/docs/third-party-integrations/analytics/_category_.json @@ -1,5 +1,4 @@ { "label": "Analytics", - "collapsed": false, "position": 10 -} +} \ No newline at end of file diff --git a/docs/docs/integrations/analytics/adobe.md b/docs/docs/third-party-integrations/analytics/adobe.md similarity index 86% rename from docs/docs/integrations/analytics/adobe.md rename to docs/docs/third-party-integrations/analytics/adobe.md index 4a4f3c458490..aaac35aa9e86 100644 --- a/docs/docs/integrations/analytics/adobe.md +++ b/docs/docs/third-party-integrations/analytics/adobe.md @@ -9,4 +9,4 @@ hide_title: true Flagsmith does not currently support integrating with Adobe Analytics, but we are eager to build this integration based on your requirements. Contact -us if you are interested in this or any other integrations. +us if you are interested in this or any other integrations. \ No newline at end of file diff --git a/docs/docs/integrations/analytics/amplitude.md b/docs/docs/third-party-integrations/analytics/amplitude.md similarity index 83% rename from docs/docs/integrations/analytics/amplitude.md rename to docs/docs/third-party-integrations/analytics/amplitude.md index 023e943efa04..6eabbe5a4003 100644 --- a/docs/docs/integrations/analytics/amplitude.md +++ b/docs/docs/third-party-integrations/analytics/amplitude.md @@ -6,7 +6,8 @@ hide_title: true ![Amplitude](/img/integrations/amplitude/amplitude-logo.svg) -You can integrate Flagsmith with [Amplitude](https://www.flagsmith.com/blog/feature-flag-analytics-for-users-of-flagsmith-and-amplitude) and automate AB tests and phased rollouts, gaining more insight into your product analytics. The process is as follows: +You can integrate Flagsmith with Amplitude. You can automate AB tests by connecting the Flagsmith platform with +Amplitude. The process is as follows: ## Integration Setup @@ -29,8 +30,7 @@ For flags that contain remote config values, Flagsmith will pass the value of th ::: -Identity flag values are passed into Amplitude. If we make the call to the Flagsmith API to get the flags for an -Identity: +Identity flag values are passed into Amplitude. If we make the call to the Flagsmith API to get the flags for an identity: ```bash curl 'https://edge.api.flagsmith.com/api/v1/identities/?identifier=development_user_123456' \ @@ -49,5 +49,4 @@ saw. This means you can run AB tests driven by Flagsmith segments, and have the ## Integration Notes -You have to identify users on both platforms in the same way. The Flagsmith `Identity ID` must be the same as the -Amplitude `user_id`. +You have to identify users on both platforms in the same way. The Flagsmith `Identity ID` must be the same as the Amplitude `user_id`. \ No newline at end of file diff --git a/docs/docs/integrations/analytics/heap.md b/docs/docs/third-party-integrations/analytics/heap.md similarity index 85% rename from docs/docs/integrations/analytics/heap.md rename to docs/docs/third-party-integrations/analytics/heap.md index 4d3203786d56..f500e698d608 100644 --- a/docs/docs/integrations/analytics/heap.md +++ b/docs/docs/third-party-integrations/analytics/heap.md @@ -1,6 +1,6 @@ --- title: Heap Analytics Integration -sidebar_label: Heap Analytics +sidebar_label: Heap hide_title: true --- @@ -27,7 +27,7 @@ If the flag has no remote config value, Flagsmith will just pass the boolean sta ::: -Identity flag values are passed into Heap. If we make the call to the Flagsmith API to get the flags for an Identity. +Identity flag values are passed into Heap. If we make the call to the Flagsmith API to get the flags for an identity. ```bash curl 'https://edge.api.flagsmith.com/api/v1/identities/?identifier=development_user_123456' \ @@ -60,19 +60,16 @@ In Heap, go to Definitions > New Definition > New Event. Set up your event simil ### Step 2 - Create a Segment based on this new Custom Event Property -In Heap, go to Definitions > New Definition > New Segment. Set up your Segment similar to the below: +In Heap, go to Definitions > New Definition > New Segment. Set up your segment similar to the below: ![Heap Analytics Step 2](/img/integrations/heap/heap-mv-step-2.png) ### Step 3 - Create your Report -Once you have your Segment created, based on a Flagsmith flag value, you can use that Segment in reports within Heap. -Here's an example of us seeing what the conversion rate of a dark mode vs non-dark mode user looks like. Notice the -"Group Analysis - Conversion Rate" at the bottom of the page. +Once you have your segment created, based on a Flagsmith feature flag value, you can use that segment in reports within Heap. Here's an example of us seeing what the conversion rate of a dark mode vs non-dark mode user looks like. Notice the "Group Analysis - Conversion Rate" at the bottom of the page. ![Heap Analytics Step 3](/img/integrations/heap/heap-mv-step-3.png) ## Integration Notes -You have to identify users on both platforms in the same way. The Flagsmith `Identity ID` must be the same as the Heap -`identity`. +You have to identify users on both platforms in the same way. The Flagsmith `Identity ID` must be the same as the Heap `identity`. \ No newline at end of file diff --git a/docs/docs/integrations/analytics/mixpanel.md b/docs/docs/third-party-integrations/analytics/mixpanel.md similarity index 82% rename from docs/docs/integrations/analytics/mixpanel.md rename to docs/docs/third-party-integrations/analytics/mixpanel.md index f705dc12f1ee..d9945077d7e4 100644 --- a/docs/docs/integrations/analytics/mixpanel.md +++ b/docs/docs/third-party-integrations/analytics/mixpanel.md @@ -15,9 +15,7 @@ Mixpanel for cohort analysis, A/B testing and more. The process is as follows: 1. Get the Mixpanel Project Environment ID for your Mixpanel project from the Mixpanel Manage Project page (Project Settings > Project Token) 2. Add the Project Token into Flagsmith (Integrations > Add Mixpanel Integration) -3. All API calls generated by the Flagsmith SDK to the `Get Identity Flags` endpoint will send the a full set of flag - evaluations for that particular user to Mixpanel as a - [`User Profile`](https://developer.mixpanel.com/reference/user-profiles) +3. All API calls generated by the Flagsmith SDK to the `Get Identity Flags` endpoint will send the a full set of flag evaluations for that particular user to Mixpanel as a [`User Profile`](https://developer.mixpanel.com/reference/user-profiles) ## How it Works @@ -28,8 +26,7 @@ For flags that contain remote config values, Flagsmith will pass the value of th ::: -Identity flag values are passed into Mixpanel. If we make the call to the Flagsmith API to get the flags for an -Identity: +Identity flag values are passed into Mixpanel. If we make the call to the Flagsmith API to get the flags for an identity: ```bash curl 'https://edge.api.flagsmith.com/api/v1/identities/?identifier=development_user_123456' \ @@ -53,7 +50,7 @@ Mixpanel `identity`. ## How It Works Under The Hood -Every time an `Identity` requests their flags from the Flagsmith API, Flagsmith will send a `POST` to +Every time an identity requests their flags from the Flagsmith API, Flagsmith will send a `POST` to `https://api.mixpanel.com/engage#profile-set` with the following JSON payload: ```json @@ -70,7 +67,7 @@ Every time an `Identity` requests their flags from the Flagsmith API, Flagsmith ## Getting Mixpanel Cohorts into Flagsmith -If you want to control Flagsmith Flags based on Cohorts in Mixpanel, you will need to send cohort data from Mixpanel +If you want to control Flagsmith flags based on cohorts in Mixpanel, you will need to send cohort data from Mixpanel into Flagsmith. There is no way currently of automating this process from the Mixpanel side, but you can send cohort data from Mixpanel into Flagsmith, using [Mixpanel Webhooks](https://developer.mixpanel.com/docs/cohort-webhooks). The flow looks like this: @@ -78,10 +75,10 @@ flow looks like this: Identity cohort changes in Mixpanel -> Triggers Mixpanel Webhook -> Hits endpoint on your infrastructure -> You trigger a request to Flagsmith to set traits -This Webhook will be triggered by Mixpanel as Identities/Users enter or leave Mixpanel cohorts. We can use this trigger +This Webhook will be triggered by Mixpanel as identities/users enter or leave Mixpanel cohorts. We can use this trigger to copy the relevant data from Mixpanel into Flagsmith. Set up a webhook that accepts Mixpanel cohort data as described -[here](https://developer.mixpanel.com/docs/cohort-webhooks), then write the cohorts as Traits within the relevant -Identities. You can send Trait data either using our SDKs or with a REST query as defined in our -[API docs](/edge-api/identify-user). +[here](https://developer.mixpanel.com/docs/cohort-webhooks), then write the cohorts as traits within the relevant +identities. You can send trait data either using our SDKs or with a REST query as defined in our +[API docs](/edge-api/identify-user). \ No newline at end of file diff --git a/docs/docs/integrations/analytics/rudderstack.md b/docs/docs/third-party-integrations/analytics/rudderstack.md similarity index 92% rename from docs/docs/integrations/analytics/rudderstack.md rename to docs/docs/third-party-integrations/analytics/rudderstack.md index 55035fa9f1a0..496a8057ba5d 100644 --- a/docs/docs/integrations/analytics/rudderstack.md +++ b/docs/docs/third-party-integrations/analytics/rudderstack.md @@ -7,7 +7,7 @@ hide_title: true ![RudderStack](/img/integrations/rudderstack/rudderstack-logo.svg) -You can integrate Flagsmith with RudderStack. Send your Identity flag states into RudderStack for further downstream +You can integrate Flagsmith with RudderStack. Send your identity flag states into RudderStack for further downstream analysis. ## Integration Setup @@ -16,7 +16,7 @@ analysis. `Write Key`. 2. Make a note of the RudderStack _Data Plane URL_. 3. Go to your Flagsmith project, and click Integrations. Add the RudderStack Integration. -4. Paste the `Write Key` from step 1 into the API Key field in Flagsmith. Select the Flagsmith Environment that you want +4. Paste the `Write Key` from step 1 into the API Key field in Flagsmith. Select the Flagsmith environment that you want to send events from. Then hit Save. 5. All API calls generated by the Flagsmith SDK to the `Get Identity Flags` endpoint will send the a full set of flag evaluations for that particular user to RudderStack. @@ -31,7 +31,7 @@ For flags that contain remote config values, Flagsmith will pass the value of th ::: Identity flag values are passed into RudderStack. If we make the call to the Flagsmith API to get the flags for an -Identity: +identity: ```bash curl 'https://edge.api.flagsmith.com/api/v1/identities/?identifier=development_user_123456' \ @@ -52,4 +52,4 @@ RudderStack. ## Integration Notes You have to identify users on both platforms in the same way. The Flagsmith `Identity ID` must be the same as the -RudderStack `user_id`. +RudderStack `user_id`. \ No newline at end of file diff --git a/docs/docs/integrations/analytics/segment.md b/docs/docs/third-party-integrations/analytics/segment.md similarity index 90% rename from docs/docs/integrations/analytics/segment.md rename to docs/docs/third-party-integrations/analytics/segment.md index 2d90b8256092..a122f6ba289f 100644 --- a/docs/docs/integrations/analytics/segment.md +++ b/docs/docs/third-party-integrations/analytics/segment.md @@ -7,13 +7,13 @@ hide_title: true ![Segment](/img/integrations/segment/segment-logo.svg) -You can integrate Flagsmith with Segment. Send your Identity flag states into segment for further downstream analysis. +You can integrate Flagsmith with Segment. Send your identity flag states into segment for further downstream analysis. ## Integration Setup 1. Get the Segment API key for your Segment project. Add an _HTTP API_ source, and make a note of the `Write Key`. 2. Go to your Flagsmith project, and click Integrations. Add the Segment Integration. -3. Paste the `Write Key` from step 1 into the API Key field in Flagsmith. Select the Flagsmith Environment that you want +3. Paste the `Write Key` from step 1 into the API Key field in Flagsmith. Select the Flagsmith environment that you want to send events from. Then hit Save. 4. All API calls generated by the Flagsmith SDK to the `Get Identity Flags` endpoint will send the a full set of flag evaluations for that particular user to the Segment @@ -28,7 +28,7 @@ For flags that contain remote config values, Flagsmith will pass the value of th ::: -Identity flag values are passed into Segment. If we make the call to the Flagsmith API to get the flags for an Identity: +Identity flag values are passed into Segment. If we make the call to the Flagsmith API to get the flags for an identity: ```bash curl 'https://edge.api.flagsmith.com/api/v1/identities/?identifier=development_user_123456' \ @@ -48,4 +48,4 @@ This means you can run AB tests driven by Flagsmith segments, and have the data ## Integration Notes You have to identify users on both platforms in the same way. The Flagsmith `Identity ID` must be the same as the -Segment `user_id`. +Segment `user_id`. \ No newline at end of file diff --git a/docs/docs/third-party-integrations/ci-cd/_category_.json b/docs/docs/third-party-integrations/ci-cd/_category_.json new file mode 100644 index 000000000000..066b385b7086 --- /dev/null +++ b/docs/docs/third-party-integrations/ci-cd/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "CI/CD", + "position": 40 +} \ No newline at end of file diff --git a/docs/docs/third-party-integrations/ci-cd/index.md b/docs/docs/third-party-integrations/ci-cd/index.md new file mode 100644 index 000000000000..f3dffe6011dd --- /dev/null +++ b/docs/docs/third-party-integrations/ci-cd/index.md @@ -0,0 +1,8 @@ +--- +title: CI/CD Overview +sidebar_label: Overview +--- + +Flagsmith can be integrated into your Continuous Integration and Continuous Deployment (CI/CD) pipelines to help you automate your feature flag management. By managing your flags as code, you can ensure that your feature flag configurations are version-controlled, repeatable, and aligned with your code deployments. + +This section covers the tools and providers that allow you to integrate Flagsmith into your CI/CD workflows. \ No newline at end of file diff --git a/docs/docs/integrations/terraform.md b/docs/docs/third-party-integrations/ci-cd/terraform.md similarity index 88% rename from docs/docs/integrations/terraform.md rename to docs/docs/third-party-integrations/ci-cd/terraform.md index 8a408b6aeed3..10d3e3e45b34 100644 --- a/docs/docs/integrations/terraform.md +++ b/docs/docs/third-party-integrations/ci-cd/terraform.md @@ -1,7 +1,6 @@ --- title: Terraform Provider sidebar_label: Terraform -sidebar_position: 90 hide_title: true --- @@ -16,7 +15,7 @@ your Infrastructure as Code tooling. You can find the latest Hashicorp docs for using the Flagsmith provider [here](https://registry.terraform.io/providers/Flagsmith/flagsmith/latest/docs). -Some API actions require object UUIDs/IDs to be referenced. You can enable the [JSON View](../clients/rest.md#json-view) +Some API actions require object UUIDs/IDs to be referenced. You can enable the [JSON View](/integrating-with-flagsmith/flagsmith-api-overview/admin-api#json-view) from your account settings page which will help you access these variables. ::: @@ -54,7 +53,7 @@ provider "flagsmith" { master_api_key = "" } -# the feature that you want to manage +# the feature flag that you want to manage resource "flagsmith_feature" "new_standard_feature" { feature_name = "new_standard_feature" project_uuid = "10421b1f-5f29-4da9-abe2-30f88c07c9e8" @@ -64,7 +63,7 @@ resource "flagsmith_feature" "new_standard_feature" { ``` -Now, to create the feature all you have to do is run `terraform apply`. +Now, to create the feature flag all you have to do is run `terraform apply`. ```bash Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: @@ -97,13 +96,13 @@ Do you want to perform these actions? flagsmith_feature.new_standard_feature: Creating... flagsmith_feature.new_standard_feature: Creation complete after 2s -Apply complete! Resources: 1 added, 0 changed, 0 destroyed. +Apply complete! Resources: 1 added, 0 changed, 0 to destroy. ``` -Next, let's say you want to update the description of the feature: +Next, let's say you want to update the description of the feature flag: ```hcl -# the feature that you want to manage +# the feature flag that you want to manage resource "flagsmith_feature" "new_standard_feature" { feature_name = "new_standard_feature" project_uuid = "10421b1f-5f29-4da9-abe2-30f88c07c9e8" @@ -138,8 +137,8 @@ Do you want to perform these actions? flagsmith_feature.new_standard_feature: Modifying... flagsmith_feature.new_standard_feature: Modifications complete after 1s -Apply complete! Resources: 0 added, 1 changed, 0 destroyed. +Apply complete! Resources: 0 added, 1 changed, 0 to destroy. ``` -To bring an existing Flagsmith feature into Terraform (and start tracking state) you can go ahead and -[import](https://registry.terraform.io/providers/Flagsmith/flagsmith/latest/docs/resources/feature#import) it. +To bring an existing Flagsmith feature flag into Terraform (and start tracking state) you can go ahead and +[import](https://registry.terraform.io/providers/Flagsmith/flagsmith/latest/docs/resources/feature#import) it. \ No newline at end of file diff --git a/docs/docs/third-party-integrations/index.mdx b/docs/docs/third-party-integrations/index.mdx new file mode 100644 index 000000000000..87e134a83722 --- /dev/null +++ b/docs/docs/third-party-integrations/index.mdx @@ -0,0 +1,144 @@ +--- +title: Third-Party Integrations +description: Connect Flagsmith with analytics, observability, project management, and CI/CD platforms +--- + +import Card from '../../src/components/Card'; +import Link from '@docusaurus/Link'; + +Integrate Flagsmith with popular third-party tools to enhance your feature flag workflow with analytics tracking, monitoring, project management, and CI/CD automation across your entire stack. + +## Analytics & Data Platforms + +Connect your feature flag data with analytics platforms to track feature usage and measure impact on user behaviour. + +
+ + +

Amplitude Integration

+

Analytics and product insight platform

+
+ + +

Mixpanel Analytics Integration

+

User behaviour analytics and tracking

+
+ + +

Segment Integration

+

Customer data platform for seamless data flow

+
+ + +

Heap Analytics Integration

+

Automatic user behaviour tracking

+
+ + +

Adobe Analytics Integration

+

Enterprise analytics and marketing automation

+
+ + +

RudderStack Integration

+

Customer data pipeline and privacy-focused analytics

+
+ +
+ +## Observability & Monitoring + +Monitor flag performance and get alerts with APM tools to ensure proper operation and quick issue detection. + +
+ + +

Datadog Integration

+

Cloud monitoring and observability platform

+
+ + +

New Relic Integration

+

Application performance monitoring

+
+ + +

Grafana & Prometheus Integration

+

Open-source monitoring and analytics

+
+ + +

AppDynamics Integration

+

Enterprise application performance monitoring

+
+ + +

Dynatrace Integration

+

Automatic observability and AIOps

+
+ + +

Sentry Integration

+

Error monitoring and crash reporting

+
+ +
+ +## Project Management & Communication + +Keep your teams in sync with project management tools for automatic notifications of flag changes and updates. + +
+ + +

Jira Integration

+

Issue tracking and project management

+
+ + +

GitHub Integration

+

Version control and development collaboration

+
+ + +

Slack Integration

+

Team communication and notifications

+
+ + +

ServiceNow Integration

+

IT service management and automation

+
+ +
+ +## CI/CD & Infrastructure + +Automate feature flag management in your development workflows with CI/CD platforms for infrastructure-as-code deployments. + +
+ + +

Terraform Provider

+

Infrastructure as code automation

+
+ + +

CI/CD Overview

+

Get started with continuous integration and deployment

+
+ +
+ +## Additional Integrations + +Extend Flagsmith functionality with webhooks and other integration methods. + +
+ + +

Webhook Integration

+

Send HTTP notifications when flags change

+
+ +
\ No newline at end of file diff --git a/docs/docs/third-party-integrations/observability-and-monitoring/_category_.json b/docs/docs/third-party-integrations/observability-and-monitoring/_category_.json new file mode 100644 index 000000000000..4d1a5cae366c --- /dev/null +++ b/docs/docs/third-party-integrations/observability-and-monitoring/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Observability and Monitoring", + "position": 20 +} \ No newline at end of file diff --git a/docs/docs/integrations/apm/appdynamics.md b/docs/docs/third-party-integrations/observability-and-monitoring/appdynamics.md similarity index 100% rename from docs/docs/integrations/apm/appdynamics.md rename to docs/docs/third-party-integrations/observability-and-monitoring/appdynamics.md diff --git a/docs/docs/integrations/apm/datadog.md b/docs/docs/third-party-integrations/observability-and-monitoring/datadog.md similarity index 81% rename from docs/docs/integrations/apm/datadog.md rename to docs/docs/third-party-integrations/observability-and-monitoring/datadog.md index 0ac572fd53bf..7bc37eaa4797 100644 --- a/docs/docs/integrations/apm/datadog.md +++ b/docs/docs/third-party-integrations/observability-and-monitoring/datadog.md @@ -11,9 +11,9 @@ import ReactPlayer from 'react-player' You can integrate Flagsmith with Datadog in three ways: - [1. Integrate Flagsmith into your Datadog Dashboard](#1-integrate-flagsmith-into-your-datadog-dashboard) -- [2. Send Flag Change events to Datadog](#2-send-flag-change-events-to-datadog) +- [2. Send flag change events to Datadog](#2-send-flag-change-events-to-datadog) - [Custom Source](#custom-source) -- [3. Integrate with the DataDog RUM](#3-integrate-with-the-datadog-rum) +- [3. Integrate with the Datadog RUM](#3-integrate-with-the-datadog-rum) ## 1. Integrate Flagsmith into your Datadog Dashboard @@ -32,7 +32,7 @@ The video below will walk you through the steps of adding the integration: ## 2. Send Flag Change events to Datadog -The second type of integration allows you to send Flag change events in Flagsmith into your Datadog event stream. +The second type of integration allows you to send flag change events in Flagsmith into your Datadog event stream. ![Datadog](/img/integrations/datadog/datadog-3.png) @@ -51,5 +51,5 @@ Flag change events will now be sent to Datadog. ## 3. Integrate with the DataDog RUM -You can also send Identity flag values from Flagsmith to DataDog using our -[Javascript Integration](/clients/client-side/javascript.md#datadog-rum-javascript-sdk-integration). +You can also send identity flag values from Flagsmith to Datadog using our +[Javascript Integration](/integrating-with-flagsmith/sdks/client-side-sdks/javascript#datadog-rum-javascript-sdk-integration). \ No newline at end of file diff --git a/docs/docs/integrations/apm/dynatrace.md b/docs/docs/third-party-integrations/observability-and-monitoring/dynatrace.md similarity index 74% rename from docs/docs/integrations/apm/dynatrace.md rename to docs/docs/third-party-integrations/observability-and-monitoring/dynatrace.md index 5032148b7121..618f09e905db 100644 --- a/docs/docs/integrations/apm/dynatrace.md +++ b/docs/docs/third-party-integrations/observability-and-monitoring/dynatrace.md @@ -13,7 +13,7 @@ You can integrate Flagsmith with Dynatrace. Send flag change events from Flagsmi :::tip The Flagsmith Javascript SDK can also talk to the Dynatrace Javascript SDK. -[More info available in our JS docs page](/clients/client-side/javascript.md#dynatrace-javascript-sdk-integration). +[More info available in our JS docs page](/integrating-with-flagsmith/sdks/client-side-sdks/javascript#dynatrace-javascript-sdk-integration). ::: @@ -22,24 +22,24 @@ The Flagsmith Javascript SDK can also talk to the Dynatrace Javascript SDK. 1. Log into Dynatrace create a new Access Token with the following permissions: - `API v2 scopes` : `Ingest events` 2. Go to Flagsmith > Integrations > Dynatrace > Add Integration -3. Select the `Flagsmith Environment` you want to track. +3. Select the `Flagsmith environment` you want to track. 4. The `Base URL` depends on your Dynatrace installation: - Managed: `https://{your-domain}/e/{your-environment-id}/` - SaaS: `https://{your-environment-id}.live.dynatrace.com/` - Environment ActiveGate: `https://{your-activegate-domain}/e/{your-environment-id}/` 5. The `API Key` is the Access Token you created in step 1. -6. The `Entity Selector` defines which Dynatrace entities you want to associate Flag Change events with. +6. The `Entity Selector` defines which Dynatrace entities you want to associate flag change events with. [Check the Dynatrace Docs](https://www.dynatrace.com/support/help/dynatrace-api/environment-api/entity-v2/entity-selector) for more information on those. ### How It Works -Once the setup is complete, try changing a Flag value in the Environment you configured during setup. Then look at one +Once the setup is complete, try changing a flag value in the environment you configured during setup. Then look at one of the Dynatrace Entities defined in the `Entity Selector`. You will see Deployment Events show up in the Events panel. ![Dynatrace Events](/img/integrations/dynatrace/dynatrace-events-panel.png) ## Javascript to Javascript SDK -You can also send Identity flag values from Flagsmith to Dynatrace using our -[Javascript Integration](/clients/client-side/javascript.md#dynatrace-javascript-sdk-integration). +You can also send identity flag values from Flagsmith to Dynatrace using our +[Javascript Integration](/integrating-with-flagsmith/sdks/client-side-sdks/javascript#dynatrace-javascript-sdk-integration). \ No newline at end of file diff --git a/docs/docs/integrations/apm/grafana.md b/docs/docs/third-party-integrations/observability-and-monitoring/grafana.md similarity index 77% rename from docs/docs/integrations/apm/grafana.md rename to docs/docs/third-party-integrations/observability-and-monitoring/grafana.md index d86b13abface..7ecc80a99311 100644 --- a/docs/docs/integrations/apm/grafana.md +++ b/docs/docs/third-party-integrations/observability-and-monitoring/grafana.md @@ -1,7 +1,7 @@ --- -title: Grafana Integration -description: Integrate Flagsmith with Grafana -sidebar_label: Grafana +title: Grafana & Prometheus Integration +description: Integrate Flagsmith with Grafana & Prometheus +sidebar_label: Grafana & Prometheus hide_title: true --- @@ -47,7 +47,7 @@ Annotations for feature-specific events include project tags, user-defined tags, :::info -[Feature Health](/advanced-use/feature-health) is in Beta, please email support@flagsmith.com or chat with us here if you'd like to join. +[Feature Health](/managing-flags/feature-health-metrics) is in Beta, please email support@flagsmith.com or chat with us here if you'd like to join. ::: ### In Flagsmith: @@ -60,11 +60,11 @@ Annotations for feature-specific events include project tags, user-defined tags, 1. Create a new Webhook contact point using the Webhook URL from Flagsmith. Refer to the [Grafana documentation on contact points](https://grafana.com/docs/grafana/latest/alerting/configure-notifications/manage-contact-points/#add-a-contact-point) for details. 2. Leave Optional Webhook settings empty. Ensure the "Disable resolved message" checkbox is unchecked. -3. Add the `flagsmith_feature` label to your alert rule, specifying the Flagsmith Feature name. Refer to the [Grafana documentation on alert rule labels](https://grafana.com/docs/grafana/latest/alerting/fundamentals/alert-rules/annotation-label/#labels) for more information. -4. Optionally, include the `flagsmith_environment` label in your alert rule, using the Flagsmith Environment name as the value. +3. Add the `flagsmith_feature` label to your alert rule, specifying the Flagsmith feature flag name. Refer to the [Grafana documentation on alert rule labels](https://grafana.com/docs/grafana/latest/alerting/fundamentals/alert-rules/annotation-label/#labels) for more information. +4. Optionally, include the `flagsmith_environment` label in your alert rule, using the Flagsmith environment name as the value. 5. Set the previously created contact point as the alert rule recipient. -You can create multiple alert rules pointing to the Feature Health Provider webhook. Ensure they include the `flagsmith_feature` label with a Feature name from the Project you created the Feature Health Provider for, to see Feature Health status changes for your features. +You can create multiple alert rules pointing to the Feature Health Provider webhook. Ensure they include the `flagsmith_feature` label with a feature flag name from the project you created the Feature Health Provider for, to see Feature Health status changes for your feature flags. You can integrate Grafana Feature Health with Prometheus Alertmanager. For detailed instructions on adding Flagsmith labels to your alerts in Prometheus, refer to the [Prometheus Alertmanager webhook configuration](https://prometheus.io/docs/alerting/latest/configuration/#webhook_config) and [Alerting rules configuration](https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/#defining-alerting-rules) documentation. @@ -75,4 +75,4 @@ The Feature Health UI will display the following information: - Alert summary (if provided in alert annotations) - Dashboard URL (if Grafana) - Panel URL (if Grafana) -- Runbook URL (if provided in alert annotations) +- Runbook URL (if provided in alert annotations) \ No newline at end of file diff --git a/docs/docs/integrations/apm/newrelic.md b/docs/docs/third-party-integrations/observability-and-monitoring/new-relic.md similarity index 95% rename from docs/docs/integrations/apm/newrelic.md rename to docs/docs/third-party-integrations/observability-and-monitoring/new-relic.md index 3afd9def04d2..91ef683657c4 100644 --- a/docs/docs/integrations/apm/newrelic.md +++ b/docs/docs/third-party-integrations/observability-and-monitoring/new-relic.md @@ -1,5 +1,5 @@ --- -title: New Relic Analytics Integration +title: New Relic Integration description: Integrate Flagsmith with New Relic sidebar_label: New Relic hide_title: true @@ -19,4 +19,4 @@ You can integrate Flagsmith with New Relic. Send flag change events from Flagsmi - `https://api.eu.newrelic.com/` for the EU datacenter 4. Click Save. -Flag change events will now be sent to New Relic as `Deployments`. +Flag change events will now be sent to New Relic as `Deployments`. \ No newline at end of file diff --git a/docs/docs/third-party-integrations/observability-and-monitoring/sentry.md b/docs/docs/third-party-integrations/observability-and-monitoring/sentry.md new file mode 100644 index 000000000000..42ead86cbbcb --- /dev/null +++ b/docs/docs/third-party-integrations/observability-and-monitoring/sentry.md @@ -0,0 +1,109 @@ +--- +title: Sentry Integration +description: Integrate Flagsmith with Sentry +sidebar_label: Sentry +hide_title: true +--- + +![Sentry logo](/img/integrations/sentry/sentry-logo.svg) + +Integrate Flagsmith with Sentry to enable feature flag +[Change Tracking](https://docs.sentry.io/product/issues/issue-details/feature-flags/#change-tracking). + +:::tip + +Along with _Change Tracking_, Sentry also offers +[Evaluation Tracking](https://docs.sentry.io/product/issues/issue-details/feature-flags/#evaluation-tracking). +Integrating with _Evaluation Tracking_ is currently only possible via our +[OpenFeature provider](/integrating-with-flagsmith/openfeature). + +::: + +## Change Tracking Setup + +1. **In Sentry** + 1. Visit the + [feature flags settings page](https://sentry.io/orgredirect/organizations/:orgslug/settings/feature-flags/change-tracking/) + in a new tab. + 1. Click the "Add New Provider" button. + 1. Select "Generic" in the dropdown that says "Select a provider". + 1. Copy the provided Sentry webhook URL — we'll use that soon. + 1. **Do not close this page!** We're not done yet. +1. **In Flagsmith** + 1. Go to Integrations > Sentry > Add Integration. + 1. Choose the environment from which Sentry will receive feature flag change events. + 1. Paste the URL copied above into "Sentry webhook URL". + 1. Insert a secret (10-60 characters) and copy it. + 1. Click "Save". ✅ +1. **Back to Sentry** + 1. Paste the secret copied above. + 1. Click "Add Provider". ✅ + +Flag change events will now be sent to Sentry, and should be displayed in issue details. For more information, visit +Sentry's [Issue Details page documentation](https://docs.sentry.io/product/issues/issue-details/#feature-flags). + +## Evaluation Tracking example + +In order to add **evaluated** feature flags to a Sentry issue when it occurs, events must be sent via Sentry SDK, i.e. +the same SDK used to send application errors to Sentry. + +Flagsmith relies on the OpenFeature SDK and its integration with Sentry. + +### Python + +Sentry offers [good documentation](https://docs.sentry.io/platforms/python/integrations/openfeature/) on how to +integrate the Sentry SDK with the OpenFeature SDK. We'll extend it a bit adding an example of using it with Flagsmith. + +You'll need to install the following libraries: + +```sh +pip install "sentry-sdk[openfeature]" +pip install openfeature-provider-flagsmith +``` + +The following snippet is a micro-application that reports feature flags to Sentry when an exception is raised. + +```python +import flagsmith +import sentry_sdk +from openfeature import api as of_api +from openfeature_flagsmith.provider import FlagsmithProvider +from sentry_sdk.integrations.openfeature import OpenFeatureIntegration + +app = flask.Flask(__name__) + +flagsmith_client = flagsmith.Flagsmith( + environment_key='', +) + +feature_flag_provider = FlagsmithProvider( + client=flagsmith_client, +) + +of_api.set_provider(feature_flag_provider) + +sentry_sdk.init( + dsn="", + send_default_pii=True, + integrations=[ + OpenFeatureIntegration(), + ] +) + +def apply_discount(price): + of_client = of_api.get_client() + is_discount_enabled = of_client.get_boolean_value('discount_enabled', False) + if is_discount_enabled: + discount = price / 0 # ZeroDivisionError + else: + discount = None + return price * discount # TypeError +``` + +You can learn more about feature flags and Sentry issues in their +[Issue Details documentation](https://docs.sentry.io/product/issues/issue-details/#feature-flags). + +## JavaScript + +You'll need to manually call `Sentry.FeatureFlagsIntegration.addFeatureFlag` when evaluating a feature flag. Learn more +in the [Sentry documentation](https://docs.sentry.io/platforms/javascript/configuration/integrations/featureflags/). diff --git a/docs/docs/third-party-integrations/project-management/_category_.json b/docs/docs/third-party-integrations/project-management/_category_.json new file mode 100644 index 000000000000..434b19dd2cf3 --- /dev/null +++ b/docs/docs/third-party-integrations/project-management/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Project Management", + "position": 30 +} \ No newline at end of file diff --git a/docs/docs/integrations/project-management/github.md b/docs/docs/third-party-integrations/project-management/github.md similarity index 84% rename from docs/docs/integrations/project-management/github.md rename to docs/docs/third-party-integrations/project-management/github.md index 93eca4dc1c85..9d94794beeeb 100644 --- a/docs/docs/integrations/project-management/github.md +++ b/docs/docs/third-party-integrations/project-management/github.md @@ -1,13 +1,13 @@ --- -title: GitHub +title: GitHub Integration description: View your Flagsmith flags inside GitHub -sidebar_position: 10 +sidebar_label: GitHub hide_title: true --- GitHub Logo -View your Flagsmith Flags inside GitHub Issues and Pull Requests. +View your Flagsmith flags inside GitHub Issues and Pull Requests. ![Github Integration](/img/integrations/github/github-integration-1.png) @@ -30,7 +30,7 @@ You can either set up the integration from the Flagsmith side or from the Github 3. Select your repositories where you want install the app. 4. You will be redirected back to the Flagsmith app to finish the integration setup. 5. Select your Flagsmith Organisation. -6. Select the Flagsmith Project you want to associate with the repository where the app was installed to create the +6. Select the Flagsmith project you want to associate with the repository where the app was installed to create the Integration. ## Integration Setup (Self-Hosted) @@ -67,9 +67,7 @@ E.g. `https://flagsmith-api.example.com/api/v1/github-webhook/` ### Configuring Flagsmith -You must set the [API Env variables](/deployment/hosting/locally-api.md#github-integration-environment-variables) and -the [Frontend Env variables](/deployment/hosting/locally-frontend.md#github-integration-environment-variables) to use -your own GitHub App. +You must set the appropriate environment variables for your GitHub App configuration. Refer to the [environment variables documentation](/deployment-self-hosting/core-configuration/environment-variables) for more details. In the 'Webhook' section: @@ -77,7 +75,7 @@ In the 'Webhook' section: ## Adding a Flagsmith Flag to a GitHub issue or pull request -1. Create or select a Feature Flag. +1. Create or select a feature flag. 2. Go to the 'Link' Tab inside the Feature modal. 3. Select your GitHub integration. 4. Select GitHub Issue or GitHub PR and Save. @@ -85,4 +83,4 @@ In the 'Webhook' section: ## Removing the GitHub Integration 1. From Flagsmith, click 'Integrations', find the GitHub integration and click on 'Manage Integration'. -2. Click on 'Delete Integration' button, and confirm. +2. Click on 'Delete Integration' button, and confirm. \ No newline at end of file diff --git a/docs/docs/integrations/project-management/jira.md b/docs/docs/third-party-integrations/project-management/jira.md similarity index 74% rename from docs/docs/integrations/project-management/jira.md rename to docs/docs/third-party-integrations/project-management/jira.md index e828a92bbcb8..3c03ffced69b 100644 --- a/docs/docs/integrations/project-management/jira.md +++ b/docs/docs/third-party-integrations/project-management/jira.md @@ -1,7 +1,8 @@ --- -title: Jira +title: Jira Integration description: View your Flagsmith flags inside Jira -sidebar_position: 10 +sidebar_label: Jira +hide_title: true --- View your Flagsmith flags inside Jira. @@ -22,10 +23,10 @@ View your Flagsmith flags inside Jira. 3. When prompted, add the Flagsmith API key. 4. If you are a member of more than one Organisation, select the Organisation you want to associate with the Integration. Otherwise you can skip this step. -5. Go back to the Jira project, click `Project Settings > Connect Flagsmith project` and select the Flagsmith Projects +5. Go back to the Jira project, click `Project Settings > Connect Flagsmith project` and select the Flagsmith project you want to associate. Click Save. -## Adding a Flagsmith Flag to a Jira ticket +## Adding a Flagsmith Flag to a Jira Ticket Open a ticket and click the Flagsmith button: @@ -33,9 +34,9 @@ Open a ticket and click the Flagsmith button: --- -Select the Flag you want to associate with the Jira ticket: +Select the flag you want to associate with the Jira ticket: -![Jira associate Flag](/img/integrations/jira/associate-flag.png) +![Jira associate flag](/img/integrations/jira/associate-flag.png) --- @@ -47,5 +48,4 @@ Flag states now show inside Jira: ## Additional Tips -- You can associate multiple Flagsmith projects to a single Jira project -- You can associate multiple flags to a single Jira ticket. +- You can associate multiple flags to a single Jira ticket. \ No newline at end of file diff --git a/docs/docs/integrations/project-management/servicenow.md b/docs/docs/third-party-integrations/project-management/servicenow.md similarity index 88% rename from docs/docs/integrations/project-management/servicenow.md rename to docs/docs/third-party-integrations/project-management/servicenow.md index 374c51218d34..e8a67ac3c9b2 100644 --- a/docs/docs/integrations/project-management/servicenow.md +++ b/docs/docs/third-party-integrations/project-management/servicenow.md @@ -9,4 +9,4 @@ hide_title: true Flagsmith does not currently support integrating with ServiceNow, but we are eager to build this integration based on your requirements. Contact us if -you are interested in this or any other integrations. +you are interested in this or any other integrations. \ No newline at end of file diff --git a/docs/docs/integrations/project-management/slack.md b/docs/docs/third-party-integrations/project-management/slack.md similarity index 77% rename from docs/docs/integrations/project-management/slack.md rename to docs/docs/third-party-integrations/project-management/slack.md index e6d527a766c7..2a912ae08f19 100644 --- a/docs/docs/integrations/project-management/slack.md +++ b/docs/docs/third-party-integrations/project-management/slack.md @@ -2,7 +2,6 @@ title: Slack Integration description: Integrate Flagsmith with Slack sidebar_label: Slack -sidebar_position: 10 hide_title: true --- @@ -12,19 +11,18 @@ You can integrate Flagsmith with Slack. Send flag change events from Flagsmith i ## Integration Setup -If you don't have an account with Flagsmith already, please [sign up](https://app.flagsmith.com/signup) and create a new -Project within Flagsmith. +If you don't have an account with Flagsmith already, please [sign up](https://app.flagsmith.com/signup) and create a new project within Flagsmith. 1. Go to your Flagsmith project, and click Integrations. Then click "Add Integration" by the Slack integration. -2. Select the Environment you want to receive events for in Slack, then click "Authorise" +2. Select the environment you want to receive events for in Slack, then click "Authorise" 3. You will be sent to Slack to authenticate your account. 4. Once back in Flagsmith, select the Slack Channel you want to send events to. ## Using the Slack App -Whenever you create, update or delete a Flag in your selected Environments, the Flagsmith Bot will send a message into +Whenever you create, update or delete a flag in your selected environments, the Flagsmith Bot will send a message into your selected Slack channel detailing the changes that were made. ## Privacy Policy -You can view our [privacy policy here](https://flagsmith.com/privacy-policy/). +You can view our [privacy policy here](https://flagsmith.com/privacy-policy/). \ No newline at end of file diff --git a/docs/docs/integrations/webhook.md b/docs/docs/third-party-integrations/webhook.md similarity index 92% rename from docs/docs/integrations/webhook.md rename to docs/docs/third-party-integrations/webhook.md index 4cfe9a65c1b1..08b106473823 100644 --- a/docs/docs/integrations/webhook.md +++ b/docs/docs/third-party-integrations/webhook.md @@ -1,9 +1,8 @@ --- -title: Webhook Analytics Integration +title: Webhook Integration description: Integrate Flagsmith with your own infrastructure sidebar_label: Webhook hide_title: true -sidebar_position: 100 --- ![Webhook](/img/integrations/webhook/webhook-logo.svg) @@ -88,9 +87,7 @@ Flagsmith will send a `POST` request to the Webhook url you provide, with the fo ## Use Case -Once the integration has been set up, you can start segmenting your data warehouse users based on the flags that they -saw and the Segments that they are a member of. This allows you to enrich the data within your warehouse through -Flagsmith. +Once the integration has been set up, you can start segmenting your data warehouse users based on the flags that they saw and the segments that they are a member of. This allows you to enrich the data within your warehouse through Flagsmith. ## Webhook Signature @@ -115,4 +112,4 @@ expected_signature = hmac.new( received_signature = request["headers"]["x-flagsmith-signature"] hmac.compare_digest(expected_signature, received_signature) is True -``` +``` \ No newline at end of file diff --git a/docs/docs/version-comparison.md b/docs/docs/version-comparison.md deleted file mode 100644 index 7f5d10b603cf..000000000000 --- a/docs/docs/version-comparison.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -sidebar_position: 3 ---- - -# Version Comparison - -You are free to run the Open Source version of Flagsmith however you see fit! There are some differences between the -Open Source, SaaS hosted and Enterprise versions: - -- The Open Source version has **no** API request or Identity limits - you can run as many API instances in a cluster as - you wish. -- The Open Source version has **no** Dashboard User limits. -- The Open Source version has **no** Environment limits. -- The Open Source version is limited to a single Project. -- The SaaS and Enterprise versions have [Change Requests and Flag Scheduling](advanced-use/change-requests.md). -- The SaaS and Enterprise versions have [Role-Based Access Control](/system-administration/rbac). -- The SaaS and Enterprise versions have [Audit Logs](/system-administration/audit-logs). -- The SaaS and Enterprise versions have additional Authentication Providers: - - [Okta](/system-administration/authentication/Okta) - - [LDAP](/system-administration/authentication/LDAP) - - [SAML](/system-administration/authentication/SAML) - - [ADFS](/system-administration/authentication/ADFS) - -:::tip - -You can switch between SaaS and Self Hosted Flagsmith using our -[Import and Export tools](system-administration/importing-and-exporting/organisations). - -::: - -## SaaS Benefits - -Our SaaS platform has a number of benefits: - -- You can get up and running right away. -- Our global [Edge API](advanced-use/edge-api.md) provides global low latency flags. We aim to serve all flag requests - in < 150ms, globally. -- Get real-time flag updates to your clients, the moment they are changed in the dashboard. -- We deal with platform upgrades, security patches, scaling and backups. - -## Enterprise Benefits - -You can run our Enterprise version either on-premise, or we can provide private cloud instance dedicated to your -organisation. - -- [Role Based Access Control](/system-administration/rbac). -- [Custom fields](/advanced-use/custom-fields.md) for features, segments and environments. -- [Okta](/system-administration/authentication/Okta), [LDAP](/system-administration/authentication/LDAP), - [SAML](/system-administration/authentication/SAML) and [ADFS](/system-administration/authentication/ADFS) - authentication, as well as the ability to lock authentication to a single provider. -- Additional database engines: Oracle and MySQL. -- Additional deployment and orchestration options as detailed below. - -## Open Source Benefits - -- Completely Free! -- The Open Source version has **no** API request or Identity limits - you can run as many API instances in a cluster as - you wish. -- The Open Source version has **no** Dashboard User limits - you can have as many team members as you wish. -- Deploy with one click to a number of different [IaaS and PaaS providers](/deployment#one-click-installers). diff --git a/docs/package-lock.json b/docs/package-lock.json index cf3261ffea9e..55787ae02917 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -13,10 +13,12 @@ "@docusaurus/plugin-google-tag-manager": "^3.9.2", "@docusaurus/preset-classic": "^3.9.2", "@docusaurus/theme-mermaid": "^3.9.2", + "@ionic/react": "^8.7.7", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", "docusaurus-plugin-openapi-docs": "^4.2.0", "docusaurus-theme-openapi-docs": "^4.2.0", + "ionicons": "^8.0.13", "prism-react-renderer": "^2.3.0", "react": "^18.0.0", "react-dom": "^18.0.0", @@ -27,9 +29,10 @@ } }, "node_modules/@ai-sdk/gateway": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.0.tgz", - "integrity": "sha512-Gj0PuawK7NkZuyYgO/h5kDK/l6hFOjhLdTq3/Lli1FTl47iGmwhH1IZQpAL3Z09BeFYWakcwUmn02ovIm2wy9g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.1.tgz", + "integrity": "sha512-vPVIbnP35ZnayS937XLo85vynR85fpBQWHCdUweq7apzqFOTU2YkUd4V3msebEHbQ2Zro60ZShDDy9SMiyWTqA==", + "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.12", @@ -46,6 +49,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz", "integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==", + "license": "Apache-2.0", "dependencies": { "json-schema": "^0.4.0" }, @@ -57,6 +61,7 @@ "version": "3.0.12", "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.12.tgz", "integrity": "sha512-ZtbdvYxdMoria+2SlNarEk6Hlgyf+zzcznlD55EAl+7VZvJaSg2sqPvwArY7L6TfDEDJsnCq0fdhBSkYo0Xqdg==", + "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", @@ -70,12 +75,13 @@ } }, "node_modules/@ai-sdk/react": { - "version": "2.0.76", - "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-2.0.76.tgz", - "integrity": "sha512-ggAPzyaKJTqUWigpxMzI5DuC0Y3iEpDUPCgz6/6CpnKZY/iok+x5xiZhDemeaP0ILw5IQekV0kdgBR8JPgI8zQ==", + "version": "2.0.78", + "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-2.0.78.tgz", + "integrity": "sha512-f5inDBHJyUEzbtNxc9HiTxbcGjtot0uuc//0/khGrl8IZlLxw+yTxO/T1Qq95Rw5QPwTx9/Aw7wIZei3qws9hA==", + "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider-utils": "3.0.12", - "ai": "5.0.76", + "ai": "5.0.78", "swr": "^2.2.5", "throttleit": "2.1.0" }, @@ -93,14 +99,15 @@ } }, "node_modules/@algolia/abtesting": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.6.1.tgz", - "integrity": "sha512-wV/gNRkzb7sI9vs1OneG129hwe3Q5zPj7zigz3Ps7M5Lpo2hSorrOnXNodHEOV+yXE/ks4Pd+G3CDFIjFTWhMQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.7.0.tgz", + "integrity": "sha512-hOEItTFOvNLI6QX6TSGu7VE4XcUcdoKZT8NwDY+5mWwu87rGhkjlY7uesKTInlg6Sh8cyRkDBYRumxbkoBbBhA==", + "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.41.0", + "@algolia/requester-browser-xhr": "5.41.0", + "@algolia/requester-fetch": "5.41.0", + "@algolia/requester-node-http": "5.41.0" }, "engines": { "node": ">= 14.0.0" @@ -110,6 +117,7 @@ "version": "1.19.2", "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.19.2.tgz", "integrity": "sha512-mKv7RyuAzXvwmq+0XRK8HqZXt9iZ5Kkm2huLjgn5JoCPtDy+oh9yxUMfDDaVCw0oyzZ1isdJBc7l9nuCyyR7Nw==", + "license": "MIT", "dependencies": { "@algolia/autocomplete-plugin-algolia-insights": "1.19.2", "@algolia/autocomplete-shared": "1.19.2" @@ -119,6 +127,7 @@ "version": "1.19.2", "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.2.tgz", "integrity": "sha512-TjxbcC/r4vwmnZaPwrHtkXNeqvlpdyR+oR9Wi2XyfORkiGkLTVhX2j+O9SaCCINbKoDfc+c2PB8NjfOnz7+oKg==", + "license": "MIT", "dependencies": { "@algolia/autocomplete-shared": "1.19.2" }, @@ -130,98 +139,106 @@ "version": "1.19.2", "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.2.tgz", "integrity": "sha512-jEazxZTVD2nLrC+wYlVHQgpBoBB5KPStrJxLzsIFl6Kqd1AlG9sIAGl39V5tECLpIQzB3Qa2T6ZPJ1ChkwMK/w==", + "license": "MIT", "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", "algoliasearch": ">= 4.9.1 < 6" } }, "node_modules/@algolia/client-abtesting": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.40.1.tgz", - "integrity": "sha512-cxKNATPY5t+Mv8XAVTI57altkaPH+DZi4uMrnexPxPHODMljhGYY+GDZyHwv9a+8CbZHcY372OkxXrDMZA4Lnw==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.41.0.tgz", + "integrity": "sha512-iRuvbEyuHCAhIMkyzG3tfINLxTS7mSKo7q8mQF+FbQpWenlAlrXnfZTN19LRwnVjx0UtAdZq96ThMWGS6cQ61A==", + "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.41.0", + "@algolia/requester-browser-xhr": "5.41.0", + "@algolia/requester-fetch": "5.41.0", + "@algolia/requester-node-http": "5.41.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-analytics": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.40.1.tgz", - "integrity": "sha512-XP008aMffJCRGAY8/70t+hyEyvqqV7YKm502VPu0+Ji30oefrTn2al7LXkITz7CK6I4eYXWRhN6NaIUi65F1OA==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.41.0.tgz", + "integrity": "sha512-OIPVbGfx/AO8l1V70xYTPSeTt/GCXPEl6vQICLAXLCk9WOUbcLGcy6t8qv0rO7Z7/M/h9afY6Af8JcnI+FBFdQ==", + "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.41.0", + "@algolia/requester-browser-xhr": "5.41.0", + "@algolia/requester-fetch": "5.41.0", + "@algolia/requester-node-http": "5.41.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-common": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.40.1.tgz", - "integrity": "sha512-gWfQuQUBtzUboJv/apVGZMoxSaB0M4Imwl1c9Ap+HpCW7V0KhjBddqF2QQt5tJZCOFsfNIgBbZDGsEPaeKUosw==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.41.0.tgz", + "integrity": "sha512-8Mc9niJvfuO8dudWN5vSUlYkz7U3M3X3m1crDLc9N7FZrIVoNGOUETPk3TTHviJIh9y6eKZKbq1hPGoGY9fqPA==", + "license": "MIT", "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-insights": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.40.1.tgz", - "integrity": "sha512-RTLjST/t+lsLMouQ4zeLJq2Ss+UNkLGyNVu+yWHanx6kQ3LT5jv8UvPwyht9s7R6jCPnlSI77WnL80J32ZuyJg==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.41.0.tgz", + "integrity": "sha512-vXzvCGZS6Ixxn+WyzGUVDeR3HO/QO5POeeWy1kjNJbEf6f+tZSI+OiIU9Ha+T3ntV8oXFyBEuweygw4OLmgfiQ==", + "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.41.0", + "@algolia/requester-browser-xhr": "5.41.0", + "@algolia/requester-fetch": "5.41.0", + "@algolia/requester-node-http": "5.41.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-personalization": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.40.1.tgz", - "integrity": "sha512-2FEK6bUomBzEYkTKzD0iRs7Ljtjb45rKK/VSkyHqeJnG+77qx557IeSO0qVFE3SfzapNcoytTofnZum0BQ6r3Q==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.41.0.tgz", + "integrity": "sha512-tkymXhmlcc7w/HEvLRiHcpHxLFcUB+0PnE9FcG6hfFZ1ZXiWabH+sX+uukCVnluyhfysU9HRU2kUmUWfucx1Dg==", + "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.41.0", + "@algolia/requester-browser-xhr": "5.41.0", + "@algolia/requester-fetch": "5.41.0", + "@algolia/requester-node-http": "5.41.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-query-suggestions": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.40.1.tgz", - "integrity": "sha512-Nju4NtxAvXjrV2hHZNLKVJLXjOlW6jAXHef/CwNzk1b2qIrCWDO589ELi5ZHH1uiWYoYyBXDQTtHmhaOVVoyXg==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.41.0.tgz", + "integrity": "sha512-vyXDoz3kEZnosNeVQQwf0PbBt5IZJoHkozKRIsYfEVm+ylwSDFCW08qy2YIVSHdKy69/rWN6Ue/6W29GgVlmKQ==", + "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.41.0", + "@algolia/requester-browser-xhr": "5.41.0", + "@algolia/requester-fetch": "5.41.0", + "@algolia/requester-node-http": "5.41.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-search": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.40.1.tgz", - "integrity": "sha512-Mw6pAUF121MfngQtcUb5quZVqMC68pSYYjCRZkSITC085S3zdk+h/g7i6FxnVdbSU6OztxikSDMh1r7Z+4iPlA==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.41.0.tgz", + "integrity": "sha512-G9I2atg1ShtFp0t7zwleP6aPS4DcZvsV4uoQOripp16aR6VJzbEnKFPLW4OFXzX7avgZSpYeBAS+Zx4FOgmpPw==", + "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.41.0", + "@algolia/requester-browser-xhr": "5.41.0", + "@algolia/requester-fetch": "5.41.0", + "@algolia/requester-node-http": "5.41.0" }, "engines": { "node": ">= 14.0.0" @@ -230,78 +247,85 @@ "node_modules/@algolia/events": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz", - "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==" + "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==", + "license": "MIT" }, "node_modules/@algolia/ingestion": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.40.1.tgz", - "integrity": "sha512-z+BPlhs45VURKJIxsR99NNBWpUEEqIgwt10v/fATlNxc4UlXvALdOsWzaFfe89/lbP5Bu4+mbO59nqBC87ZM/g==", + "version": "1.41.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.41.0.tgz", + "integrity": "sha512-sxU/ggHbZtmrYzTkueTXXNyifn+ozsLP+Wi9S2hOBVhNWPZ8uRiDTDcFyL7cpCs1q72HxPuhzTP5vn4sUl74cQ==", + "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.41.0", + "@algolia/requester-browser-xhr": "5.41.0", + "@algolia/requester-fetch": "5.41.0", + "@algolia/requester-node-http": "5.41.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/monitoring": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.40.1.tgz", - "integrity": "sha512-VJMUMbO0wD8Rd2VVV/nlFtLJsOAQvjnVNGkMkspFiFhpBA7s/xJOb+fJvvqwKFUjbKTUA7DjiSi1ljSMYBasXg==", + "version": "1.41.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.41.0.tgz", + "integrity": "sha512-UQ86R6ixraHUpd0hn4vjgTHbViNO8+wA979gJmSIsRI3yli2v89QSFF/9pPcADR6PbtSio/99PmSNxhZy+CR3Q==", + "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.41.0", + "@algolia/requester-browser-xhr": "5.41.0", + "@algolia/requester-fetch": "5.41.0", + "@algolia/requester-node-http": "5.41.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/recommend": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.40.1.tgz", - "integrity": "sha512-ehvJLadKVwTp9Scg9NfzVSlBKH34KoWOQNTaN8i1Ac64AnO6iH2apJVSP6GOxssaghZ/s8mFQsDH3QIZoluFHA==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.41.0.tgz", + "integrity": "sha512-DxP9P8jJ8whJOnvmyA5mf1wv14jPuI0L25itGfOHSU6d4ZAjduVfPjTS3ROuUN5CJoTdlidYZE+DtfWHxJwyzQ==", + "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.41.0", + "@algolia/requester-browser-xhr": "5.41.0", + "@algolia/requester-fetch": "5.41.0", + "@algolia/requester-node-http": "5.41.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.40.1.tgz", - "integrity": "sha512-PbidVsPurUSQIr6X9/7s34mgOMdJnn0i6p+N6Ab+lsNhY5eiu+S33kZEpZwkITYBCIbhzDLOvb7xZD3gDi+USA==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.41.0.tgz", + "integrity": "sha512-C21J+LYkE48fDwtLX7YXZd2Fn7Fe0/DOEtvohSfr/ODP8dGDhy9faaYeWB0n1AvmZltugjkjAXT7xk0CYNIXsQ==", + "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1" + "@algolia/client-common": "5.41.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-fetch": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.40.1.tgz", - "integrity": "sha512-ThZ5j6uOZCF11fMw9IBkhigjOYdXGXQpj6h4k+T9UkZrF2RlKcPynFzDeRgaLdpYk8Yn3/MnFbwUmib7yxj5Lw==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.41.0.tgz", + "integrity": "sha512-FhJy/+QJhMx1Hajf2LL8og4J7SqOAHiAuUXq27cct4QnPhSIuIGROzeRpfDNH5BUbq22UlMuGd44SeD4HRAqvA==", + "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1" + "@algolia/client-common": "5.41.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-node-http": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.40.1.tgz", - "integrity": "sha512-H1gYPojO6krWHnUXu/T44DrEun/Wl95PJzMXRcM/szstNQczSbwq6wIFJPI9nyE95tarZfUNU3rgorT+wZ6iCQ==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.41.0.tgz", + "integrity": "sha512-tYv3rGbhBS0eZ5D8oCgV88iuWILROiemk+tQ3YsAKZv2J4kKUNvKkrX/If/SreRy4MGP2uJzMlyKcfSfO2mrsQ==", + "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1" + "@algolia/client-common": "5.41.0" }, "engines": { "node": ">= 14.0.0" @@ -392,22 +416,22 @@ } }, "node_modules/@antfu/install-pkg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.0.0.tgz", - "integrity": "sha512-xvX6P/lo1B3ej0OsaErAjqgFYzYVcJpamjLAFLYh9vRJngBrMoUG7aVnrGTeqM7yxbyTD5p3F2+0/QUEh8Vzhw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", "license": "MIT", "dependencies": { - "package-manager-detector": "^0.2.8", - "tinyexec": "^0.3.2" + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@antfu/utils": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-8.1.1.tgz", - "integrity": "sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-9.3.0.tgz", + "integrity": "sha512-9hFT4RauhcUzqOE4f1+frMKLZrgNog5b06I7VmZQV1BkvwvqrbC8EBZf3L1eEL2AKb6rNKjER0sEvJiSP1FXEA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" @@ -433,6 +457,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", @@ -443,9 +468,10 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", - "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -488,12 +514,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", - "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.3", - "@babel/types": "^7.28.2", + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -529,6 +556,7 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "license": "MIT", "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", @@ -604,6 +632,7 @@ "version": "0.6.5", "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", @@ -619,6 +648,7 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -639,6 +669,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "license": "MIT", "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" @@ -678,6 +709,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -742,14 +774,16 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -758,6 +792,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -789,11 +824,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", - "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.28.4" + "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" @@ -891,6 +927,7 @@ "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==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1654,9 +1691,10 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.3.tgz", - "integrity": "sha512-Y6ab1kGqZ0u42Zv/4a7l0l72n9DKP/MKoKWaUSBylrhNZO2prYuqFOLbn5aW5SIFXwSH93yfjbgllL8lxuGKLg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.5.tgz", + "integrity": "sha512-20NUVgOrinudkIBzQ2bNxP08YpKprUkRTiRSd2/Z5GOdPImJGkoN4Z7IQe1T5AdyKI1i5L6RBmluqdSzvaq9/w==", + "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", @@ -1676,6 +1714,7 @@ "version": "0.13.0", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "license": "MIT", "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5", "core-js-compat": "^3.43.0" @@ -1688,6 +1727,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -1996,6 +2036,7 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.28.4.tgz", "integrity": "sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ==", + "license": "MIT", "dependencies": { "core-js-pure": "^3.43.0" }, @@ -2007,6 +2048,7 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", @@ -2017,16 +2059,17 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", - "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", + "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.4", + "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4", + "@babel/types": "^7.28.5", "debug": "^4.3.1" }, "engines": { @@ -2034,12 +2077,13 @@ } }, "node_modules/@babel/types": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", - "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -2055,6 +2099,7 @@ "version": "11.0.3", "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", + "license": "Apache-2.0", "dependencies": { "@chevrotain/gast": "11.0.3", "@chevrotain/types": "11.0.3", @@ -2065,6 +2110,7 @@ "version": "11.0.3", "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", + "license": "Apache-2.0", "dependencies": { "@chevrotain/types": "11.0.3", "lodash-es": "4.17.21" @@ -2073,17 +2119,20 @@ "node_modules/@chevrotain/regexp-to-ast": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", - "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==" + "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", + "license": "Apache-2.0" }, "node_modules/@chevrotain/types": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", - "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==" + "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", + "license": "Apache-2.0" }, "node_modules/@chevrotain/utils": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", - "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==" + "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", + "license": "Apache-2.0" }, "node_modules/@colors/colors": { "version": "1.5.0", @@ -2108,6 +2157,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT", "engines": { "node": ">=18" }, @@ -2130,6 +2180,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "engines": { "node": ">=18" } @@ -2148,6 +2199,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT", "engines": { "node": ">=18" }, @@ -2170,6 +2222,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT", "dependencies": { "@csstools/color-helpers": "^5.1.0", "@csstools/css-calc": "^2.1.4" @@ -2196,6 +2249,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT", "engines": { "node": ">=18" }, @@ -2217,6 +2271,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT", "engines": { "node": ">=18" } @@ -2235,6 +2290,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT", "engines": { "node": ">=18" }, @@ -2257,6 +2313,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", @@ -2285,6 +2342,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/selector-specificity": "^5.0.0", "postcss-selector-parser": "^7.0.0" @@ -2310,6 +2368,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "engines": { "node": ">=18" }, @@ -2321,6 +2380,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -2343,6 +2403,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", @@ -2371,6 +2432,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", @@ -2399,6 +2461,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", @@ -2427,6 +2490,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", @@ -2455,6 +2519,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", @@ -2482,6 +2547,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", @@ -2510,6 +2576,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/css-calc": "^2.1.4", "@csstools/css-parser-algorithms": "^3.0.5", @@ -2536,6 +2603,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" @@ -2561,6 +2629,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", @@ -2587,6 +2656,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", @@ -2615,6 +2685,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", @@ -2643,6 +2714,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0", @@ -2669,6 +2741,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "engines": { "node": ">=18" }, @@ -2690,6 +2763,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/selector-specificity": "^5.0.0", "postcss-selector-parser": "^7.0.0" @@ -2715,6 +2789,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "engines": { "node": ">=18" }, @@ -2726,6 +2801,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -2748,6 +2824,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", @@ -2775,6 +2852,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "engines": { "node": ">=18" }, @@ -2796,6 +2874,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "engines": { "node": ">=18" }, @@ -2817,6 +2896,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "engines": { "node": ">=18" }, @@ -2838,6 +2918,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -2862,6 +2943,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/css-tokenizer": "^3.0.4", "@csstools/utilities": "^2.0.0" @@ -2887,6 +2969,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT", "dependencies": { "@csstools/css-calc": "^2.1.4", "@csstools/css-parser-algorithms": "^3.0.5", @@ -2914,6 +2997,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", @@ -2940,6 +3024,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" @@ -2965,6 +3050,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -2989,6 +3075,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", @@ -3017,6 +3104,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -3041,6 +3129,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/css-calc": "^2.1.4", "@csstools/css-parser-algorithms": "^3.0.5", @@ -3067,6 +3156,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", @@ -3095,6 +3185,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "postcss-selector-parser": "^7.0.0" }, @@ -3109,6 +3200,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -3131,6 +3223,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/css-calc": "^2.1.4", "@csstools/css-parser-algorithms": "^3.0.5", @@ -3157,6 +3250,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/css-calc": "^2.1.4", "@csstools/css-parser-algorithms": "^3.0.5", @@ -3183,6 +3277,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/color-helpers": "^5.1.0", "postcss-value-parser": "^4.2.0" @@ -3208,6 +3303,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/css-calc": "^2.1.4", "@csstools/css-parser-algorithms": "^3.0.5", @@ -3234,6 +3330,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "engines": { "node": ">=18" }, @@ -3255,6 +3352,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "engines": { "node": ">=18" }, @@ -3273,12 +3371,14 @@ "node_modules/@docsearch/css": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-4.2.0.tgz", - "integrity": "sha512-65KU9Fw5fGsPPPlgIghonMcndyx1bszzrDQYLfierN+Ha29yotMHzVS94bPkZS6On9LS8dE4qmW4P/fGjtCf/g==" + "integrity": "sha512-65KU9Fw5fGsPPPlgIghonMcndyx1bszzrDQYLfierN+Ha29yotMHzVS94bPkZS6On9LS8dE4qmW4P/fGjtCf/g==", + "license": "MIT" }, "node_modules/@docsearch/react": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-4.2.0.tgz", "integrity": "sha512-zSN/KblmtBcerf7Z87yuKIHZQmxuXvYc6/m0+qnjyNu+Ir67AVOagTa1zBqcxkVUVkmBqUExdcyrdo9hbGbqTw==", + "license": "MIT", "dependencies": { "@ai-sdk/react": "^2.0.30", "@algolia/autocomplete-core": "1.19.2", @@ -3313,6 +3413,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.9.2.tgz", "integrity": "sha512-GEANdi/SgER+L7Japs25YiGil/AUDnFFHaCGPBbundxoWtCkA2lmy7/tFmgED4y1htAy6Oi4wkJEQdGssnw9MA==", + "license": "MIT", "dependencies": { "@babel/core": "^7.25.9", "@babel/generator": "^7.25.9", @@ -3338,6 +3439,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.9.2.tgz", "integrity": "sha512-ZOVi6GYgTcsZcUzjblpzk3wH1Fya2VNpd5jtHoCCFcJlMQ1EYXZetfAnRHLcyiFeBABaI1ltTYbOBtH/gahGVA==", + "license": "MIT", "dependencies": { "@babel/core": "^7.25.9", "@docusaurus/babel": "3.9.2", @@ -3380,6 +3482,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.9.2.tgz", "integrity": "sha512-HbjwKeC+pHUFBfLMNzuSjqFE/58+rLVKmOU3lxQrpsxLBOGosYco/Q0GduBb0/jEMRiyEqjNT/01rRdOMWq5pw==", + "license": "MIT", "dependencies": { "@docusaurus/babel": "3.9.2", "@docusaurus/bundler": "3.9.2", @@ -3453,6 +3556,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.9.2.tgz", "integrity": "sha512-8gBKup94aGttRduABsj7bpPFTX7kbwu+xh3K9NMCF5K4bWBqTFYW+REKHF6iBVDHRJ4grZdIPbvkiHd/XNKRMQ==", + "license": "MIT", "dependencies": { "cssnano-preset-advanced": "^6.1.2", "postcss": "^8.5.4", @@ -3467,6 +3571,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.9.2.tgz", "integrity": "sha512-/SVCc57ByARzGSU60c50rMyQlBuMIJCjcsJlkphxY6B0GV4UH3tcA1994N8fFfbJ9kX3jIBe/xg3XP5qBtGDbA==", + "license": "MIT", "dependencies": { "chalk": "^4.1.2", "tslib": "^2.6.0" @@ -3479,6 +3584,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.9.2.tgz", "integrity": "sha512-wiYoGwF9gdd6rev62xDU8AAM8JuLI/hlwOtCzMmYcspEkzecKrP8J8X+KpYnTlACBUUtXNJpSoCwFWJhLRevzQ==", + "license": "MIT", "dependencies": { "@docusaurus/logger": "3.9.2", "@docusaurus/utils": "3.9.2", @@ -3517,6 +3623,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.9.2.tgz", "integrity": "sha512-8qVe2QA9hVLzvnxP46ysuofJUIc/yYQ82tvA/rBTrnpXtCjNSFLxEZfd5U8cYZuJIVlkPxamsIgwd5tGZXfvew==", + "license": "MIT", "dependencies": { "@docusaurus/types": "3.9.2", "@types/history": "^4.7.11", @@ -3535,6 +3642,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.9.2.tgz", "integrity": "sha512-3I2HXy3L1QcjLJLGAoTvoBnpOwa6DPUa3Q0dMK19UTY9mhPkKQg/DYhAGTiBUKcTR0f08iw7kLPqOhIgdV3eVQ==", + "license": "MIT", "dependencies": { "@docusaurus/core": "3.9.2", "@docusaurus/logger": "3.9.2", @@ -3568,6 +3676,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.9.2.tgz", "integrity": "sha512-C5wZsGuKTY8jEYsqdxhhFOe1ZDjH0uIYJ9T/jebHwkyxqnr4wW0jTkB72OMqNjsoQRcb0JN3PcSeTwFlVgzCZg==", + "license": "MIT", "dependencies": { "@docusaurus/core": "3.9.2", "@docusaurus/logger": "3.9.2", @@ -3600,6 +3709,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.9.2.tgz", "integrity": "sha512-s4849w/p4noXUrGpPUF0BPqIAfdAe76BLaRGAGKZ1gTDNiGxGcpsLcwJ9OTi1/V8A+AzvsmI9pkjie2zjIQZKA==", + "license": "MIT", "dependencies": { "@docusaurus/core": "3.9.2", "@docusaurus/mdx-loader": "3.9.2", @@ -3622,6 +3732,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.9.2.tgz", "integrity": "sha512-w1s3+Ss+eOQbscGM4cfIFBlVg/QKxyYgj26k5AnakuHkKxH6004ZtuLe5awMBotIYF2bbGDoDhpgQ4r/kcj4rQ==", + "license": "MIT", "dependencies": { "@docusaurus/core": "3.9.2", "@docusaurus/types": "3.9.2", @@ -3637,6 +3748,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.9.2.tgz", "integrity": "sha512-j7a5hWuAFxyQAkilZwhsQ/b3T7FfHZ+0dub6j/GxKNFJp2h9qk/P1Bp7vrGASnvA9KNQBBL1ZXTe7jlh4VdPdA==", + "license": "MIT", "dependencies": { "@docusaurus/core": "3.9.2", "@docusaurus/types": "3.9.2", @@ -3657,6 +3769,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.9.2.tgz", "integrity": "sha512-mAwwQJ1Us9jL/lVjXtErXto4p4/iaLlweC54yDUK1a97WfkC6Z2k5/769JsFgwOwOP+n5mUQGACXOEQ0XDuVUw==", + "license": "MIT", "dependencies": { "@docusaurus/core": "3.9.2", "@docusaurus/types": "3.9.2", @@ -3675,6 +3788,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.9.2.tgz", "integrity": "sha512-YJ4lDCphabBtw19ooSlc1MnxtYGpjFV9rEdzjLsUnBCeis2djUyCozZaFhCg6NGEwOn7HDDyMh0yzcdRpnuIvA==", + "license": "MIT", "dependencies": { "@docusaurus/core": "3.9.2", "@docusaurus/types": "3.9.2", @@ -3694,6 +3808,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.9.2.tgz", "integrity": "sha512-LJtIrkZN/tuHD8NqDAW1Tnw0ekOwRTfobWPsdO15YxcicBo2ykKF0/D6n0vVBfd3srwr9Z6rzrIWYrMzBGrvNw==", + "license": "MIT", "dependencies": { "@docusaurus/core": "3.9.2", "@docusaurus/types": "3.9.2", @@ -3712,6 +3827,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.9.2.tgz", "integrity": "sha512-WLh7ymgDXjG8oPoM/T4/zUP7KcSuFYRZAUTl8vR6VzYkfc18GBM4xLhcT+AKOwun6kBivYKUJf+vlqYJkm+RHw==", + "license": "MIT", "dependencies": { "@docusaurus/core": "3.9.2", "@docusaurus/logger": "3.9.2", @@ -3735,6 +3851,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-svgr/-/plugin-svgr-3.9.2.tgz", "integrity": "sha512-n+1DE+5b3Lnf27TgVU5jM1d4x5tUh2oW5LTsBxJX4PsAPV0JGcmI6p3yLYtEY0LRVEIJh+8RsdQmRE66wSV8mw==", + "license": "MIT", "dependencies": { "@docusaurus/core": "3.9.2", "@docusaurus/types": "3.9.2", @@ -3757,6 +3874,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.9.2.tgz", "integrity": "sha512-IgyYO2Gvaigi21LuDIe+nvmN/dfGXAiMcV/murFqcpjnZc7jxFAxW+9LEjdPt61uZLxG4ByW/oUmX/DDK9t/8w==", + "license": "MIT", "dependencies": { "@docusaurus/core": "3.9.2", "@docusaurus/plugin-content-blog": "3.9.2", @@ -3786,6 +3904,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.9.2.tgz", "integrity": "sha512-IGUsArG5hhekXd7RDb11v94ycpJpFdJPkLnt10fFQWOVxAtq5/D7hT6lzc2fhyQKaaCE62qVajOMKL7OiAFAIA==", + "license": "MIT", "dependencies": { "@docusaurus/core": "3.9.2", "@docusaurus/logger": "3.9.2", @@ -3825,6 +3944,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.9.2.tgz", "integrity": "sha512-6c4DAbR6n6nPbnZhY2V3tzpnKnGL+6aOsLvFL26VRqhlczli9eWG0VDUNoCQEPnGwDMhPS42UhSAnz5pThm5Ag==", + "license": "MIT", "dependencies": { "@docusaurus/mdx-loader": "3.9.2", "@docusaurus/module-type-aliases": "3.9.2", @@ -3852,6 +3972,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/theme-mermaid/-/theme-mermaid-3.9.2.tgz", "integrity": "sha512-5vhShRDq/ntLzdInsQkTdoKWSzw8d1jB17sNPYhA/KvYYFXfuVEGHLM6nrf8MFbV8TruAHDG21Fn3W4lO8GaDw==", + "license": "MIT", "dependencies": { "@docusaurus/core": "3.9.2", "@docusaurus/module-type-aliases": "3.9.2", @@ -3879,6 +4000,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.9.2.tgz", "integrity": "sha512-GBDSFNwjnh5/LdkxCKQHkgO2pIMX1447BxYUBG2wBiajS21uj64a+gH/qlbQjDLxmGrbrllBrtJkUHxIsiwRnw==", + "license": "MIT", "dependencies": { "@docsearch/react": "^3.9.0 || ^4.1.0", "@docusaurus/core": "3.9.2", @@ -3909,6 +4031,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.9.2.tgz", "integrity": "sha512-vIryvpP18ON9T9rjgMRFLr2xJVDpw1rtagEGf8Ccce4CkTrvM/fRB8N2nyWYOW5u3DdjkwKw5fBa+3tbn9P4PA==", + "license": "MIT", "dependencies": { "fs-extra": "^11.1.1", "tslib": "^2.6.0" @@ -3921,6 +4044,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.9.2.tgz", "integrity": "sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q==", + "license": "MIT", "dependencies": { "@mdx-js/mdx": "^3.0.0", "@types/history": "^4.7.11", @@ -3942,6 +4066,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.9.2.tgz", "integrity": "sha512-lBSBiRruFurFKXr5Hbsl2thmGweAPmddhF3jb99U4EMDA5L+e5Y1rAkOS07Nvrup7HUMBDrCV45meaxZnt28nQ==", + "license": "MIT", "dependencies": { "@docusaurus/logger": "3.9.2", "@docusaurus/types": "3.9.2", @@ -3973,6 +4098,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.9.2.tgz", "integrity": "sha512-I53UC1QctruA6SWLvbjbhCpAw7+X7PePoe5pYcwTOEXD/PxeP8LnECAhTHHwWCblyUX5bMi4QLRkxvyZ+IT8Aw==", + "license": "MIT", "dependencies": { "@docusaurus/types": "3.9.2", "tslib": "^2.6.0" @@ -3985,6 +4111,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.9.2.tgz", "integrity": "sha512-l7yk3X5VnNmATbwijJkexdhulNsQaNDwoagiwujXoxFbWLcxHQqNQ+c/IAlzrfMMOfa/8xSBZ7KEKDesE/2J7A==", + "license": "MIT", "dependencies": { "@docusaurus/logger": "3.9.2", "@docusaurus/utils": "3.9.2", @@ -4040,18 +4167,18 @@ "license": "MIT" }, "node_modules/@iconify/utils": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.3.0.tgz", - "integrity": "sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.0.2.tgz", + "integrity": "sha512-EfJS0rLfVuRuJRn4psJHtK2A9TqVnkxPpHY6lYHiB9+8eSuudsxbwMiavocG45ujOo6FJ+CIRlRnlOGinzkaGQ==", "license": "MIT", "dependencies": { - "@antfu/install-pkg": "^1.0.0", - "@antfu/utils": "^8.1.0", + "@antfu/install-pkg": "^1.1.0", + "@antfu/utils": "^9.2.0", "@iconify/types": "^2.0.0", - "debug": "^4.4.0", - "globals": "^15.14.0", + "debug": "^4.4.1", + "globals": "^15.15.0", "kolorist": "^1.8.0", - "local-pkg": "^1.0.0", + "local-pkg": "^1.1.1", "mlly": "^1.7.4" } }, @@ -4067,6 +4194,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@ionic/core": { + "version": "8.7.7", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.7.tgz", + "integrity": "sha512-XMvVkiRiB9I1Jc63RqderjHzxxiyr6KM2vBDzYBQS6rk7Fb4wC/ZyUuFgnrYKk71r1mgwYTPMy/2qrCShNXgXQ==", + "license": "MIT", + "dependencies": { + "@stencil/core": "4.38.0", + "ionicons": "^8.0.13", + "tslib": "^2.1.0" + } + }, + "node_modules/@ionic/react": { + "version": "8.7.7", + "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.7.7.tgz", + "integrity": "sha512-X/olNPQrITyVbKkZRrhauC6cKXO+C6ISCnoDJFxH34TLphSrpOFOpVD/c+a17QMD8RMxe5/zsQ8oY/DOH8pC6w==", + "license": "MIT", + "dependencies": { + "@ionic/core": "8.7.7", + "ionicons": "^8.0.13", + "tslib": "*" + }, + "peerDependencies": { + "react": ">=16.8.6", + "react-dom": ">=16.8.6" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -4112,6 +4265,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.27.8" }, @@ -4123,6 +4277,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -4139,6 +4294,7 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" @@ -4164,12 +4320,14 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -4184,6 +4342,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "license": "Apache-2.0", "engines": { "node": ">=10.0" }, @@ -4199,6 +4358,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "license": "Apache-2.0", "engines": { "node": ">=10.0" }, @@ -4214,6 +4374,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "license": "Apache-2.0", "engines": { "node": ">=10.0" }, @@ -4229,6 +4390,7 @@ "version": "1.21.0", "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", + "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/base64": "^1.1.2", "@jsonjoy.com/buffers": "^1.2.0", @@ -4254,6 +4416,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", + "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/codegen": "^1.0.0", "@jsonjoy.com/util": "^1.9.0" @@ -4273,6 +4436,7 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", + "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/buffers": "^1.0.0", "@jsonjoy.com/codegen": "^1.0.0" @@ -4291,7 +4455,8 @@ "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==" + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "license": "MIT" }, "node_modules/@mdx-js/mdx": { "version": "3.0.1", @@ -4344,9 +4509,10 @@ } }, "node_modules/@mermaid-js/parser": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.2.tgz", - "integrity": "sha512-+PO02uGF6L6Cs0Bw8RpGhikVvMWEysfAyl27qTlroUB8jSWr1lL0Sf6zi78ZxlSnmgSY2AMMKVgghnN9jTtwkQ==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.3.tgz", + "integrity": "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==", + "license": "MIT", "dependencies": { "langium": "3.3.1" } @@ -4387,6 +4553,7 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", "engines": { "node": ">=8.0.0" } @@ -4813,6 +4980,110 @@ } } }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz", + "integrity": "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz", + "integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz", + "integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz", + "integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz", + "integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz", + "integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz", + "integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz", + "integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -4834,7 +5105,8 @@ "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "license": "MIT" }, "node_modules/@sindresorhus/is": { "version": "4.6.0", @@ -4860,7 +5132,31 @@ "node_modules/@standard-schema/spec": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", - "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==" + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "license": "MIT" + }, + "node_modules/@stencil/core": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.38.0.tgz", + "integrity": "sha512-oC3QFKO0X1yXVvETgc8OLY525MNKhn9vISBrbtKnGoPlokJ6rI8Vk1RK22TevnNrHLI4SExNLbcDnqilKR35JQ==", + "license": "MIT", + "bin": { + "stencil": "bin/stencil" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.10.0" + }, + "optionalDependencies": { + "@rollup/rollup-darwin-arm64": "4.34.9", + "@rollup/rollup-darwin-x64": "4.34.9", + "@rollup/rollup-linux-arm64-gnu": "4.34.9", + "@rollup/rollup-linux-arm64-musl": "4.34.9", + "@rollup/rollup-linux-x64-gnu": "4.34.9", + "@rollup/rollup-linux-x64-musl": "4.34.9", + "@rollup/rollup-win32-arm64-msvc": "4.34.9", + "@rollup/rollup-win32-x64-msvc": "4.34.9" + } }, "node_modules/@svgr/babel-plugin-add-jsx-attribute": { "version": "8.0.0", @@ -5136,6 +5432,7 @@ "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "license": "MIT", "dependencies": { "@types/connect": "*", "@types/node": "*" @@ -5145,6 +5442,7 @@ "version": "3.5.13", "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -5153,6 +5451,7 @@ "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -5161,6 +5460,7 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "license": "MIT", "dependencies": { "@types/express-serve-static-core": "*", "@types/node": "*" @@ -5205,9 +5505,9 @@ } }, "node_modules/@types/d3-array": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", - "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", "license": "MIT" }, "node_modules/@types/d3-axis": { @@ -5257,9 +5557,9 @@ "license": "MIT" }, "node_modules/@types/d3-dispatch": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz", - "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", "license": "MIT" }, "node_modules/@types/d3-drag": { @@ -5441,9 +5741,10 @@ } }, "node_modules/@types/express": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", - "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.24.tgz", + "integrity": "sha512-Mbrt4SRlXSTWryOnHAh2d4UQ/E7n9lZyGSi6KgX+4hkuL9soYbLOVXVhnk/ODp12YsGc95f4pOvqywJ6kngUwg==", + "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -5455,6 +5756,7 @@ "version": "4.19.7", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz", "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==", + "license": "MIT", "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -5471,7 +5773,8 @@ "node_modules/@types/gtag.js": { "version": "0.0.12", "resolved": "https://registry.npmjs.org/@types/gtag.js/-/gtag.js-0.0.12.tgz", - "integrity": "sha512-YQV9bUsemkzG81Ea295/nF/5GijnD2Af7QhEofh7xu+kvCN6RdodgNwwGWXB5GMI3NoyvQo0odNctoH/qLMIpg==" + "integrity": "sha512-YQV9bUsemkzG81Ea295/nF/5GijnD2Af7QhEofh7xu+kvCN6RdodgNwwGWXB5GMI3NoyvQo0odNctoH/qLMIpg==", + "license": "MIT" }, "node_modules/@types/hast": { "version": "3.0.4", @@ -5508,12 +5811,14 @@ "node_modules/@types/http-errors": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==" + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "license": "MIT" }, "node_modules/@types/http-proxy": { - "version": "1.17.16", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", - "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", + "version": "1.17.17", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", + "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -5521,12 +5826,14 @@ "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==" + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "*" } @@ -5535,6 +5842,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "license": "MIT", "dependencies": { "@types/istanbul-lib-report": "*" } @@ -5560,7 +5868,8 @@ "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" }, "node_modules/@types/ms": { "version": "0.7.34", @@ -5579,6 +5888,7 @@ "version": "1.3.14", "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -5601,12 +5911,14 @@ "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==" + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" }, "node_modules/@types/react": { "version": "18.2.41", @@ -5652,6 +5964,7 @@ "version": "5.3.3", "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "license": "MIT", "dependencies": { "@types/history": "^4.7.11", "@types/react": "*", @@ -5661,12 +5974,14 @@ "node_modules/@types/retry": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", - "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==" + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "license": "MIT" }, "node_modules/@types/sax": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -5677,9 +5992,10 @@ "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" }, "node_modules/@types/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.0.tgz", - "integrity": "sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -5688,14 +6004,16 @@ "version": "1.9.4", "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "license": "MIT", "dependencies": { "@types/express": "*" } }, "node_modules/@types/serve-static": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.9.tgz", - "integrity": "sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA==", + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "license": "MIT", "dependencies": { "@types/http-errors": "*", "@types/node": "*", @@ -5703,9 +6021,10 @@ } }, "node_modules/@types/serve-static/node_modules/@types/send": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", - "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "license": "MIT", "dependencies": { "@types/mime": "^1", "@types/node": "*" @@ -5715,6 +6034,7 @@ "version": "0.3.36", "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -5723,6 +6043,7 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", "optional": true }, "node_modules/@types/unist": { @@ -5734,14 +6055,16 @@ "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.34", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.34.tgz", + "integrity": "sha512-KExbHVa92aJpw9WDQvzBaGVE2/Pz+pLZQloT2hjL8IqsZnV62rlPOYvNnLmf/L2dyllfVUOVBj64M0z/46eR2A==", + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } @@ -5749,7 +6072,8 @@ "node_modules/@types/yargs-parser": { "version": "21.0.3", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "license": "MIT" }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", @@ -5760,6 +6084,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.0.3.tgz", "integrity": "sha512-yNEQvPcVrK9sIe637+I0jD6leluPxzwJKx/Haw6F4H77CdDsszUn5V3o96LPziXkSNE2B83+Z3mjqGKBK/R6Gg==", + "license": "Apache-2.0", "engines": { "node": ">= 20" } @@ -5920,6 +6245,7 @@ "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==", + "license": "MIT", "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -5932,6 +6258,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -5940,6 +6267,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -5951,14 +6279,15 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -6017,6 +6346,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "license": "MIT", "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -6026,11 +6356,12 @@ } }, "node_modules/ai": { - "version": "5.0.76", - "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.76.tgz", - "integrity": "sha512-ZCxi1vrpyCUnDbtYrO/W8GLvyacV9689f00yshTIQ3mFFphbD7eIv40a2AOZBv3GGRA7SSRYIDnr56wcS/gyQg==", + "version": "5.0.78", + "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.78.tgz", + "integrity": "sha512-ec77fmQwJGLduswMrW4AAUGSOiu8dZaIwMmWHHGKsrMUFFS6ugfkTyx0srtuKYHNRRLRC2dT7cPirnUl98VnxA==", + "license": "Apache-2.0", "dependencies": { - "@ai-sdk/gateway": "2.0.0", + "@ai-sdk/gateway": "2.0.1", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.12", "@opentelemetry/api": "1.9.0" @@ -6090,6 +6421,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -6098,24 +6430,25 @@ } }, "node_modules/algoliasearch": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.40.1.tgz", - "integrity": "sha512-iUNxcXUNg9085TJx0HJLjqtDE0r1RZ0GOGrt8KNQqQT5ugu8lZsHuMUYW/e0lHhq6xBvmktU9Bw4CXP9VQeKrg==", - "dependencies": { - "@algolia/abtesting": "1.6.1", - "@algolia/client-abtesting": "5.40.1", - "@algolia/client-analytics": "5.40.1", - "@algolia/client-common": "5.40.1", - "@algolia/client-insights": "5.40.1", - "@algolia/client-personalization": "5.40.1", - "@algolia/client-query-suggestions": "5.40.1", - "@algolia/client-search": "5.40.1", - "@algolia/ingestion": "1.40.1", - "@algolia/monitoring": "1.40.1", - "@algolia/recommend": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.41.0.tgz", + "integrity": "sha512-9E4b3rJmYbBkn7e3aAPt1as+VVnRhsR4qwRRgOzpeyz4PAOuwKh0HI4AN6mTrqK0S0M9fCCSTOUnuJ8gPY/tvA==", + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.7.0", + "@algolia/client-abtesting": "5.41.0", + "@algolia/client-analytics": "5.41.0", + "@algolia/client-common": "5.41.0", + "@algolia/client-insights": "5.41.0", + "@algolia/client-personalization": "5.41.0", + "@algolia/client-query-suggestions": "5.41.0", + "@algolia/client-search": "5.41.0", + "@algolia/ingestion": "1.41.0", + "@algolia/monitoring": "1.41.0", + "@algolia/recommend": "5.41.0", + "@algolia/requester-browser-xhr": "5.41.0", + "@algolia/requester-fetch": "5.41.0", + "@algolia/requester-node-http": "5.41.0" }, "engines": { "node": ">= 14.0.0" @@ -6125,6 +6458,7 @@ "version": "3.26.0", "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.26.0.tgz", "integrity": "sha512-Rv2x3GXleQ3ygwhkhJubhhYGsICmShLAiqtUuJTUkr9uOCOXyF2E71LVT4XDnVffbknv8XgScP4U0Oxtgm+hIw==", + "license": "MIT", "dependencies": { "@algolia/events": "^4.0.1" }, @@ -6170,6 +6504,7 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", "dependencies": { "type-fest": "^0.21.3" }, @@ -6184,6 +6519,7 @@ "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -6198,6 +6534,7 @@ "engines": [ "node >= 0.8.0" ], + "license": "Apache-2.0", "bin": { "ansi-html": "bin/ansi-html" } @@ -6233,6 +6570,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -6244,7 +6582,8 @@ "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==" + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" }, "node_modules/argparse": { "version": "2.0.1", @@ -6254,7 +6593,8 @@ "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==" + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" }, "node_modules/array-union": { "version": "2.1.0", @@ -6330,6 +6670,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", @@ -6366,6 +6707,7 @@ "version": "9.2.1", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", + "license": "MIT", "dependencies": { "find-cache-dir": "^4.0.0", "schema-utils": "^4.0.0" @@ -6382,6 +6724,7 @@ "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==", + "license": "MIT", "dependencies": { "object.assign": "^4.1.0" } @@ -6390,6 +6733,7 @@ "version": "0.4.14", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "license": "MIT", "dependencies": { "@babel/compat-data": "^7.27.7", "@babel/helper-define-polyfill-provider": "^0.6.5", @@ -6423,6 +6767,7 @@ "version": "0.6.5", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "license": "MIT", "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5" }, @@ -6464,9 +6809,10 @@ ] }, "node_modules/baseline-browser-mapping": { - "version": "2.8.18", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.18.tgz", - "integrity": "sha512-UYmTpOBwgPScZpS4A+YbapwWuBwasxvO/2IOHArSsAhL/+ZdmATBXTex3t+l2hXwLVYK382ibr/nKoY9GKe86w==", + "version": "2.8.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.20.tgz", + "integrity": "sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ==", + "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" } @@ -6474,7 +6820,8 @@ "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==" + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "license": "MIT" }, "node_modules/big.js": { "version": "5.2.2", @@ -6488,6 +6835,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", "engines": { "node": ">=8" }, @@ -6504,6 +6852,7 @@ "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -6527,6 +6876,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -6535,19 +6885,34 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } }, + "node_modules/body-parser/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==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.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==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/bonjour-service": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" @@ -6717,9 +7082,9 @@ } }, "node_modules/browserslist": { - "version": "4.26.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz", - "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", + "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", "funding": [ { "type": "opencollective", @@ -6734,12 +7099,13 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.8.9", - "caniuse-lite": "^1.0.30001746", - "electron-to-chromium": "^1.5.227", - "node-releases": "^2.0.21", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.8.19", + "caniuse-lite": "^1.0.30001751", + "electron-to-chromium": "^1.5.238", + "node-releases": "^2.0.26", + "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" @@ -6790,6 +7156,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "license": "MIT", "dependencies": { "run-applescript": "^7.0.0" }, @@ -6928,6 +7295,7 @@ "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==", + "license": "MIT", "dependencies": { "browserslist": "^4.0.0", "caniuse-lite": "^1.0.0", @@ -6952,7 +7320,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/ccount": { "version": "2.0.1", @@ -7034,6 +7403,7 @@ "version": "1.0.0-rc.12", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "license": "MIT", "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", @@ -7054,6 +7424,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", @@ -7070,6 +7441,7 @@ "version": "11.0.3", "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", + "license": "Apache-2.0", "dependencies": { "@chevrotain/cst-dts-gen": "11.0.3", "@chevrotain/gast": "11.0.3", @@ -7083,6 +7455,7 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", + "license": "MIT", "dependencies": { "lodash-es": "^4.17.21" }, @@ -7094,6 +7467,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -7170,6 +7544,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "license": "MIT", "engines": { "node": ">=6" } @@ -7324,12 +7699,14 @@ "node_modules/colord": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "license": "MIT" }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" }, "node_modules/combine-promises": { "version": "1.2.0", @@ -7359,12 +7736,14 @@ "node_modules/common-path-prefix": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==" + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "license": "ISC" }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", "dependencies": { "mime-db": ">= 1.43.0 < 2" }, @@ -7376,6 +7755,7 @@ "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7384,6 +7764,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", @@ -7401,6 +7782,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -7409,6 +7791,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -7416,7 +7799,8 @@ "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==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/compute-gcd": { "version": "1.2.1", @@ -7445,9 +7829,9 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", "license": "MIT" }, "node_modules/config-chain": { @@ -7481,6 +7865,7 @@ "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==", + "license": "MIT", "engines": { "node": ">=0.8" } @@ -7489,6 +7874,7 @@ "version": "3.4.2", "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "license": "MIT", "engines": { "node": "^14.18.0 || >=16.10.0" } @@ -7515,6 +7901,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7528,6 +7915,7 @@ "version": "0.7.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7535,7 +7923,8 @@ "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" }, "node_modules/copy-text-to-clipboard": { "version": "3.2.0", @@ -7552,6 +7941,7 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "license": "MIT", "dependencies": { "fast-glob": "^3.2.11", "glob-parent": "^6.0.1", @@ -7575,6 +7965,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -7586,6 +7977,7 @@ "version": "13.2.2", "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "license": "MIT", "dependencies": { "dir-glob": "^3.0.1", "fast-glob": "^3.3.0", @@ -7604,6 +7996,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -7625,6 +8018,7 @@ "version": "3.46.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.46.0.tgz", "integrity": "sha512-p9hObIIEENxSV8xIu+V68JjSeARg6UVMG5mR+JEUguG3sI6MsiS1njz2jHmyJDvA+8jX/sytkBHup6kxhM9law==", + "license": "MIT", "dependencies": { "browserslist": "^4.26.3" }, @@ -7638,6 +8032,7 @@ "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.46.0.tgz", "integrity": "sha512-NMCW30bHNofuhwLhYPt66OLOKTMbOhgTTatKVbaQC3KRHpTCiRIBYvtshr+NBYSnBxwAFhjW/RfJ0XbIjS16rw==", "hasInstallScript": true, + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" @@ -7803,6 +8198,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "postcss-selector-parser": "^7.0.0" }, @@ -7817,6 +8213,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -7829,6 +8226,7 @@ "version": "7.3.0", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.3.0.tgz", "integrity": "sha512-LQF6N/3vkAMYF4xoHLJfG718HRJh34Z8BnNhd6bosOMIVjMlhuZK5++oZa3uYAgrI5+7x2o27gUqTR2U/KjUOQ==", + "license": "ISC", "engines": { "node": "^14 || ^16 || >=18" }, @@ -7850,6 +8248,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/selector-specificity": "^5.0.0", "postcss-selector-parser": "^7.0.0", @@ -7876,6 +8275,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "engines": { "node": ">=18" }, @@ -7887,6 +8287,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -7899,6 +8300,7 @@ "version": "6.11.0", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "license": "MIT", "dependencies": { "icss-utils": "^5.1.0", "postcss": "^8.4.33", @@ -7933,6 +8335,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==", + "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "cssnano": "^6.0.1", @@ -7986,6 +8389,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "engines": { "node": ">=18" }, @@ -8044,12 +8448,14 @@ "type": "github", "url": "https://github.com/sponsors/csstools" } - ] + ], + "license": "MIT-0" }, "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==", + "license": "MIT", "bin": { "cssesc": "bin/cssesc" }, @@ -8061,6 +8467,7 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", + "license": "MIT", "dependencies": { "cssnano-preset-default": "^6.1.2", "lilconfig": "^3.1.1" @@ -8080,6 +8487,7 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz", "integrity": "sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ==", + "license": "MIT", "dependencies": { "autoprefixer": "^10.4.19", "browserslist": "^4.23.0", @@ -8100,6 +8508,7 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", + "license": "MIT", "dependencies": { "browserslist": "^4.23.0", "css-declaration-sorter": "^7.2.0", @@ -8143,6 +8552,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", + "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -8186,9 +8596,9 @@ "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, "node_modules/cytoscape": { - "version": "3.30.2", - "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.30.2.tgz", - "integrity": "sha512-oICxQsjW8uSaRmn4UK/jkczKOqTrVqt5/1WL0POiJUT2EKNc9STM4hYFHv917yu55aTBMFNRzymlJhVAiWPCxw==", + "version": "3.33.1", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", + "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", "license": "MIT", "engines": { "node": ">=0.10" @@ -8412,18 +8822,6 @@ "node": ">= 10" } }, - "node_modules/d3-dsv/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/d3-ease": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", @@ -8706,9 +9104,10 @@ } }, "node_modules/dayjs": { - "version": "1.11.13", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", - "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", + "license": "MIT" }, "node_modules/debounce": { "version": "1.2.1", @@ -8719,6 +9118,7 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -8788,6 +9188,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "license": "MIT", "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" @@ -8803,6 +9204,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "license": "MIT", "engines": { "node": ">=18" }, @@ -8838,6 +9240,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "license": "MIT", "engines": { "node": ">=8" } @@ -8871,6 +9274,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -8896,6 +9300,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -8916,7 +9321,8 @@ "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==" + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "license": "MIT" }, "node_modules/detect-package-manager": { "version": "3.0.2", @@ -8993,6 +9399,7 @@ "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "license": "MIT", "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" }, @@ -10114,9 +10521,10 @@ } }, "node_modules/dompurify": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", - "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz", + "integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==", + "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" } @@ -10192,12 +10600,14 @@ "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==" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.237", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.237.tgz", - "integrity": "sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==" + "version": "1.5.240", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.240.tgz", + "integrity": "sha512-OBwbZjWgrCOH+g6uJsA2/7Twpas2OlepS9uvByJjR2datRDuKGYeD+nP8lBBks2qnB7bGJNHDUx7c/YLaT3QMQ==", + "license": "ISC" }, "node_modules/elliptic": { "version": "6.6.1", @@ -10249,6 +10659,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -10515,6 +10926,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -10542,7 +10954,8 @@ "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" }, "node_modules/events": { "version": "3.3.0", @@ -10556,6 +10969,7 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", "engines": { "node": ">=18.0.0" } @@ -10600,6 +11014,7 @@ "version": "4.21.2", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -10645,6 +11060,7 @@ "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" }, @@ -10656,6 +11072,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -10663,21 +11080,30 @@ "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==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/express/node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" }, "node_modules/express/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==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, + "node_modules/exsolve": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "license": "MIT" + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -10748,6 +11174,7 @@ "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", "dependencies": { "websocket-driver": ">=0.5.1" }, @@ -10759,6 +11186,7 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz", "integrity": "sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==", + "license": "MIT", "dependencies": { "xml-js": "^1.6.11" }, @@ -10770,6 +11198,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "license": "MIT", "dependencies": { "escape-string-regexp": "^1.0.5" }, @@ -10784,6 +11213,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", "engines": { "node": ">=0.8.0" } @@ -10888,6 +11318,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", @@ -10905,6 +11336,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -10912,12 +11344,14 @@ "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==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/find-cache-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", + "license": "MIT", "dependencies": { "common-path-prefix": "^3.0.0", "pkg-dir": "^7.0.0" @@ -10933,6 +11367,7 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "license": "MIT", "dependencies": { "locate-path": "^7.1.0", "path-exists": "^5.0.0" @@ -10962,6 +11397,7 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -11030,6 +11466,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -11038,6 +11475,7 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "license": "MIT", "engines": { "node": "*" }, @@ -11050,6 +11488,7 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -11077,6 +11516,7 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -11201,6 +11641,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==", + "license": "Apache-2.0", "engines": { "node": ">=10.0" }, @@ -11383,7 +11824,8 @@ "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==" + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "license": "MIT" }, "node_modules/has-flag": { "version": "4.0.0", @@ -11684,6 +12126,7 @@ "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "license": "MIT", "dependencies": { "inherits": "^2.0.1", "obuf": "^1.0.0", @@ -11694,12 +12137,14 @@ "node_modules/hpack.js/node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" }, "node_modules/hpack.js/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -11713,12 +12158,14 @@ "node_modules/hpack.js/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==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" }, "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==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } @@ -11732,6 +12179,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", + "license": "MIT", "dependencies": { "camel-case": "^4.1.2", "clean-css": "~5.3.2", @@ -11752,6 +12200,7 @@ "version": "10.0.1", "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "license": "MIT", "engines": { "node": ">=14" } @@ -11846,6 +12295,7 @@ "url": "https://github.com/sponsors/fb55" } ], + "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", @@ -11861,12 +12311,14 @@ "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==" + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "license": "MIT" }, "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==", + "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -11881,12 +12333,14 @@ "node_modules/http-parser-js": { "version": "0.5.10", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", - "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==" + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "license": "MIT" }, "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==", + "license": "MIT", "dependencies": { "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", @@ -11900,6 +12354,7 @@ "version": "2.0.9", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "license": "MIT", "dependencies": { "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", @@ -11923,6 +12378,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -11981,16 +12437,18 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "license": "MIT", "engines": { "node": ">=10.18" } }, "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==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" @@ -12000,6 +12458,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "license": "ISC", "engines": { "node": "^10 || ^12 || >= 14" }, @@ -12038,6 +12497,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz", "integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==", + "license": "MIT", "bin": { "image-size": "bin/image-size.js" }, @@ -12094,6 +12554,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", "engines": { "node": ">=8" } @@ -12102,6 +12563,7 @@ "version": "0.2.0-alpha.45", "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.45.tgz", "integrity": "sha512-uyH0zfr1erU1OohLk0fT4Rrb94AOhguWNOcD9uGrSpRvNB+6gZXUoJX5J0NtvzBO10YZ9PgvA4NFgt+fYg8ojw==", + "license": "MIT", "engines": { "node": ">=12" } @@ -12155,10 +12617,20 @@ "loose-envify": "^1.0.0" } }, + "node_modules/ionicons": { + "version": "8.0.13", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-8.0.13.tgz", + "integrity": "sha512-2QQVyG2P4wszne79jemMjWYLp0DBbDhr4/yFroPCxvPP1wtMxgdIV3l5n+XZ5E9mgoXU79w7yTWpm2XzJsISxQ==", + "license": "MIT", + "dependencies": { + "@stencil/core": "^4.35.3" + } + }, "node_modules/ipaddr.js": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "license": "MIT", "engines": { "node": ">= 10" } @@ -12209,6 +12681,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" }, @@ -12264,6 +12737,7 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", "dependencies": { "hasown": "^2.0.2" }, @@ -12287,6 +12761,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", "bin": { "is-docker": "cli.js" }, @@ -12359,6 +12834,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", "dependencies": { "is-docker": "^3.0.0" }, @@ -12376,6 +12852,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", "bin": { "is-docker": "cli.js" }, @@ -12420,6 +12897,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==", + "license": "MIT", "engines": { "node": ">=16" }, @@ -12524,6 +13002,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", "dependencies": { "is-docker": "^2.0.0" }, @@ -12575,6 +13054,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -12591,6 +13071,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "license": "MIT", "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", @@ -12605,6 +13086,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -12699,7 +13181,8 @@ "node_modules/json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" }, "node_modules/json-schema-compare": { "version": "0.2.2", @@ -12750,13 +13233,14 @@ } }, "node_modules/katex": { - "version": "0.16.22", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz", - "integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==", + "version": "0.16.25", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.25.tgz", + "integrity": "sha512-woHRUZ/iF23GBP1dkDQMh1QBad9dmr8/PAwNA54VrSOVYgI12MAcE14TqnDdQOdzyEonGzMepYnqBMYdsoAr8Q==", "funding": [ "https://opencollective.com/katex", "https://github.com/sponsors/katex" ], + "license": "MIT", "dependencies": { "commander": "^8.3.0" }, @@ -12768,6 +13252,7 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", "engines": { "node": ">= 12" } @@ -12811,6 +13296,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz", "integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==", + "license": "MIT", "dependencies": { "chevrotain": "~11.0.3", "chevrotain-allstar": "~0.3.0", @@ -12840,6 +13326,7 @@ "version": "2.11.1", "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.11.1.tgz", "integrity": "sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg==", + "license": "MIT", "dependencies": { "picocolors": "^1.1.1", "shell-quote": "^1.8.3" @@ -12863,6 +13350,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", "engines": { "node": ">=14" }, @@ -12910,13 +13398,14 @@ } }, "node_modules/local-pkg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.0.0.tgz", - "integrity": "sha512-bbgPw/wmroJsil/GgL4qjDzs5YLTBMQ99weRsok1XCDccQeehbHA/I1oRvk2NPtr7KGZgT/Y5tPRnAtMqeG2Kg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", "license": "MIT", "dependencies": { - "mlly": "^1.7.3", - "pkg-types": "^1.3.0" + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" }, "engines": { "node": ">=14" @@ -12929,6 +13418,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "license": "MIT", "dependencies": { "p-locate": "^6.0.0" }, @@ -12963,12 +13453,14 @@ "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==" + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "license": "MIT" }, "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==" + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" }, "node_modules/longest-streak": { "version": "3.1.0", @@ -13041,6 +13533,7 @@ "version": "16.4.1", "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.1.tgz", "integrity": "sha512-ntROs7RaN3EvWfy3EZi14H4YxmT6A5YvywfhO+0pm+cH/dnSQRmdAmoFIc3B9aiwTehyk7pESH4ofyBY+V5hZg==", + "license": "MIT", "bin": { "marked": "bin/marked.js" }, @@ -13520,6 +14013,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -13528,6 +14022,7 @@ "version": "4.49.0", "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.49.0.tgz", "integrity": "sha512-L9uC9vGuc4xFybbdOpRLoOAOq1YEBBsocCs5NVW32DfU+CZWWIn3OVF+lB8Gp4ttBVSMazwrTrjv8ussX/e3VQ==", + "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/json-pack": "^1.11.0", "@jsonjoy.com/util": "^1.9.0", @@ -13550,6 +14045,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -13568,12 +14064,13 @@ } }, "node_modules/mermaid": { - "version": "11.10.1", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.10.1.tgz", - "integrity": "sha512-0PdeADVWURz7VMAX0+MiMcgfxFKY4aweSGsjgFihe3XlMKNqmai/cugMrqTd3WNHM93V+K+AZL6Wu6tB5HmxRw==", + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.0.tgz", + "integrity": "sha512-ZudVx73BwrMJfCFmSSJT84y6u5brEoV8DOItdHomNLz32uBjNrelm7mg95X7g+C6UoQH/W6mBLGDEDv73JdxBg==", + "license": "MIT", "dependencies": { - "@braintree/sanitize-url": "^7.0.4", - "@iconify/utils": "^2.1.33", + "@braintree/sanitize-url": "^7.1.1", + "@iconify/utils": "^3.0.1", "@mermaid-js/parser": "^0.6.2", "@types/d3": "^7.4.3", "cytoscape": "^3.29.3", @@ -13582,12 +14079,12 @@ "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.11", - "dayjs": "^1.11.13", + "dayjs": "^1.11.18", "dompurify": "^3.2.5", "katex": "^0.16.22", "khroma": "^2.1.0", "lodash-es": "^4.17.21", - "marked": "^16.0.0", + "marked": "^16.2.1", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", @@ -13602,6 +14099,7 @@ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], + "license": "MIT", "bin": { "uuid": "dist/esm/bin/uuid" } @@ -13610,6 +14108,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -15318,6 +15817,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -15375,6 +15875,7 @@ "version": "2.9.4", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.4.tgz", "integrity": "sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ==", + "license": "MIT", "dependencies": { "schema-utils": "^4.0.0", "tapable": "^2.2.1" @@ -15428,15 +15929,32 @@ } }, "node_modules/mlly": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", - "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", "license": "MIT", "dependencies": { - "acorn": "^8.14.0", - "pathe": "^2.0.1", - "pkg-types": "^1.3.0", - "ufo": "^1.5.4" + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" } }, "node_modules/mri": { @@ -15466,6 +15984,7 @@ "version": "7.2.5", "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "license": "MIT", "dependencies": { "dns-packet": "^5.2.2", "thunky": "^1.0.2" @@ -15502,6 +16021,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -15513,6 +16033,7 @@ "version": "0.6.4", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -15593,6 +16114,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { "node": ">= 6.13.0" } @@ -15667,14 +16189,16 @@ } }, "node_modules/node-releases": { - "version": "2.0.25", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.25.tgz", - "integrity": "sha512-4auku8B/vw5psvTiiN9j1dAOsXvMoGqJuKJcR+dTdqiXEK20mMTk1UEo3HS16LeGQsVG6+qKTPM9u/qQ2LqATA==" + "version": "2.0.26", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz", + "integrity": "sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==", + "license": "MIT" }, "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==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -15683,6 +16207,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -15701,7 +16226,8 @@ "node_modules/nprogress": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", - "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==" + "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==", + "license": "MIT" }, "node_modules/nth-check": { "version": "2.1.1", @@ -15718,6 +16244,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-4.0.1.tgz", "integrity": "sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==", + "license": "MIT", "dependencies": { "loader-utils": "^2.0.0", "schema-utils": "^3.0.0" @@ -15737,6 +16264,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -15752,6 +16280,7 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "license": "MIT", "peerDependencies": { "ajv": "^6.9.1" } @@ -15759,12 +16288,14 @@ "node_modules/null-loader/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==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" }, "node_modules/null-loader/node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -15932,12 +16463,14 @@ "node_modules/obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "license": "MIT" }, "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==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -15949,6 +16482,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -15979,6 +16513,7 @@ "version": "8.4.2", "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "license": "MIT", "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", @@ -16066,6 +16601,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "license": "MIT", "engines": { "node": ">=4" } @@ -16074,6 +16610,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "license": "MIT", "dependencies": { "yocto-queue": "^1.0.0" }, @@ -16088,6 +16625,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "license": "MIT", "dependencies": { "p-limit": "^4.0.0" }, @@ -16102,6 +16640,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "license": "MIT", "dependencies": { "aggregate-error": "^3.0.0" }, @@ -16116,6 +16655,7 @@ "version": "6.6.2", "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "license": "MIT", "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" @@ -16131,6 +16671,7 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "license": "MIT", "dependencies": { "@types/retry": "0.12.2", "is-network-error": "^1.0.0", @@ -16147,6 +16688,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "license": "MIT", "dependencies": { "p-finally": "^1.0.0" }, @@ -16177,9 +16719,9 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" }, "node_modules/package-manager-detector": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.9.tgz", - "integrity": "sha512-+vYvA/Y31l8Zk8dwxHhL3JfTuHPm6tlxM2A3GeQyl7ovYnSp1+mzAxClxaOr0qO1TtPxbQxetI7v5XqKLJZk7Q==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.5.0.tgz", + "integrity": "sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw==", "license": "MIT" }, "node_modules/pako": { @@ -16284,6 +16826,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "license": "MIT", "dependencies": { "domhandler": "^5.0.3", "parse5": "^7.0.0" @@ -16296,6 +16839,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -16333,6 +16877,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } @@ -16504,6 +17049,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "license": "MIT", "dependencies": { "find-up": "^6.3.0" }, @@ -16515,14 +17061,14 @@ } }, "node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", "license": "MIT", "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" } }, "node_modules/pluralize": { @@ -16575,6 +17121,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -16598,6 +17145,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT", "dependencies": { "postcss-selector-parser": "^7.0.0" }, @@ -16612,6 +17160,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -16624,6 +17173,7 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "license": "MIT", "dependencies": { "postcss-selector-parser": "^6.0.11", "postcss-value-parser": "^4.2.0" @@ -16639,6 +17189,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -16663,6 +17214,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", @@ -16691,6 +17243,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT", "dependencies": { "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" @@ -16716,6 +17269,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" @@ -16731,6 +17285,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", + "license": "MIT", "dependencies": { "browserslist": "^4.23.0", "caniuse-api": "^3.0.0", @@ -16748,6 +17303,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", + "license": "MIT", "dependencies": { "browserslist": "^4.23.0", "postcss-value-parser": "^4.2.0" @@ -16773,6 +17329,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT", "dependencies": { "@csstools/cascade-layer-name-parser": "^2.0.5", "@csstools/css-parser-algorithms": "^3.0.5", @@ -16800,6 +17357,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT", "dependencies": { "@csstools/cascade-layer-name-parser": "^2.0.5", "@csstools/css-parser-algorithms": "^3.0.5", @@ -16828,6 +17386,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT", "dependencies": { "@csstools/cascade-layer-name-parser": "^2.0.5", "@csstools/css-parser-algorithms": "^3.0.5", @@ -16845,6 +17404,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -16867,6 +17427,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "postcss-selector-parser": "^7.0.0" }, @@ -16881,6 +17442,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -16893,6 +17455,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", + "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -16904,6 +17467,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", + "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -16915,6 +17479,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -16926,6 +17491,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -16937,6 +17503,7 @@ "version": "6.0.5", "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz", "integrity": "sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA==", + "license": "MIT", "dependencies": { "postcss-selector-parser": "^6.0.16" }, @@ -16961,6 +17528,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0", @@ -16987,6 +17555,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "postcss-selector-parser": "^7.0.0" }, @@ -17001,6 +17570,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -17023,6 +17593,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "postcss-selector-parser": "^7.0.0" }, @@ -17037,6 +17608,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -17049,6 +17621,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "license": "MIT", "peerDependencies": { "postcss": "^8.1.0" } @@ -17067,6 +17640,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "engines": { "node": ">=18" }, @@ -17088,6 +17662,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" @@ -17113,6 +17688,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", @@ -17131,6 +17707,7 @@ "version": "7.3.4", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz", "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==", + "license": "MIT", "dependencies": { "cosmiconfig": "^8.3.5", "jiti": "^1.20.0", @@ -17162,6 +17739,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -17176,6 +17754,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-6.0.3.tgz", "integrity": "sha512-1oIoAsODUs6IHQZkLQGO15uGEbK3EAl5wi9SS8hs45VgsxQfMnxvt+L+zIr7ifZFIH14cfAeVe2uCTa+SPRa3g==", + "license": "MIT", "dependencies": { "cssnano-utils": "^4.0.2", "postcss-value-parser": "^4.2.0" @@ -17191,6 +17770,7 @@ "version": "6.0.5", "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", "stylehacks": "^6.1.1" @@ -17206,6 +17786,7 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", + "license": "MIT", "dependencies": { "browserslist": "^4.23.0", "caniuse-api": "^3.0.0", @@ -17223,6 +17804,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -17237,6 +17819,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", + "license": "MIT", "dependencies": { "colord": "^2.9.3", "cssnano-utils": "^4.0.2", @@ -17253,6 +17836,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", + "license": "MIT", "dependencies": { "browserslist": "^4.23.0", "cssnano-utils": "^4.0.2", @@ -17269,6 +17853,7 @@ "version": "6.0.4", "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", + "license": "MIT", "dependencies": { "postcss-selector-parser": "^6.0.16" }, @@ -17283,6 +17868,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "license": "ISC", "engines": { "node": "^10 || ^12 || >= 14" }, @@ -17294,6 +17880,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "license": "MIT", "dependencies": { "icss-utils": "^5.0.0", "postcss-selector-parser": "^7.0.0", @@ -17310,6 +17897,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -17322,6 +17910,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "license": "ISC", "dependencies": { "postcss-selector-parser": "^7.0.0" }, @@ -17336,6 +17925,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -17348,6 +17938,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "license": "ISC", "dependencies": { "icss-utils": "^5.0.0" }, @@ -17372,6 +17963,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/selector-resolve-nested": "^3.1.0", "@csstools/selector-specificity": "^5.0.0", @@ -17398,6 +17990,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "engines": { "node": ">=18" }, @@ -17419,6 +18012,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "engines": { "node": ">=18" }, @@ -17430,6 +18024,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -17442,6 +18037,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", + "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -17453,6 +18049,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -17467,6 +18064,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -17481,6 +18079,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -17495,6 +18094,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -17509,6 +18109,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -17523,6 +18124,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", + "license": "MIT", "dependencies": { "browserslist": "^4.23.0", "postcss-value-parser": "^4.2.0" @@ -17538,6 +18140,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -17552,6 +18155,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -17576,6 +18180,7 @@ "url": "https://liberapay.com/mrcgrtz" } ], + "license": "MIT", "engines": { "node": ">=18" }, @@ -17587,6 +18192,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", + "license": "MIT", "dependencies": { "cssnano-utils": "^4.0.2", "postcss-value-parser": "^4.2.0" @@ -17612,6 +18218,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -17626,6 +18233,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "license": "MIT", "peerDependencies": { "postcss": "^8" } @@ -17644,6 +18252,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -17668,6 +18277,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "@csstools/postcss-alpha-function": "^1.0.1", "@csstools/postcss-cascade-layers": "^5.0.2", @@ -17758,6 +18368,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { "postcss-selector-parser": "^7.0.0" }, @@ -17772,6 +18383,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -17784,6 +18396,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-6.0.3.tgz", "integrity": "sha512-G3yCqZDpsNPoQgbDUy3T0E6hqOQ5xigUtBQyrmq3tn2GxlyiL0yyl7H+T8ulQR6kOcHJ9t7/9H4/R2tv8tJbMA==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -17798,6 +18411,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", + "license": "MIT", "dependencies": { "browserslist": "^4.23.0", "caniuse-api": "^3.0.0" @@ -17813,6 +18427,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -17827,6 +18442,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "license": "MIT", "peerDependencies": { "postcss": "^8.0.3" } @@ -17845,6 +18461,7 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT", "dependencies": { "postcss-selector-parser": "^7.0.0" }, @@ -17859,6 +18476,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -17871,6 +18489,7 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -17883,6 +18502,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-5.2.0.tgz", "integrity": "sha512-AZ5fDMLD8SldlAYlvi8NIqo0+Z8xnXU2ia0jxmuhxAU+Lqt9K+AlmLNJ/zWEnE9x+Zx3qL3+1K20ATgNOr3fAA==", + "license": "MIT", "dependencies": { "sort-css-media-queries": "2.2.0" }, @@ -17897,6 +18517,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", "svgo": "^3.2.0" @@ -17912,6 +18533,7 @@ "version": "6.0.4", "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", + "license": "MIT", "dependencies": { "postcss-selector-parser": "^6.0.16" }, @@ -17925,12 +18547,14 @@ "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==" + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" }, "node_modules/postcss-zindex": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-6.0.2.tgz", "integrity": "sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg==", + "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -17981,17 +18605,6 @@ "node": ">=10" } }, - "node_modules/postman-collection/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/postman-collection/node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -18058,6 +18671,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz", "integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==", + "license": "MIT", "engines": { "node": ">=4" } @@ -18078,6 +18692,7 @@ "version": "1.30.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", "engines": { "node": ">=6" } @@ -18135,6 +18750,7 @@ "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==", + "license": "MIT", "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -18147,6 +18763,7 @@ "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==", + "license": "MIT", "engines": { "node": ">= 0.10" } @@ -18202,6 +18819,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, "node_modules/querystring-es3": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", @@ -18269,6 +18902,7 @@ "version": "2.5.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -18283,10 +18917,23 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/raw-body/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==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -18377,6 +19024,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-2.5.0.tgz", "integrity": "sha512-tk7o7QG9oYyELWHL8xiMQ8x4WzjCzbWNyig3uexmkLb54r8jO0yH3WCWx8UZS0c49eSA4QUmG5caiRJ8fAn58g==", + "license": "MIT", "engines": { "node": ">=18" }, @@ -19191,6 +19839,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", "dependencies": { "picomatch": "^2.2.1" }, @@ -19558,6 +20207,7 @@ "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "license": "MIT", "engines": { "node": ">=0.10" } @@ -19589,7 +20239,8 @@ "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==" + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" }, "node_modules/reselect": { "version": "4.1.8", @@ -19597,11 +20248,12 @@ "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==" }, "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -19651,6 +20303,7 @@ "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", "engines": { "node": ">= 4" } @@ -19695,6 +20348,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", + "license": "MIT", "dependencies": { "escalade": "^3.1.1", "picocolors": "^1.0.0", @@ -19712,6 +20366,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "license": "MIT", "engines": { "node": ">=18" }, @@ -19878,7 +20533,8 @@ "node_modules/sax": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", - "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "license": "ISC" }, "node_modules/scheduler": { "version": "0.23.0", @@ -19891,12 +20547,14 @@ "node_modules/schema-dts": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/schema-dts/-/schema-dts-1.1.5.tgz", - "integrity": "sha512-RJr9EaCmsLzBX2NDiO5Z3ux2BVosNZN5jo0gWgsyKvxKIUL5R3swNvoorulAeL9kLB0iTSX7V6aokhla2m7xbg==" + "integrity": "sha512-RJr9EaCmsLzBX2NDiO5Z3ux2BVosNZN5jo0gWgsyKvxKIUL5R3swNvoorulAeL9kLB0iTSX7V6aokhla2m7xbg==", + "license": "Apache-2.0" }, "node_modules/schema-utils": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -19915,6 +20573,7 @@ "version": "2.17.3", "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "license": "MIT", "peer": true }, "node_modules/section-matter": { @@ -19932,12 +20591,14 @@ "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==" + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "license": "MIT" }, "node_modules/selfsigned": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "license": "MIT", "dependencies": { "@types/node-forge": "^1.3.0", "node-forge": "^1" @@ -19975,6 +20636,7 @@ "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -19998,6 +20660,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -20005,12 +20668,14 @@ "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==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/send/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==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -20019,6 +20684,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -20055,6 +20721,7 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "license": "MIT", "dependencies": { "accepts": "~1.3.4", "batch": "0.6.1", @@ -20072,6 +20739,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -20080,6 +20748,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -20088,6 +20757,7 @@ "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "license": "MIT", "dependencies": { "depd": "~1.1.2", "inherits": "2.0.3", @@ -20101,22 +20771,26 @@ "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==" + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC" }, "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==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "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==" + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "license": "ISC" }, "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==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -20125,6 +20799,7 @@ "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", @@ -20159,25 +20834,19 @@ "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" }, "node_modules/sha.js": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", - "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dependencies": { - "inherits": "^2.0.4", - "safe-buffer": "^5.2.1", - "to-buffer": "^1.2.0" + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" }, "bin": { "sha.js": "bin.js" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/shallow-clone": { @@ -20219,6 +20888,7 @@ "version": "1.8.3", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -20334,6 +21004,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.2.tgz", "integrity": "sha512-ARCqzHJ0p4gWt+j7NlU5eDlIO9+Rkr/JhPFZKKQ1l5GCus7rJH4UdrlVAh0xC/gDS/Qir2UMxqYNHtsKr2rpCw==", + "license": "MIT", "dependencies": { "@types/node": "^17.0.5", "@types/sax": "^1.2.1", @@ -20351,7 +21022,8 @@ "node_modules/sitemap/node_modules/@types/node": { "version": "17.0.45", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==" + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "license": "MIT" }, "node_modules/skin-tone": { "version": "2.0.0", @@ -20385,6 +21057,7 @@ "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "license": "MIT", "dependencies": { "faye-websocket": "^0.11.3", "uuid": "^8.3.2", @@ -20395,6 +21068,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-2.2.0.tgz", "integrity": "sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA==", + "license": "MIT", "engines": { "node": ">= 6.3.0" } @@ -20445,6 +21119,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "license": "MIT", "dependencies": { "debug": "^4.1.0", "handle-thing": "^2.0.0", @@ -20460,6 +21135,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "license": "MIT", "dependencies": { "debug": "^4.1.0", "detect-node": "^2.0.4", @@ -20478,6 +21154,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz", "integrity": "sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -20489,6 +21166,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -20496,7 +21174,8 @@ "node_modules/std-env": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==" + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "license": "MIT" }, "node_modules/stream-browserify": { "version": "3.0.0", @@ -20655,6 +21334,7 @@ "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==", + "license": "MIT", "engines": { "node": ">=8" }, @@ -20674,6 +21354,7 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", + "license": "MIT", "dependencies": { "browserslist": "^4.23.0", "postcss-selector-parser": "^6.0.16" @@ -20850,6 +21531,7 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.6.tgz", "integrity": "sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==", + "license": "MIT", "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.4.0" @@ -21016,6 +21698,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.5.0.tgz", "integrity": "sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==", + "license": "MIT", "engines": { "node": ">=10.18" }, @@ -21031,6 +21714,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz", "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==", + "license": "MIT", "engines": { "node": ">=18" }, @@ -21041,7 +21725,8 @@ "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "license": "MIT" }, "node_modules/timers-browserify": { "version": "2.0.12", @@ -21065,15 +21750,16 @@ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", + "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", "license": "MIT" }, "node_modules/tinypool": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" } @@ -21113,6 +21799,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", "engines": { "node": ">=0.6" } @@ -21134,6 +21821,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", + "license": "Apache-2.0", "engines": { "node": ">=10.0" }, @@ -21202,6 +21890,7 @@ "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -21214,6 +21903,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -21222,6 +21912,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -21266,9 +21957,9 @@ } }, "node_modules/ufo": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", - "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", "license": "MIT" }, "node_modules/undici-types": { @@ -21461,14 +22152,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", "funding": [ { "type": "opencollective", @@ -21483,6 +22175,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" @@ -21699,6 +22392,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } @@ -21737,6 +22431,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", "engines": { "node": ">= 0.4.0" } @@ -21817,6 +22512,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -21870,6 +22566,7 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -21878,6 +22575,7 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "license": "MIT", "dependencies": { "vscode-languageserver-protocol": "3.17.5" }, @@ -21889,6 +22587,7 @@ "version": "3.17.5", "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", "dependencies": { "vscode-jsonrpc": "8.2.0", "vscode-languageserver-types": "3.17.5" @@ -21897,17 +22596,20 @@ "node_modules/vscode-languageserver-textdocument": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", - "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==" + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT" }, "node_modules/vscode-languageserver-types": { "version": "3.17.5", "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", - "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" }, "node_modules/vscode-uri": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", - "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==" + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "license": "MIT" }, "node_modules/warning": { "version": "4.0.3", @@ -21933,6 +22635,7 @@ "version": "1.7.3", "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "license": "MIT", "dependencies": { "minimalistic-assert": "^1.0.0" } @@ -22033,6 +22736,7 @@ "version": "7.4.5", "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.5.tgz", "integrity": "sha512-uxQ6YqGdE4hgDKNf7hUiPXOdtkXvBJXrfEGYSx7P7LC8hnUYGK70X6xQXUvXeNyBDDcsiQXpG2m3G9vxowaEuA==", + "license": "MIT", "dependencies": { "colorette": "^2.0.10", "memfs": "^4.43.1", @@ -22061,6 +22765,7 @@ "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -22069,6 +22774,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", "dependencies": { "mime-db": "^1.54.0" }, @@ -22080,6 +22786,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -22088,6 +22795,7 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.2.tgz", "integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==", + "license": "MIT", "dependencies": { "@types/bonjour": "^3.5.13", "@types/connect-history-api-fallback": "^1.5.4", @@ -22144,6 +22852,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -22155,6 +22864,7 @@ "version": "10.2.0", "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "license": "MIT", "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", @@ -22172,6 +22882,7 @@ "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -22277,6 +22988,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-6.0.1.tgz", "integrity": "sha512-TnErZpmuKdwWBdMoexjio3KKX6ZtoKHRVvLIU0A47R0VVBDtx3ZyOJDktgYixhoJokZTYTt1Z37OkO9pnGJa9Q==", + "license": "MIT", "dependencies": { "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", @@ -22297,12 +23009,14 @@ "node_modules/webpackbar/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==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" }, "node_modules/webpackbar/node_modules/markdown-table": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", + "license": "MIT", "dependencies": { "repeat-string": "^1.0.0" }, @@ -22315,6 +23029,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -22328,6 +23043,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -22344,6 +23060,7 @@ "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "license": "Apache-2.0", "dependencies": { "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", @@ -22357,6 +23074,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", "engines": { "node": ">=0.8.0" } @@ -22548,6 +23266,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "license": "MIT", "dependencies": { "is-wsl": "^3.1.0" }, @@ -22562,6 +23281,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "license": "MIT", "dependencies": { "is-inside-container": "^1.0.0" }, @@ -22598,6 +23318,7 @@ "version": "1.6.11", "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "license": "MIT", "dependencies": { "sax": "^1.2.4" }, @@ -22694,6 +23415,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", + "license": "MIT", "engines": { "node": ">=12.20" }, @@ -22705,6 +23427,7 @@ "version": "4.1.12", "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -22721,9 +23444,9 @@ }, "dependencies": { "@ai-sdk/gateway": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.0.tgz", - "integrity": "sha512-Gj0PuawK7NkZuyYgO/h5kDK/l6hFOjhLdTq3/Lli1FTl47iGmwhH1IZQpAL3Z09BeFYWakcwUmn02ovIm2wy9g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.1.tgz", + "integrity": "sha512-vPVIbnP35ZnayS937XLo85vynR85fpBQWHCdUweq7apzqFOTU2YkUd4V3msebEHbQ2Zro60ZShDDy9SMiyWTqA==", "requires": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.12", @@ -22749,25 +23472,25 @@ } }, "@ai-sdk/react": { - "version": "2.0.76", - "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-2.0.76.tgz", - "integrity": "sha512-ggAPzyaKJTqUWigpxMzI5DuC0Y3iEpDUPCgz6/6CpnKZY/iok+x5xiZhDemeaP0ILw5IQekV0kdgBR8JPgI8zQ==", + "version": "2.0.78", + "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-2.0.78.tgz", + "integrity": "sha512-f5inDBHJyUEzbtNxc9HiTxbcGjtot0uuc//0/khGrl8IZlLxw+yTxO/T1Qq95Rw5QPwTx9/Aw7wIZei3qws9hA==", "requires": { "@ai-sdk/provider-utils": "3.0.12", - "ai": "5.0.76", + "ai": "5.0.78", "swr": "^2.2.5", "throttleit": "2.1.0" } }, "@algolia/abtesting": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.6.1.tgz", - "integrity": "sha512-wV/gNRkzb7sI9vs1OneG129hwe3Q5zPj7zigz3Ps7M5Lpo2hSorrOnXNodHEOV+yXE/ks4Pd+G3CDFIjFTWhMQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.7.0.tgz", + "integrity": "sha512-hOEItTFOvNLI6QX6TSGu7VE4XcUcdoKZT8NwDY+5mWwu87rGhkjlY7uesKTInlg6Sh8cyRkDBYRumxbkoBbBhA==", "requires": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.41.0", + "@algolia/requester-browser-xhr": "5.41.0", + "@algolia/requester-fetch": "5.41.0", + "@algolia/requester-node-http": "5.41.0" } }, "@algolia/autocomplete-core": { @@ -22794,74 +23517,74 @@ "requires": {} }, "@algolia/client-abtesting": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.40.1.tgz", - "integrity": "sha512-cxKNATPY5t+Mv8XAVTI57altkaPH+DZi4uMrnexPxPHODMljhGYY+GDZyHwv9a+8CbZHcY372OkxXrDMZA4Lnw==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.41.0.tgz", + "integrity": "sha512-iRuvbEyuHCAhIMkyzG3tfINLxTS7mSKo7q8mQF+FbQpWenlAlrXnfZTN19LRwnVjx0UtAdZq96ThMWGS6cQ61A==", "requires": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.41.0", + "@algolia/requester-browser-xhr": "5.41.0", + "@algolia/requester-fetch": "5.41.0", + "@algolia/requester-node-http": "5.41.0" } }, "@algolia/client-analytics": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.40.1.tgz", - "integrity": "sha512-XP008aMffJCRGAY8/70t+hyEyvqqV7YKm502VPu0+Ji30oefrTn2al7LXkITz7CK6I4eYXWRhN6NaIUi65F1OA==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.41.0.tgz", + "integrity": "sha512-OIPVbGfx/AO8l1V70xYTPSeTt/GCXPEl6vQICLAXLCk9WOUbcLGcy6t8qv0rO7Z7/M/h9afY6Af8JcnI+FBFdQ==", "requires": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.41.0", + "@algolia/requester-browser-xhr": "5.41.0", + "@algolia/requester-fetch": "5.41.0", + "@algolia/requester-node-http": "5.41.0" } }, "@algolia/client-common": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.40.1.tgz", - "integrity": "sha512-gWfQuQUBtzUboJv/apVGZMoxSaB0M4Imwl1c9Ap+HpCW7V0KhjBddqF2QQt5tJZCOFsfNIgBbZDGsEPaeKUosw==" + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.41.0.tgz", + "integrity": "sha512-8Mc9niJvfuO8dudWN5vSUlYkz7U3M3X3m1crDLc9N7FZrIVoNGOUETPk3TTHviJIh9y6eKZKbq1hPGoGY9fqPA==" }, "@algolia/client-insights": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.40.1.tgz", - "integrity": "sha512-RTLjST/t+lsLMouQ4zeLJq2Ss+UNkLGyNVu+yWHanx6kQ3LT5jv8UvPwyht9s7R6jCPnlSI77WnL80J32ZuyJg==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.41.0.tgz", + "integrity": "sha512-vXzvCGZS6Ixxn+WyzGUVDeR3HO/QO5POeeWy1kjNJbEf6f+tZSI+OiIU9Ha+T3ntV8oXFyBEuweygw4OLmgfiQ==", "requires": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.41.0", + "@algolia/requester-browser-xhr": "5.41.0", + "@algolia/requester-fetch": "5.41.0", + "@algolia/requester-node-http": "5.41.0" } }, "@algolia/client-personalization": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.40.1.tgz", - "integrity": "sha512-2FEK6bUomBzEYkTKzD0iRs7Ljtjb45rKK/VSkyHqeJnG+77qx557IeSO0qVFE3SfzapNcoytTofnZum0BQ6r3Q==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.41.0.tgz", + "integrity": "sha512-tkymXhmlcc7w/HEvLRiHcpHxLFcUB+0PnE9FcG6hfFZ1ZXiWabH+sX+uukCVnluyhfysU9HRU2kUmUWfucx1Dg==", "requires": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.41.0", + "@algolia/requester-browser-xhr": "5.41.0", + "@algolia/requester-fetch": "5.41.0", + "@algolia/requester-node-http": "5.41.0" } }, "@algolia/client-query-suggestions": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.40.1.tgz", - "integrity": "sha512-Nju4NtxAvXjrV2hHZNLKVJLXjOlW6jAXHef/CwNzk1b2qIrCWDO589ELi5ZHH1uiWYoYyBXDQTtHmhaOVVoyXg==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.41.0.tgz", + "integrity": "sha512-vyXDoz3kEZnosNeVQQwf0PbBt5IZJoHkozKRIsYfEVm+ylwSDFCW08qy2YIVSHdKy69/rWN6Ue/6W29GgVlmKQ==", "requires": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.41.0", + "@algolia/requester-browser-xhr": "5.41.0", + "@algolia/requester-fetch": "5.41.0", + "@algolia/requester-node-http": "5.41.0" } }, "@algolia/client-search": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.40.1.tgz", - "integrity": "sha512-Mw6pAUF121MfngQtcUb5quZVqMC68pSYYjCRZkSITC085S3zdk+h/g7i6FxnVdbSU6OztxikSDMh1r7Z+4iPlA==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.41.0.tgz", + "integrity": "sha512-G9I2atg1ShtFp0t7zwleP6aPS4DcZvsV4uoQOripp16aR6VJzbEnKFPLW4OFXzX7avgZSpYeBAS+Zx4FOgmpPw==", "requires": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.41.0", + "@algolia/requester-browser-xhr": "5.41.0", + "@algolia/requester-fetch": "5.41.0", + "@algolia/requester-node-http": "5.41.0" } }, "@algolia/events": { @@ -22870,60 +23593,60 @@ "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==" }, "@algolia/ingestion": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.40.1.tgz", - "integrity": "sha512-z+BPlhs45VURKJIxsR99NNBWpUEEqIgwt10v/fATlNxc4UlXvALdOsWzaFfe89/lbP5Bu4+mbO59nqBC87ZM/g==", + "version": "1.41.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.41.0.tgz", + "integrity": "sha512-sxU/ggHbZtmrYzTkueTXXNyifn+ozsLP+Wi9S2hOBVhNWPZ8uRiDTDcFyL7cpCs1q72HxPuhzTP5vn4sUl74cQ==", "requires": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.41.0", + "@algolia/requester-browser-xhr": "5.41.0", + "@algolia/requester-fetch": "5.41.0", + "@algolia/requester-node-http": "5.41.0" } }, "@algolia/monitoring": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.40.1.tgz", - "integrity": "sha512-VJMUMbO0wD8Rd2VVV/nlFtLJsOAQvjnVNGkMkspFiFhpBA7s/xJOb+fJvvqwKFUjbKTUA7DjiSi1ljSMYBasXg==", + "version": "1.41.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.41.0.tgz", + "integrity": "sha512-UQ86R6ixraHUpd0hn4vjgTHbViNO8+wA979gJmSIsRI3yli2v89QSFF/9pPcADR6PbtSio/99PmSNxhZy+CR3Q==", "requires": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.41.0", + "@algolia/requester-browser-xhr": "5.41.0", + "@algolia/requester-fetch": "5.41.0", + "@algolia/requester-node-http": "5.41.0" } }, "@algolia/recommend": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.40.1.tgz", - "integrity": "sha512-ehvJLadKVwTp9Scg9NfzVSlBKH34KoWOQNTaN8i1Ac64AnO6iH2apJVSP6GOxssaghZ/s8mFQsDH3QIZoluFHA==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.41.0.tgz", + "integrity": "sha512-DxP9P8jJ8whJOnvmyA5mf1wv14jPuI0L25itGfOHSU6d4ZAjduVfPjTS3ROuUN5CJoTdlidYZE+DtfWHxJwyzQ==", "requires": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.41.0", + "@algolia/requester-browser-xhr": "5.41.0", + "@algolia/requester-fetch": "5.41.0", + "@algolia/requester-node-http": "5.41.0" } }, "@algolia/requester-browser-xhr": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.40.1.tgz", - "integrity": "sha512-PbidVsPurUSQIr6X9/7s34mgOMdJnn0i6p+N6Ab+lsNhY5eiu+S33kZEpZwkITYBCIbhzDLOvb7xZD3gDi+USA==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.41.0.tgz", + "integrity": "sha512-C21J+LYkE48fDwtLX7YXZd2Fn7Fe0/DOEtvohSfr/ODP8dGDhy9faaYeWB0n1AvmZltugjkjAXT7xk0CYNIXsQ==", "requires": { - "@algolia/client-common": "5.40.1" + "@algolia/client-common": "5.41.0" } }, "@algolia/requester-fetch": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.40.1.tgz", - "integrity": "sha512-ThZ5j6uOZCF11fMw9IBkhigjOYdXGXQpj6h4k+T9UkZrF2RlKcPynFzDeRgaLdpYk8Yn3/MnFbwUmib7yxj5Lw==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.41.0.tgz", + "integrity": "sha512-FhJy/+QJhMx1Hajf2LL8og4J7SqOAHiAuUXq27cct4QnPhSIuIGROzeRpfDNH5BUbq22UlMuGd44SeD4HRAqvA==", "requires": { - "@algolia/client-common": "5.40.1" + "@algolia/client-common": "5.41.0" } }, "@algolia/requester-node-http": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.40.1.tgz", - "integrity": "sha512-H1gYPojO6krWHnUXu/T44DrEun/Wl95PJzMXRcM/szstNQczSbwq6wIFJPI9nyE95tarZfUNU3rgorT+wZ6iCQ==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.41.0.tgz", + "integrity": "sha512-tYv3rGbhBS0eZ5D8oCgV88iuWILROiemk+tQ3YsAKZv2J4kKUNvKkrX/If/SreRy4MGP2uJzMlyKcfSfO2mrsQ==", "requires": { - "@algolia/client-common": "5.40.1" + "@algolia/client-common": "5.41.0" } }, "@amplitude/analytics-browser": { @@ -23008,18 +23731,18 @@ } }, "@antfu/install-pkg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.0.0.tgz", - "integrity": "sha512-xvX6P/lo1B3ej0OsaErAjqgFYzYVcJpamjLAFLYh9vRJngBrMoUG7aVnrGTeqM7yxbyTD5p3F2+0/QUEh8Vzhw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", "requires": { - "package-manager-detector": "^0.2.8", - "tinyexec": "^0.3.2" + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" } }, "@antfu/utils": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-8.1.1.tgz", - "integrity": "sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==" + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-9.3.0.tgz", + "integrity": "sha512-9hFT4RauhcUzqOE4f1+frMKLZrgNog5b06I7VmZQV1BkvwvqrbC8EBZf3L1eEL2AKb6rNKjER0sEvJiSP1FXEA==" }, "@apidevtools/json-schema-ref-parser": { "version": "11.7.2", @@ -23042,9 +23765,9 @@ } }, "@babel/compat-data": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", - "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==" + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==" }, "@babel/core": { "version": "7.26.0", @@ -23076,12 +23799,12 @@ } }, "@babel/generator": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", - "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", "requires": { - "@babel/parser": "^7.28.3", - "@babel/types": "^7.28.2", + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -23263,9 +23986,9 @@ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==" }, "@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==" + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==" }, "@babel/helper-validator-option": { "version": "7.27.1", @@ -23292,11 +24015,11 @@ } }, "@babel/parser": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", - "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "requires": { - "@babel/types": "^7.28.4" + "@babel/types": "^7.28.5" } }, "@babel/plugin-bugfix-firefox-class-in-computed-class-key": { @@ -23807,9 +24530,9 @@ } }, "@babel/plugin-transform-runtime": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.3.tgz", - "integrity": "sha512-Y6ab1kGqZ0u42Zv/4a7l0l72n9DKP/MKoKWaUSBylrhNZO2prYuqFOLbn5aW5SIFXwSH93yfjbgllL8lxuGKLg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.5.tgz", + "integrity": "sha512-20NUVgOrinudkIBzQ2bNxP08YpKprUkRTiRSd2/Z5GOdPImJGkoN4Z7IQe1T5AdyKI1i5L6RBmluqdSzvaq9/w==", "requires": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", @@ -24068,26 +24791,26 @@ } }, "@babel/traverse": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", - "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", "requires": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", + "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.4", + "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4", + "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "@babel/types": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", - "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "requires": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" } }, "@braintree/sanitize-url": { @@ -25159,17 +25882,17 @@ "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==" }, "@iconify/utils": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.3.0.tgz", - "integrity": "sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.0.2.tgz", + "integrity": "sha512-EfJS0rLfVuRuJRn4psJHtK2A9TqVnkxPpHY6lYHiB9+8eSuudsxbwMiavocG45ujOo6FJ+CIRlRnlOGinzkaGQ==", "requires": { - "@antfu/install-pkg": "^1.0.0", - "@antfu/utils": "^8.1.0", + "@antfu/install-pkg": "^1.1.0", + "@antfu/utils": "^9.2.0", "@iconify/types": "^2.0.0", - "debug": "^4.4.0", - "globals": "^15.14.0", + "debug": "^4.4.1", + "globals": "^15.15.0", "kolorist": "^1.8.0", - "local-pkg": "^1.0.0", + "local-pkg": "^1.1.1", "mlly": "^1.7.4" }, "dependencies": { @@ -25180,6 +25903,26 @@ } } }, + "@ionic/core": { + "version": "8.7.7", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.7.tgz", + "integrity": "sha512-XMvVkiRiB9I1Jc63RqderjHzxxiyr6KM2vBDzYBQS6rk7Fb4wC/ZyUuFgnrYKk71r1mgwYTPMy/2qrCShNXgXQ==", + "requires": { + "@stencil/core": "4.38.0", + "ionicons": "^8.0.13", + "tslib": "^2.1.0" + } + }, + "@ionic/react": { + "version": "8.7.7", + "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.7.7.tgz", + "integrity": "sha512-X/olNPQrITyVbKkZRrhauC6cKXO+C6ISCnoDJFxH34TLphSrpOFOpVD/c+a17QMD8RMxe5/zsQ8oY/DOH8pC6w==", + "requires": { + "@ionic/core": "8.7.7", + "ionicons": "^8.0.13", + "tslib": "*" + } + }, "@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -25366,9 +26109,9 @@ } }, "@mermaid-js/parser": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.2.tgz", - "integrity": "sha512-+PO02uGF6L6Cs0Bw8RpGhikVvMWEysfAyl27qTlroUB8jSWr1lL0Sf6zi78ZxlSnmgSY2AMMKVgghnN9jTtwkQ==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.3.tgz", + "integrity": "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==", "requires": { "langium": "3.3.1" } @@ -25613,6 +26356,54 @@ "reselect": "^4.1.8" } }, + "@rollup/rollup-darwin-arm64": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz", + "integrity": "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==", + "optional": true + }, + "@rollup/rollup-darwin-x64": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz", + "integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==", + "optional": true + }, + "@rollup/rollup-linux-arm64-gnu": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz", + "integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==", + "optional": true + }, + "@rollup/rollup-linux-arm64-musl": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz", + "integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==", + "optional": true + }, + "@rollup/rollup-linux-x64-gnu": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz", + "integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==", + "optional": true + }, + "@rollup/rollup-linux-x64-musl": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz", + "integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==", + "optional": true + }, + "@rollup/rollup-win32-arm64-msvc": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz", + "integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==", + "optional": true + }, + "@rollup/rollup-win32-x64-msvc": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz", + "integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==", + "optional": true + }, "@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -25656,6 +26447,21 @@ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==" }, + "@stencil/core": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.38.0.tgz", + "integrity": "sha512-oC3QFKO0X1yXVvETgc8OLY525MNKhn9vISBrbtKnGoPlokJ6rI8Vk1RK22TevnNrHLI4SExNLbcDnqilKR35JQ==", + "requires": { + "@rollup/rollup-darwin-arm64": "4.34.9", + "@rollup/rollup-darwin-x64": "4.34.9", + "@rollup/rollup-linux-arm64-gnu": "4.34.9", + "@rollup/rollup-linux-arm64-musl": "4.34.9", + "@rollup/rollup-linux-x64-gnu": "4.34.9", + "@rollup/rollup-linux-x64-musl": "4.34.9", + "@rollup/rollup-win32-arm64-msvc": "4.34.9", + "@rollup/rollup-win32-x64-msvc": "4.34.9" + } + }, "@svgr/babel-plugin-add-jsx-attribute": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", @@ -25869,9 +26675,9 @@ } }, "@types/d3-array": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", - "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==" + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==" }, "@types/d3-axis": { "version": "3.0.6", @@ -25914,9 +26720,9 @@ "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==" }, "@types/d3-dispatch": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz", - "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==" + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==" }, "@types/d3-drag": { "version": "3.0.7", @@ -26075,9 +26881,9 @@ } }, "@types/express": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", - "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.24.tgz", + "integrity": "sha512-Mbrt4SRlXSTWryOnHAh2d4UQ/E7n9lZyGSi6KgX+4hkuL9soYbLOVXVhnk/ODp12YsGc95f4pOvqywJ6kngUwg==", "requires": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -26144,9 +26950,9 @@ "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==" }, "@types/http-proxy": { - "version": "1.17.16", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", - "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", + "version": "1.17.17", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", + "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", "requires": { "@types/node": "*" } @@ -26310,9 +27116,9 @@ "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" }, "@types/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.0.tgz", - "integrity": "sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "requires": { "@types/node": "*" } @@ -26326,9 +27132,9 @@ } }, "@types/serve-static": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.9.tgz", - "integrity": "sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA==", + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", "requires": { "@types/http-errors": "*", "@types/node": "*", @@ -26336,9 +27142,9 @@ }, "dependencies": { "@types/send": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", - "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", "requires": { "@types/mime": "^1", "@types/node": "*" @@ -26374,9 +27180,9 @@ } }, "@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.34", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.34.tgz", + "integrity": "sha512-KExbHVa92aJpw9WDQvzBaGVE2/Pz+pLZQloT2hjL8IqsZnV62rlPOYvNnLmf/L2dyllfVUOVBj64M0z/46eR2A==", "requires": { "@types/yargs-parser": "*" } @@ -26575,9 +27381,9 @@ } }, "acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==" + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==" }, "acorn-import-attributes": { "version": "1.9.5", @@ -26622,11 +27428,11 @@ } }, "ai": { - "version": "5.0.76", - "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.76.tgz", - "integrity": "sha512-ZCxi1vrpyCUnDbtYrO/W8GLvyacV9689f00yshTIQ3mFFphbD7eIv40a2AOZBv3GGRA7SSRYIDnr56wcS/gyQg==", + "version": "5.0.78", + "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.78.tgz", + "integrity": "sha512-ec77fmQwJGLduswMrW4AAUGSOiu8dZaIwMmWHHGKsrMUFFS6ugfkTyx0srtuKYHNRRLRC2dT7cPirnUl98VnxA==", "requires": { - "@ai-sdk/gateway": "2.0.0", + "@ai-sdk/gateway": "2.0.1", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.12", "@opentelemetry/api": "1.9.0" @@ -26666,24 +27472,24 @@ } }, "algoliasearch": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.40.1.tgz", - "integrity": "sha512-iUNxcXUNg9085TJx0HJLjqtDE0r1RZ0GOGrt8KNQqQT5ugu8lZsHuMUYW/e0lHhq6xBvmktU9Bw4CXP9VQeKrg==", - "requires": { - "@algolia/abtesting": "1.6.1", - "@algolia/client-abtesting": "5.40.1", - "@algolia/client-analytics": "5.40.1", - "@algolia/client-common": "5.40.1", - "@algolia/client-insights": "5.40.1", - "@algolia/client-personalization": "5.40.1", - "@algolia/client-query-suggestions": "5.40.1", - "@algolia/client-search": "5.40.1", - "@algolia/ingestion": "1.40.1", - "@algolia/monitoring": "1.40.1", - "@algolia/recommend": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.41.0.tgz", + "integrity": "sha512-9E4b3rJmYbBkn7e3aAPt1as+VVnRhsR4qwRRgOzpeyz4PAOuwKh0HI4AN6mTrqK0S0M9fCCSTOUnuJ8gPY/tvA==", + "requires": { + "@algolia/abtesting": "1.7.0", + "@algolia/client-abtesting": "5.41.0", + "@algolia/client-analytics": "5.41.0", + "@algolia/client-common": "5.41.0", + "@algolia/client-insights": "5.41.0", + "@algolia/client-personalization": "5.41.0", + "@algolia/client-query-suggestions": "5.41.0", + "@algolia/client-search": "5.41.0", + "@algolia/ingestion": "1.41.0", + "@algolia/monitoring": "1.41.0", + "@algolia/recommend": "5.41.0", + "@algolia/requester-browser-xhr": "5.41.0", + "@algolia/requester-fetch": "5.41.0", + "@algolia/requester-node-http": "5.41.0" } }, "algoliasearch-helper": { @@ -26926,9 +27732,9 @@ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "baseline-browser-mapping": { - "version": "2.8.18", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.18.tgz", - "integrity": "sha512-UYmTpOBwgPScZpS4A+YbapwWuBwasxvO/2IOHArSsAhL/+ZdmATBXTex3t+l2hXwLVYK382ibr/nKoY9GKe86w==" + "version": "2.8.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.20.tgz", + "integrity": "sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ==" }, "batch": { "version": "0.6.1", @@ -26982,6 +27788,14 @@ "ms": "2.0.0" } }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -27153,15 +27967,15 @@ } }, "browserslist": { - "version": "4.26.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz", - "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", + "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", "requires": { - "baseline-browser-mapping": "^2.8.9", - "caniuse-lite": "^1.0.30001746", - "electron-to-chromium": "^1.5.227", - "node-releases": "^2.0.21", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.8.19", + "caniuse-lite": "^1.0.30001751", + "electron-to-chromium": "^1.5.238", + "node-releases": "^2.0.26", + "update-browserslist-db": "^1.1.4" } }, "buffer": { @@ -27660,9 +28474,9 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==" + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==" }, "config-chain": { "version": "1.1.13", @@ -28125,9 +28939,9 @@ "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, "cytoscape": { - "version": "3.30.2", - "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.30.2.tgz", - "integrity": "sha512-oICxQsjW8uSaRmn4UK/jkczKOqTrVqt5/1WL0POiJUT2EKNc9STM4hYFHv917yu55aTBMFNRzymlJhVAiWPCxw==" + "version": "3.33.1", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", + "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==" }, "cytoscape-cose-bilkent": { "version": "4.1.0", @@ -28279,14 +29093,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" - }, - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } } } }, @@ -28485,9 +29291,9 @@ } }, "dayjs": { - "version": "1.11.13", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", - "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==" }, "debounce": { "version": "1.2.1", @@ -29427,9 +30233,9 @@ } }, "dompurify": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", - "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz", + "integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==", "requires": { "@types/trusted-types": "^2.0.7" } @@ -29494,9 +30300,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "electron-to-chromium": { - "version": "1.5.237", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.237.tgz", - "integrity": "sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==" + "version": "1.5.240", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.240.tgz", + "integrity": "sha512-OBwbZjWgrCOH+g6uJsA2/7Twpas2OlepS9uvByJjR2datRDuKGYeD+nP8lBBks2qnB7bGJNHDUx7c/YLaT3QMQ==" }, "elliptic": { "version": "6.6.1", @@ -29852,6 +30658,11 @@ } } }, + "exsolve": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==" + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -30804,11 +31615,11 @@ "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==" }, "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "icss-utils": { @@ -30913,6 +31724,14 @@ "loose-envify": "^1.0.0" } }, + "ionicons": { + "version": "8.0.13", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-8.0.13.tgz", + "integrity": "sha512-2QQVyG2P4wszne79jemMjWYLp0DBbDhr4/yFroPCxvPP1wtMxgdIV3l5n+XZ5E9mgoXU79w7yTWpm2XzJsISxQ==", + "requires": { + "@stencil/core": "^4.35.3" + } + }, "ipaddr.js": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", @@ -31297,9 +32116,9 @@ } }, "katex": { - "version": "0.16.22", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz", - "integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==", + "version": "0.16.25", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.25.tgz", + "integrity": "sha512-woHRUZ/iF23GBP1dkDQMh1QBad9dmr8/PAwNA54VrSOVYgI12MAcE14TqnDdQOdzyEonGzMepYnqBMYdsoAr8Q==", "requires": { "commander": "^8.3.0" }, @@ -31414,12 +32233,13 @@ } }, "local-pkg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.0.0.tgz", - "integrity": "sha512-bbgPw/wmroJsil/GgL4qjDzs5YLTBMQ99weRsok1XCDccQeehbHA/I1oRvk2NPtr7KGZgT/Y5tPRnAtMqeG2Kg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", "requires": { - "mlly": "^1.7.3", - "pkg-types": "^1.3.0" + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" } }, "locate-path": { @@ -31892,12 +32712,12 @@ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" }, "mermaid": { - "version": "11.10.1", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.10.1.tgz", - "integrity": "sha512-0PdeADVWURz7VMAX0+MiMcgfxFKY4aweSGsjgFihe3XlMKNqmai/cugMrqTd3WNHM93V+K+AZL6Wu6tB5HmxRw==", + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.0.tgz", + "integrity": "sha512-ZudVx73BwrMJfCFmSSJT84y6u5brEoV8DOItdHomNLz32uBjNrelm7mg95X7g+C6UoQH/W6mBLGDEDv73JdxBg==", "requires": { - "@braintree/sanitize-url": "^7.0.4", - "@iconify/utils": "^2.1.33", + "@braintree/sanitize-url": "^7.1.1", + "@iconify/utils": "^3.0.1", "@mermaid-js/parser": "^0.6.2", "@types/d3": "^7.4.3", "cytoscape": "^3.29.3", @@ -31906,12 +32726,12 @@ "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.11", - "dayjs": "^1.11.13", + "dayjs": "^1.11.18", "dompurify": "^3.2.5", "katex": "^0.16.22", "khroma": "^2.1.0", "lodash-es": "^4.17.21", - "marked": "^16.0.0", + "marked": "^16.2.1", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", @@ -32902,14 +33722,31 @@ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" }, "mlly": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", - "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", "requires": { - "acorn": "^8.14.0", - "pathe": "^2.0.1", - "pkg-types": "^1.3.0", - "ufo": "^1.5.4" + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + }, + "dependencies": { + "confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==" + }, + "pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "requires": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + } } }, "mri": { @@ -33078,9 +33915,9 @@ } }, "node-releases": { - "version": "2.0.25", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.25.tgz", - "integrity": "sha512-4auku8B/vw5psvTiiN9j1dAOsXvMoGqJuKJcR+dTdqiXEK20mMTk1UEo3HS16LeGQsVG6+qKTPM9u/qQ2LqATA==" + "version": "2.0.26", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz", + "integrity": "sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==" }, "normalize-path": { "version": "3.0.0", @@ -33433,9 +34270,9 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" }, "package-manager-detector": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.9.tgz", - "integrity": "sha512-+vYvA/Y31l8Zk8dwxHhL3JfTuHPm6tlxM2A3GeQyl7ovYnSp1+mzAxClxaOr0qO1TtPxbQxetI7v5XqKLJZk7Q==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.5.0.tgz", + "integrity": "sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw==" }, "pako": { "version": "1.0.11", @@ -33711,13 +34548,13 @@ } }, "pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", "requires": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" } }, "pluralize": { @@ -34521,14 +35358,6 @@ "uuid": "8.3.2" }, "dependencies": { - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - }, "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -34689,6 +35518,11 @@ "side-channel": "^1.0.6" } }, + "quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==" + }, "querystring-es3": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", @@ -34741,6 +35575,14 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } } } }, @@ -35631,11 +36473,11 @@ "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==" }, "resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "requires": { - "is-core-module": "^2.16.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" } @@ -36029,13 +36871,12 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "sha.js": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", - "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "requires": { - "inherits": "^2.0.4", - "safe-buffer": "^5.2.1", - "to-buffer": "^1.2.0" + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, "shallow-clone": { @@ -36685,9 +37526,9 @@ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, "tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", + "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==" }, "tinypool": { "version": "1.1.1", @@ -36825,9 +37666,9 @@ "peer": true }, "ufo": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", - "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==" + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==" }, "undici-types": { "version": "5.26.5", @@ -36961,9 +37802,9 @@ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, "update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", "requires": { "escalade": "^3.2.0", "picocolors": "^1.1.1" diff --git a/docs/package.json b/docs/package.json index 4de4b4563a06..b26adc756edf 100644 --- a/docs/package.json +++ b/docs/package.json @@ -25,10 +25,12 @@ "@docusaurus/plugin-google-tag-manager": "^3.9.2", "@docusaurus/preset-classic": "^3.9.2", "@docusaurus/theme-mermaid": "^3.9.2", + "@ionic/react": "^8.7.7", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", "docusaurus-plugin-openapi-docs": "^4.2.0", "docusaurus-theme-openapi-docs": "^4.2.0", + "ionicons": "^8.0.13", "prism-react-renderer": "^2.3.0", "react": "^18.0.0", "react-dom": "^18.0.0", diff --git a/docs/src/components/Card/CardBody/index.js b/docs/src/components/Card/CardBody/index.js new file mode 100644 index 000000000000..5fb71fec28e8 --- /dev/null +++ b/docs/src/components/Card/CardBody/index.js @@ -0,0 +1,44 @@ +import React, { CSSProperties } from 'react'; +import clsx from 'clsx'; +const CardBody = ({ + className, // classNamees for the container card + style, // Custom styles for the container card + children, // Content to be included within the card + textAlign, + variant, + italic = false , + noDecoration = false, + transform, + breakWord = false, + truncate = false, + weight, +}) => { + const text = textAlign ? `text--${textAlign}` :''; + const textColor = variant ? `text--${variant}` : ''; + const textItalic = italic ? 'text--italic' : ''; + const textDecoration = noDecoration ? 'text-no-decoration' : ''; + const textType = transform ? `text--${transform}` : ''; + const textBreak = breakWord ? 'text--break' : ''; + const textTruncate = truncate ? 'text--truncate' : ''; + const textWeight = weight ? `text--${weight}` : ''; + return ( +
+ {children} +
+ ); +} +export default CardBody; \ No newline at end of file diff --git a/docs/src/components/Card/CardFooter/index.js b/docs/src/components/Card/CardFooter/index.js new file mode 100644 index 000000000000..2828f6109624 --- /dev/null +++ b/docs/src/components/Card/CardFooter/index.js @@ -0,0 +1,44 @@ +import React, { CSSProperties } from 'react'; +import clsx from 'clsx'; +const CardFooter = ({ + className, + style, + children, + textAlign, + variant, + italic = false , + noDecoration = false, + transform, + breakWord = false, + truncate = false, + weight, +}) => { + const text = textAlign ? `text--${textAlign}` :''; + const textColor = variant ? `text--${variant}` : ''; + const textItalic = italic ? 'text--italic' : ''; + const textDecoration = noDecoration ? 'text-no-decoration' : ''; + const textType = transform ? `text--${transform}` : ''; + const textBreak = breakWord ? 'text--break' : ''; + const textTruncate = truncate ? 'text--truncate' : ''; + const textWeight = weight ? `text--${weight}` : ''; + return ( +
+ {children} +
+ ); +} +export default CardFooter; \ No newline at end of file diff --git a/docs/src/components/Card/CardHeader/index.js b/docs/src/components/Card/CardHeader/index.js new file mode 100644 index 000000000000..6155b421bdb6 --- /dev/null +++ b/docs/src/components/Card/CardHeader/index.js @@ -0,0 +1,16 @@ +import React, { CSSProperties } from 'react'; // CSSProperties allows inline styling with better type checking. +import clsx from 'clsx'; // clsx helps manage conditional className names in a clean and concise manner. +const Card = ({ + className, // Custom classes for the container card + style, // Custom styles for the container card + children, // Content to be included within the card + shadow, // Used to add shadow under your card. Expected values are: low (lw), medium (md), tall (tl) +}) => { + const cardShadow = shadow ? `item shadow--${shadow}` : ''; + return ( +
+ {children} +
+ ); +}; +export default Card; \ No newline at end of file diff --git a/docs/src/components/Card/CardImage/index.js b/docs/src/components/Card/CardImage/index.js new file mode 100644 index 000000000000..731fc074d709 --- /dev/null +++ b/docs/src/components/Card/CardImage/index.js @@ -0,0 +1,20 @@ +import React, { CSSProperties } from 'react'; +import clsx from 'clsx'; +import useBaseUrl from '@docusaurus/useBaseUrl'; // Import the useBaseUrl function from Docusaurus +const CardImage = ({ + className, + style, + cardImageUrl, + alt, + title, +}) => { + const generatedCardImageUrl = useBaseUrl(cardImageUrl); + return ( + {alt} + ) +} +export default CardImage; \ No newline at end of file diff --git a/docs/src/components/Card/index.js b/docs/src/components/Card/index.js new file mode 100644 index 000000000000..6155b421bdb6 --- /dev/null +++ b/docs/src/components/Card/index.js @@ -0,0 +1,16 @@ +import React, { CSSProperties } from 'react'; // CSSProperties allows inline styling with better type checking. +import clsx from 'clsx'; // clsx helps manage conditional className names in a clean and concise manner. +const Card = ({ + className, // Custom classes for the container card + style, // Custom styles for the container card + children, // Content to be included within the card + shadow, // Used to add shadow under your card. Expected values are: low (lw), medium (md), tall (tl) +}) => { + const cardShadow = shadow ? `item shadow--${shadow}` : ''; + return ( +
+ {children} +
+ ); +}; +export default Card; \ No newline at end of file diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css index 5087e2ebd06a..7e8b4165d373 100644 --- a/docs/src/css/custom.css +++ b/docs/src/css/custom.css @@ -21,29 +21,116 @@ /* You can override the default Infima variables here. */ :root { - --ifm-color-primary: #2e8555; - --ifm-color-primary-dark: #29784c; - --ifm-color-primary-darker: #277148; - --ifm-color-primary-darkest: #205d3b; - --ifm-color-primary-light: #33925d; - --ifm-color-primary-lighter: #359962; - --ifm-color-primary-lightest: #3cad6e; + --ifm-color-primary: #956cff; + --ifm-color-primary-dark: #7c49ff; + --ifm-color-primary-darker: #6837fc; + --ifm-color-primary-darkest: #5425e8; + --ifm-color-primary-light: #ae8fff; + --ifm-color-primary-lighter: #bca1ff; + --ifm-color-primary-lightest: #d5c4ff; --ifm-code-font-size: 95%; --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); --ifm-font-family-base: 'Open Sans Regular'; --ifm-font-size-base: 92%; + --icon-color: #22194D; /* icon color */ + --icon-circle-color: #F6F3EE; /* circle color */ + --button-primary-color: #7B51FB; + --button-primary-hover: #5D3DC2; + --card-bg-hover: #7B51FB; } /* For readability concerns, you should choose a lighter palette in dark mode. */ [data-theme='dark'] { - --ifm-color-primary: #25c2a0; - --ifm-color-primary-dark: #21af90; - --ifm-color-primary-darker: #1fa588; - --ifm-color-primary-darkest: #1a8870; - --ifm-color-primary-light: #29d5b0; - --ifm-color-primary-lighter: #32d8b4; - --ifm-color-primary-lightest: #4fddbf; + --ifm-color-primary: #956cff; + --ifm-color-primary-dark: #7c49ff; + --ifm-color-primary-darker: #6837fc; + --ifm-color-primary-darkest: #5425e8; + --ifm-color-primary-light: #ae8fff; + --ifm-color-primary-lighter: #bca1ff; + --ifm-color-primary-lightest: #d5c4ff; --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); + --ifm-background-color: #111111; /* Base dark background */ + --ifm-background-surface-color: #222221; /* Lighter dark background for cards/sections */ + --icon-color: #22194D; /* icon color */ + --icon-circle-color: #7B51FB; /* circle color */ + --button-primary-color: #7B51FB; + --button-primary-hover: #5D3DC2; + --card-bg-hover: #7B51FB; +} + +/* Card styles */ +.card { + padding: 24px; + border-radius: 8px; + transition: all 0.2s ease; + border: 1px solid var(--ifm-color-primary-lightest); + height: 100%; + display: flex; + flex-direction: column; +} + +[data-theme='light'] .card { + background: #ffffff; + box-shadow: 0px 4px 8px rgba(35, 41, 54, 0.05); + border-color: #D7D7D7; +} + +[data-theme='dark'] .card { + background: var(--ifm-background-surface-color); + border-color: rgba(255, 255, 255, 0.1); +} + +.card:hover { + transform: translateY(-4px); + box-shadow: 0px 8px 16px rgba(35, 41, 54, 0.1); + background: var(--card-bg-hover); + color: white; + p { + color: white; + } +} + + + +/* Card title and text styles */ +.card h3 { + margin-top: 0; + font-size: 1.25rem; /* Increased from 1.1rem */ + font-weight: 600; /* Added bold */ + margin-bottom: 0.5rem; + color: var(--ifm-heading-color); +} + +.card p { + font-size: 1rem; /* Increased from 0.95rem */ + line-height: 1.5; + color: var(--ifm-color-emphasis-700); + margin-bottom: 1rem; + flex-grow: 1; +} + +/* Card link styles */ +.card a { + font-size: 1rem; + font-weight: 600; + color: var(--ifm-color-primary); + text-decoration: none; +} + + + +/* Grid layout for cards */ + + +/* Section styles */ +.section { + margin: 3rem 0; +} + +.section h2 { + margin-bottom: 24px; + font-size: 24px; + font-weight: 600; } /* Sidebar Method labels */ @@ -70,7 +157,7 @@ .get>.menu__link::before { content: "get"; - background-color: var(--ifm-color-primary); + background-color: #2e8555; } .put>.menu__link::before { @@ -92,3 +179,135 @@ content: "patch"; background-color: var(--openapi-code-orange); } + +/* Homepage buttons */ +.button.button--primary { + background-color: var(--button-primary-color); + color: #ffffff; + border: none; + transition: background-color 400ms cubic-bezier(0.23, 1, 0.32, 1); +} + +.button.button--primary:hover { + background-color: var(--button-primary-hover); +} + +.button.button--lg { + font-weight: 700; + line-height: 44px; + padding: 0 20px; + height: 44px; +} + +/* Icon container styles */ +.card-header { + display: flex; + align-items: left; + gap: 1rem; + margin-bottom: 1rem; +} + +.flex-column { + flex-direction: column; + align-items: left; + text-align: left; +} + +/* Icon styles */ +.card-icon { + font-size: 30px !important; /* Increased from 24px */ + padding: 10px; /* Increased from 12px for larger circle */ + border-radius: 50%; + color: var(--icon-color); + background-color: var(--icon-circle-color); +} + +/* Add bottom padding */ +main.container { + padding-bottom: 5rem; +} + +/* Badge colors specifically for API content area */ +.openapi__heading ~ * .badge--primary { + background-color: #2e8555 !important; + border-color: #2e8555 !important; +} + + + +/* Section background alternation for dark mode */ +[data-theme='dark'] section:nth-child(even) { + background: var(--ifm-background-surface-color); + padding: 3rem 0; + margin: 0; + /* full width */ + width: 100vw; + position: relative; + left: 50%; + right: 50%; + margin-left: -50vw; + margin-right: -50vw; +} + +[data-theme='dark'] section:nth-child(odd) { + background: #111111; + padding: 3rem 0; + margin: 0; + /* full width */ + width: 100vw; + position: relative; + left: 50%; + right: 50%; + margin-left: -50vw; + margin-right: -50vw; +} + +/* Section background alternation for dark mode - alternating every 2 sections */ +[data-theme='dark'] section:nth-child(4n+1), +[data-theme='dark'] section:nth-child(4n+2) { + background: #111111; + padding: 3rem 0; + margin: 0; + /* full width */ + width: 100vw; + position: relative; + left: 50%; + right: 50%; + margin-left: -50vw; + margin-right: -50vw; +} + +[data-theme='dark'] section:nth-child(4n+3), +[data-theme='dark'] section:nth-child(4n+4) { + background: var(--ifm-background-surface-color); + padding: 3rem 0; + margin: 0; + /* full width */ + width: 100vw; + position: relative; + left: 50%; + right: 50%; + margin-left: -50vw; + margin-right: -50vw; +} + +/* container width inside sections */ +section > div, +section > h2 { + margin: 0 auto; + max-width: var(--ifm-container-width); + padding: 0 var(--ifm-spacing-horizontal); + width: 100%; +} + +section > h2 { + padding-left: var(--ifm-spacing-horizontal); /* Using the same padding as div */ +} + +/* Ensure consistent container width */ +.container { + max-width: var(--ifm-container-width); + margin: 0 auto; + padding: 0 var(--ifm-spacing-horizontal); + width: 100%; +} diff --git a/docs/src/pages/index.module.css b/docs/src/pages/index.module.css index 9f71a5da775b..0cf8bf59a561 100644 --- a/docs/src/pages/index.module.css +++ b/docs/src/pages/index.module.css @@ -2,6 +2,25 @@ * CSS files with the .module.css suffix will be treated as CSS modules * and scoped locally. */ +:root { + --card-primary-text: #23232A; + --card-secondary-text: #71768B; + --border-color: #D7D7D7; + --link-color: #7B51FB; + --link-hover-color: #FFFFFF; + --icon-color: #22194D; + --icon-circle-color: #F6F3EE; +} + +[data-theme="dark"] { + --card-primary-text: #FFFFFF; + --card-secondary-text: #FFFFFF; + --border-color: #464646; + --link-color: #A384FF; + --link-hover-color: #FFFFFF; + --icon-color: #22194D; + --icon-circle-color: #7B51FB; +} .heroBanner { padding: 4rem 0; @@ -10,14 +29,221 @@ overflow: hidden; } -@media screen and (max-width: 996px) { - .heroBanner { - padding: 2rem; - } -} - .buttons { display: flex; align-items: center; justify-content: center; } + +.hero { + background: #fdf9f6; + padding: 4rem 0; +} + +[data-theme="dark"] .hero { + background: #222221; +} + +.heroInner { + display: flex; + justify-content: space-between; + align-items: center; + gap: 2rem; + vertical-align: middle; + padding: 2rem 0; +} + +.heroInner > div { + flex: 1; + max-width: 55%; +} + +.logo { + max-width: 80%; + width: 35%; + height: auto; +} + +.section { + margin-top: 4rem; +} + +.cardGrid { + display: grid; + gap: 24px; + margin-top: 1rem; +} + +[data-section="introduction"] .cardGrid { + grid-template-columns: repeat(2, 1fr); +} + +[data-section="integration"] .cardGrid { + grid-template-columns: repeat(4, 1fr); +} + +[data-section="configuration"] .cardGrid { + grid-template-columns: repeat(2, 1fr); +} + +[data-section="configuration"] .cardGrid > *:last-child { + grid-column: 1 / 3; + margin: 0 auto; + width: 100%; +} + +[data-section="ecosystem"] .cardGrid { + grid-template-columns: repeat(3, 1fr); +} + +.card { + background: #fff; + padding: 1.5rem; + border-radius: 12px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); + border: 1px solid var(--border-color); + transition: transform 0.2s; + height: 100%; + display: flex; + flex-direction: column; +} + +[data-theme="dark"] .card { + background: #222221; +} + +.card:hover { + transform: translateY(-4px); + background: #7B51FB; + box-shadow: 0 8px 16px rgba(35, 41, 54, 0.1); +} + +.card:hover h3, +.card:hover p, +.card:hover span { + color: var(--link-hover-color) !important; +} + +.card:focus { + outline: none; + border-color: #7B51FB; + box-shadow: 0 0 0 2px rgba(123, 81, 251, 0.2); +} + +.card h3 { + margin: 0 0 0.1rem; + font-size: 1.25rem; + font-weight: 600; + color: var(--card-primary-text); +} + +.card p { + color: var(--card-primary-text); + font-size: 1rem; + line-height: 1.5; + margin-bottom: 1.25rem; + flex-grow: 1; +} + +[data-theme="dark"] .card p { + color: #CCCCCC; +} + +.card a { + font-weight: 600; + font-size: 1rem; + color: var(--link-color); + text-decoration: none; +} + + + +.card-icon { + font-size: 24px !important; + padding: 12px; + border-radius: 50%; + color: var(--icon-color); + background-color: var(--icon-circle-color); + transition: color 0.2s; +} + +[data-theme='dark'] .card:hover .card-icon { + color: white; +} + +.cardHeader { + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: 1rem; + text-align: center; +} + +.iconContainer { + margin-bottom: 1rem; +} + +.cardIcon { + font-size: 24px !important; + padding: 12px; + border-radius: 50%; + color: var(--icon-color); + background-color: var(--icon-circle-color); + transition: color 0.2s; +} + +.cardLink { + font-weight: 600; + font-size: 1rem; + color: var(--link-color); + text-decoration: none; +} + +@media (max-width: 996px) { + section .cardGrid { + grid-template-columns: 1fr; + padding: 0 1rem; + } + + .heroInner { + flex-direction: column; + padding: 0 1rem; + } + + .heroInner > div, + .logo { + max-width: 100%; + width: 100%; + } + + .card { + width: 100%; + margin: 0 auto; + } + + section, + .hero, + .cardGrid { + width: 100vw; + max-width: 100%; + margin: 0; + overflow-x: hidden; + } + + :global(body) { + overflow-x: hidden; + width: 100%; + } +} + +@media (max-width: 768px) { + .hero { + padding: 2rem 0; + } + + .section { + margin-top: 2rem; + } +} + + diff --git a/docs/src/pages/index.tsx b/docs/src/pages/index.tsx new file mode 100644 index 000000000000..46a7b1f08ad7 --- /dev/null +++ b/docs/src/pages/index.tsx @@ -0,0 +1,170 @@ +import React from 'react'; +import clsx from 'clsx'; +import Layout from '@theme/Layout'; +import Link from '@docusaurus/Link'; +import { IonIcon } from '@ionic/react'; +import { + play, + flag, + analytics, + layers, + gitMerge, + phonePortrait, + server, + codeWorking, + extensionPuzzle, + settings, + cloudUpload, + documentText, + map, + gitBranch, + construct, + cog, +} from 'ionicons/icons'; +import styles from './index.module.css'; + +function Card({ title, description, link, icon }) { + return ( + +
+
+ {icon && ( +
+ +
+ )} +

{title}

+
+

{description}

+ Learn more → +
+ + ); +} + +function Section({ title, children, id }) { + return ( +
+

{title}

+
{children}
+
+ ); +} + +export default function Home() { + return ( + +
+
+
+
+

Manage feature flags and remote config across web, mobile, and server-side apps.

+ + Get Started + +
+ Flagsmith Logo +
+
+
+ +
+
+ + + + +
+ +
+ + + + +
+ +
+ + + +
+ +
+ + + +
+
+
+ ); +} diff --git a/docs/static/img/full-logo.svg b/docs/static/img/full-logo.svg new file mode 100644 index 000000000000..16fd7d721bde --- /dev/null +++ b/docs/static/img/full-logo.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/docs/vercel.json b/docs/vercel.json index 637747e03eaf..8bba447a176f 100644 --- a/docs/vercel.json +++ b/docs/vercel.json @@ -2,339 +2,703 @@ "redirects": [ { "source": "/managing-identities/", - "destination": "/basic-features/managing-identities" + "destination": "/flagsmith-concepts/identities" + }, + { + "source": "/managing-identities", + "destination": "/flagsmith-concepts/identities" }, { "source": "/system-administration/", - "destination": "/system-administration/security" + "destination": "/administration-and-security/" }, { "source": "/contributing", - "destination": "/platform/contributing" + "destination": "/project-and-community/contributing" + }, + { + "source": "/contributing/", + "destination": "/project-and-community/contributing" }, { "source": "/overview/", - "destination": "/basic-features/overview" + "destination": "/flagsmith-concepts/platform-architecture" }, { "source": "/ab-testing/", - "destination": "/advanced-use/ab-testing" + "destination": "/experimentation-ab-testing" }, { "source": "/permissions/", - "destination": "/system-administration/rbac" + "destination": "/administration-and-security/access-control/rbac" }, { "source": "/audit-logs/", - "destination": "/system-administration/security#audit-logs" + "destination": "/administration-and-security/governance-and-compliance/audit-logs" }, { "source": "/managing-features/", - "destination": "/basic-features/managing-features" + "destination": "/managing-flags/core-management" }, { "source": "/deployment-overview/", - "destination": "/deployment" + "destination": "/deployment-self-hosting/" }, { "source": "/hosted-service/", - "destination": "/basic-features/overview" + "destination": "/flagsmith-concepts/platform-architecture" }, { "source": "/flag-analytics/", - "destination": "/advanced-use/flag-analytics" + "destination": "/managing-flags/flag-analytics" + }, + { + "source": "/experimentation/ab-testing/", + "destination": "/experimentation-ab-testing" + }, + { + "source": "/experimentation/flag-analytics/", + "destination": "/managing-flags/flag-analytics" + }, + { + "source": "/flagsmith-integration/", + "destination": "/integrating-with-flagsmith/" }, { "source": "/staged-feature-rollouts/", - "destination": "/guides-and-examples/staged-feature-rollouts" + "destination": "/managing-flags/rollout/" }, { "source": "/advanced-use/staged-feature-rollouts/", - "destination": "/guides-and-examples/staged-feature-rollouts" + "destination": "/managing-flags/rollout/" }, { "source": "/guides-and-examples/okta-configuration", - "destination": "/system-administration/authentication/okta" + "destination": "/administration-and-security/access-control/okta" }, { "source": "/self-hosting/", - "destination": "/basic-features/overview" + "destination": "/deployment-self-hosting/" }, { "source": "/clients/rust/test/integration_test.rs", - "destination": "/clients/rust" + "destination": "/integrating-with-flagsmith/sdks/server-side" }, { "source": "/api/", - "destination": "/clients/rest" + "destination": "/integrating-with-flagsmith/flagsmith-api-overview/" }, { "source": "/managing-segments", - "destination": "/basic-features/segments" + "destination": "/flagsmith-concepts/segments/" + }, + { + "source": "/managing-segments/", + "destination": "/flagsmith-concepts/segments/" }, { "source": "/basic-features/managing-segments", - "destination": "/basic-features/segments" + "destination": "/flagsmith-concepts/segments/" }, { "source": "/architecture/", - "destination": "/basic-features/overview" + "destination": "/flagsmith-concepts/platform-architecture" }, { "source": "/releases/", - "destination": "/platform/releases" - }, - { - "source": "/contributing/", - "destination": "/platform/contributing" + "destination": "/project-and-community/release-notes" }, { "source": "/kubernetes", - "destination": "/deployment/hosting/kubernetes" - }, - { - "source": "/managing-identities", - "destination": "/basic-features/managing-identities" + "destination": "/deployment-self-hosting/hosting-guides/kubernetes-openshift" }, { "source": "/integration-approaches/", - "destination": "/guides-and-examples/integration-approaches" + "destination": "/best-practices/integration-approaches" }, { "source": "/advanced-use/integration-approaches/", - "destination": "/guides-and-examples/integration-approaches" + "destination": "/best-practices/integration-approaches" }, { "source": "/advanced-use/integration-approaches", - "destination": "/guides-and-examples/integration-approaches" + "destination": "/best-practices/integration-approaches" }, { "source": "/advanced-use/migrating-from-self-hosted-to-cloud", - "destination": "/deployment/migrating-from-self-hosted-to-cloud" + "destination": "/administration-and-security/data-management/import-and-export" }, { - "source": "/advanced-use/staged-feature-rollouts/", - "destination": "/guides-and-examples/staged-feature-rollouts" + "source": "/advanced-use/migrating-from-self-hosted-to-cloud/", + "destination": "/administration-and-security/data-management/import-and-export" }, { - "source": "/managing-segments/", - "destination": "/basic-features/segments" + "source": "/advanced-use/edge-proxy", + "destination": "/deployment-self-hosting/edge-proxy" }, { - "source": "/clients/02-3rd-party", - "destination": "/clients/3rd-party" + "source": "/advanced-use/edge-proxy/", + "destination": "/deployment-self-hosting/edge-proxy" + }, + { + "source": "/advanced-use/custom-fields", + "destination": "/administration-and-security/governance-and-compliance/custom-fields" + }, + { + "source": "/advanced-use/custom-fields/", + "destination": "/administration-and-security/governance-and-compliance/custom-fields" + }, + { + "source": "/advanced-use/defensive-coding", + "destination": "/best-practices/defensive-coding" + }, + { + "source": "/advanced-use/defensive-coding/", + "destination": "/best-practices/defensive-coding" + }, + { + "source": "/advanced-use/testing-with-flags", + "destination": "/best-practices/testing-with-flags" + }, + { + "source": "/advanced-use/testing-with-flags/", + "destination": "/best-practices/testing-with-flags" + }, + { + "source": "/advanced-use/when-to-use-flags", + "destination": "/best-practices/when-to-use-flags" + }, + { + "source": "/advanced-use/when-to-use-flags/", + "destination": "/best-practices/when-to-use-flags" + }, + { + "source": "/advanced-use/flag-lifecycle", + "destination": "/best-practices/flag-lifecycle" + }, + { + "source": "/advanced-use/flag-lifecycle/", + "destination": "/best-practices/flag-lifecycle" + }, + { + "source": "/advanced-use/efficient-api-usage", + "destination": "/best-practices/efficient-api-usage" + }, + { + "source": "/advanced-use/efficient-api-usage/", + "destination": "/best-practices/efficient-api-usage" + }, + { + "source": "/advanced-use/mobile-app-versioning", + "destination": "/best-practices/mobile-app-versioning" + }, + { + "source": "/advanced-use/mobile-app-versioning/", + "destination": "/best-practices/mobile-app-versioning" }, { "source": "/advanced-use/api/", - "destination": "/clients/rest" + "destination": "/integrating-with-flagsmith/flagsmith-api-overview/" }, { "source": "/advanced-use/api", - "destination": "/clients/rest" + "destination": "/integrating-with-flagsmith/flagsmith-api-overview/" }, { "source": "/advanced-use/audit-logs/", - "destination": "/system-administration/security#audit-logs" + "destination": "/administration-and-security/governance-and-compliance/audit-logs" }, { "source": "/advanced-use/audit-logs", - "destination": "/system-administration/security#audit-logs" + "destination": "/administration-and-security/governance-and-compliance/audit-logs" + }, + { + "source": "/advanced-use/openfeature", + "destination": "/integrating-with-flagsmith/openfeature" }, { "source": "/clients/python", - "destination": "/clients/server-side" + "destination": "/integrating-with-flagsmith/sdks/server-side" }, { "source": "/clients/java", - "destination": "/clients/server-side" + "destination": "/integrating-with-flagsmith/sdks/server-side" }, { "source": "/clients/dotnet", - "destination": "/clients/server-side" + "destination": "/integrating-with-flagsmith/sdks/server-side" }, { "source": "/clients/node", - "destination": "/clients/server-side" + "destination": "/integrating-with-flagsmith/sdks/server-side" }, { "source": "/clients/ruby", - "destination": "/clients/server-side" + "destination": "/integrating-with-flagsmith/sdks/server-side" }, { "source": "/clients/php", - "destination": "/clients/server-side" + "destination": "/integrating-with-flagsmith/sdks/server-side" }, { "source": "/clients/go", - "destination": "/clients/server-side" + "destination": "/integrating-with-flagsmith/sdks/server-side" }, { "source": "/clients/rust", - "destination": "/clients/server-side" + "destination": "/integrating-with-flagsmith/sdks/server-side" }, { "source": "/clients/elixir", - "destination": "/clients/server-side" + "destination": "/integrating-with-flagsmith/sdks/server-side" + }, + { + "source": "/clients/android", + "destination": "/integrating-with-flagsmith/sdks/server-side" + }, + { + "source": "/clients/android/", + "destination": "/integrating-with-flagsmith/sdks/server-side" + }, + { + "source": "/clients/csharp", + "destination": "/integrating-with-flagsmith/sdks/server-side" + }, + { + "source": "/clients/csharp/", + "destination": "/integrating-with-flagsmith/sdks/server-side" + }, + { + "source": "/clients/dart", + "destination": "/integrating-with-flagsmith/sdks/server-side" + }, + { + "source": "/clients/dart/", + "destination": "/integrating-with-flagsmith/sdks/server-side" + }, + { + "source": "/clients/kotlin", + "destination": "/integrating-with-flagsmith/sdks/server-side" + }, + { + "source": "/clients/kotlin/", + "destination": "/integrating-with-flagsmith/sdks/server-side" + }, + { + "source": "/clients/swift", + "destination": "/integrating-with-flagsmith/sdks/server-side" + }, + { + "source": "/clients/swift/", + "destination": "/integrating-with-flagsmith/sdks/server-side" + }, + { + "source": "/clients/02-3rd-party", + "destination": "/third-party-integrations/" + }, + { + "source": "/clients/overview", + "destination": "/integrating-with-flagsmith/" }, { "source": "/deployment/migrating-from-self-hosted-to-cloud", - "destination": "/deployment/importing-and-exporting" + "destination": "/administration-and-security/data-management/import-and-export" }, { "source": "/deployment/locally-api", - "destination": "/deployment/hosting/locally-api" + "destination": "/deployment-self-hosting/hosting-guides/manual-installation" }, { "source": "/deployment/locally-frontend", - "destination": "/deployment/hosting/locally-frontend" + "destination": "/deployment-self-hosting/hosting-guides/manual-installation" }, { "source": "/deployment/docker", - "destination": "/deployment/hosting/docker" + "destination": "/deployment-self-hosting/hosting-guides/docker" }, { "source": "/deployment/kubernetes", - "destination": "/deployment/hosting/kubernetes" + "destination": "/deployment-self-hosting/hosting-guides/kubernetes-openshift" }, { "source": "/deployment/openshift", - "destination": "/deployment/hosting/openshift" + "destination": "/deployment-self-hosting/hosting-guides/kubernetes-openshift" }, { "source": "/deployment/aws", - "destination": "/deployment/hosting/aws" + "destination": "/deployment-self-hosting/hosting-guides/cloud-providers/aws" }, { "source": "/deployment/google-cloud", - "destination": "/deployment/hosting/google-cloud" + "destination": "/deployment-self-hosting/hosting-guides/cloud-providers/google-cloud" }, { "source": "/deployment/enterprise-edition", - "destination": "/deployment/configuration/enterprise-edition" + "destination": "/deployment-self-hosting/enterprise-edition" }, { "source": "/deployment/authentication", - "destination": "/deployment/configuration/authentication" + "destination": "/administration-and-security/access-control/" }, { "source": "/deployment/sizing-and-scaling", - "destination": "/deployment/configuration/sizing-and-scaling" + "destination": "/deployment-self-hosting/scaling-and-performance/sizing-and-scaling" }, { "source": "/deployment/troubleshooting", - "destination": "/deployment/configuration/troubleshooting" + "destination": "/deployment-self-hosting/administration-and-maintenance/troubleshooting" }, { "source": "/deployment/django-admin", - "destination": "/deployment/configuration/django-admin" + "destination": "/deployment-self-hosting/administration-and-maintenance/using-the-django-admin" }, { "source": "/deployment/importing-and-exporting", - "destination": "/deployment/configuration/importing-and-exporting" + "destination": "/administration-and-security/data-management/import-and-export" }, { "source": "/deployment/task-processor", - "destination": "/deployment/configuration/task-processor" + "destination": "/deployment-self-hosting/scaling-and-performance/asynchronous-task-processor" + }, + { + "source": "/deployment/overview", + "destination": "/deployment-self-hosting/" }, { "source": "/deployment/configuration/authentication/ADFS", - "destination": "/system-administration/authentication/ADFS" + "destination": "/administration-and-security/access-control/adfs" }, { "source": "/deployment/configuration/authentication/LDAP", - "destination": "/system-administration/authentication/LDAP" + "destination": "/administration-and-security/access-control/ldap" }, { "source": "/deployment/configuration/authentication/OAuth", - "destination": "/system-administration/authentication/OAuth" + "destination": "/administration-and-security/access-control/oauth" }, { "source": "/deployment/configuration/authentication/Okta", - "destination": "/system-administration/authentication/SAML/Okta" + "destination": "/administration-and-security/access-control/okta" }, { "source": "/deployment/configuration/authentication/SAML", - "destination": "/system-administration/authentication/SAML" + "destination": "/administration-and-security/access-control/saml" }, { "source": "/integrations/amplitude", - "destination": "/integrations/analytics/amplitude" + "destination": "/third-party-integrations/analytics/amplitude" }, { "source": "/integrations/heap", - "destination": "/integrations/analytics/heap" + "destination": "/third-party-integrations/analytics/heap" }, { "source": "/integrations/mixpanel", - "destination": "/integrations/analytics/mixpanel" + "destination": "/third-party-integrations/analytics/mixpanel" }, { "source": "/integrations/rudderstack", - "destination": "/integrations/analytics/rudderstack" + "destination": "/third-party-integrations/analytics/rudderstack" }, { "source": "/integrations/segment", - "destination": "/integrations/analytics/segment" + "destination": "/third-party-integrations/analytics/segment" }, { "source": "/integrations/appdynamics", - "destination": "/integrations/apm/appdynamics" + "destination": "/third-party-integrations/observability-and-monitoring/appdynamics" }, { "source": "/integrations/datadog", - "destination": "/integrations/apm/datadog" + "destination": "/third-party-integrations/observability-and-monitoring/datadog" }, { "source": "/integrations/dynatrace", - "destination": "/integrations/apm/dynatrace" + "destination": "/third-party-integrations/observability-and-monitoring/dynatrace" }, { "source": "/integrations/newrelic", - "destination": "/integrations/apm/newrelic" + "destination": "/third-party-integrations/observability-and-monitoring/newrelic" }, { "source": "/system-administration/edge-proxy", - "destination": "/advanced-use/edge-proxy" + "destination": "/deployment-self-hosting/edge-proxy" }, { "source": "/integrations/slack", - "destination": "/integrations/project-management/slack" - }, - { - "source": "/advanced-use/openfeature", - "destination": "/clients/openfeature" + "destination": "/third-party-integrations/project-management/slack" }, { "source": "/system-administration/importing-and-exporting/data-migration", - "destination": "/system-administration/importing-and-exporting/organisations" + "destination": "/administration-and-security/data-management/organisations-import-export" }, { "source": "/system-administration/metadata", - "destination": "/advanced-use/custom-fields" + "destination": "/administration-and-security/governance-and-compliance/custom-fields" }, { "source": "/system-administration/custom-fields", - "destination": "/advanced-use/custom-fields" - }, - { - "source": "/clients/overview", - "destination": "/clients" + "destination": "/administration-and-security/governance-and-compliance/custom-fields" }, { "source": "/basic-features/overview", - "destination": "/basic-features" + "destination": "/flagsmith-concepts/platform-architecture" }, { "source": "/basic-features/integrations", - "destination": "/integrations" + "destination": "/third-party-integrations/" }, { - "source": "/deployment/overview", - "destination": "/deployment" + "source": "/pricing", + "destination": "/administration-and-security/billing-api-usage" }, { - "source": "/pricing", - "destination": "/billing" + "source": "/billing", + "destination": "/administration-and-security/billing-api-usage" + }, + { + "source": "/platform/contributing", + "destination": "/project-and-community/contributing" + }, + { + "source": "/platform/releases", + "destination": "/project-and-community/release-notes" + }, + { + "source": "/platform/roadmap", + "destination": "/project-and-community/roadmap" + }, + { + "source": "/guides-and-examples/integration-approaches", + "destination": "/best-practices/integration-approaches" + }, + { + "source": "/guides-and-examples/staged-feature-rollouts", + "destination": "/managing-flags/rollout/" + }, + { + "source": "/clients/rest", + "destination": "/integrating-with-flagsmith/flagsmith-api-overview/" + }, + { + "source": "/clients/openfeature", + "destination": "/integrating-with-flagsmith/openfeature" + }, + { + "source": "/clients/server-side", + "destination": "/integrating-with-flagsmith/sdks/server-side" + }, + { + "source": "/clients/client-side", + "destination": "/integrating-with-flagsmith/sdks/client-side-sdks/" + }, + { + "source": "/clients/cli", + "destination": "/integrating-with-flagsmith/CLI" + }, + { + "source": "/integrations/", + "destination": "/third-party-integrations/" + }, + { + "source": "/integrations", + "destination": "/third-party-integrations/" + }, + { + "source": "/advanced-use/", + "destination": "/best-practices/" + }, + { + "source": "/advanced-use", + "destination": "/best-practices/" + }, + { + "source": "/basic-features/", + "destination": "/flagsmith-concepts/" + }, + { + "source": "/basic-features", + "destination": "/flagsmith-concepts/" + }, + { + "source": "/clients/", + "destination": "/integrating-with-flagsmith/" + }, + { + "source": "/clients", + "destination": "/integrating-with-flagsmith/" + }, + { + "source": "/platform/", + "destination": "/project-and-community/" + }, + { + "source": "/platform", + "destination": "/project-and-community/" + }, + { + "source": "/guides-and-examples/", + "destination": "/best-practices/" + }, + { + "source": "/guides-and-examples", + "destination": "/best-practices/" + }, + { + "source": "/advanced-use/change-requests", + "destination": "/administration-and-security/governance-and-compliance/change-requests" + }, + { + "source": "/advanced-use/edge-api", + "destination": "/edge-api/" + }, + { + "source": "/advanced-use/feature-health", + "destination": "/managing-flags/feature-health-metrics" + }, + { + "source": "/advanced-use/flag-analytics", + "destination": "/managing-flags/flag-analytics" + }, + { + "source": "/advanced-use/flag-management", + "destination": "/managing-flags/core-management" + }, + { + "source": "/advanced-use/real-time-flags", + "destination": "/performance/real-time-flags" + }, + { + "source": "/advanced-use/release-pipelines/", + "destination": "/managing-flags/release-pipelines" + }, + { + "source": "/advanced-use/scheduled-flags", + "destination": "/managing-flags/scheduled-flags" + }, + { + "source": "/basic-features/managing-features", + "destination": "/managing-flags/core-management" + }, + { + "source": "/basic-features/managing-identities", + "destination": "/flagsmith-concepts/identities" + }, + { + "source": "/basic-features/segments", + "destination": "/flagsmith-concepts/segments/" + }, + { + "source": "/clients/flutter/", + "destination": "/integrating-with-flagsmith/sdks/client-side-sdks/flutter" + }, + { + "source": "/clients/javascript/", + "destination": "/integrating-with-flagsmith/sdks/client-side-sdks/javascript" + }, + { + "source": "/clients/react", + "destination": "/integrating-with-flagsmith/sdks/client-side-sdks/react" + }, + { + "source": "/clients/next-ssr", + "destination": "/integrating-with-flagsmith/sdks/client-side-sdks/nextjs-and-ssr" + }, + { + "source": "/clients/ios/", + "destination": "/integrating-with-flagsmith/sdks/client-side-sdks/ios" + }, + { + "source": "/deployment", + "destination": "/deployment-self-hosting/" + }, + { + "source": "/deployment/configuration/enterprise-edition", + "destination": "/deployment-self-hosting/enterprise-edition" + }, + { + "source": "/guides-and-examples/defensive-coding", + "destination": "/best-practices/defensive-coding" + }, + { + "source": "/integrations/analytics/amplitude", + "destination": "/third-party-integrations/analytics/amplitude" + }, + { + "source": "/integrations/analytics/heap", + "destination": "/third-party-integrations/analytics/heap" + }, + { + "source": "/integrations/analytics/mixpanel", + "destination": "/third-party-integrations/analytics/mixpanel" + }, + { + "source": "/integrations/analytics/rudderstack", + "destination": "/third-party-integrations/analytics/rudderstack" + }, + { + "source": "/integrations/analytics/segment", + "destination": "/third-party-integrations/analytics/segment" + }, + { + "source": "/integrations/apm/datadog", + "destination": "/third-party-integrations/observability-and-monitoring/datadog" + }, + { + "source": "/integrations/apm/dynatrace", + "destination": "/third-party-integrations/observability-and-monitoring/dynatrace" + }, + { + "source": "/integrations/apm/newrelic", + "destination": "/third-party-integrations/observability-and-monitoring/newrelic" + }, + { + "source": "/integrations/apm/grafana", + "destination": "/third-party-integrations/observability-and-monitoring/grafana" + }, + { + "source": "/integrations/project-management/jira", + "destination": "/third-party-integrations/project-management/jira" + }, + { + "source": "/integrations/webhook", + "destination": "/third-party-integrations/webhook" + }, + { + "source": "/integrations/terraform", + "destination": "/third-party-integrations/ci-cd/terraform" + }, + { + "source": "/quickstart/", + "destination": "/getting-started/" + }, + { + "source": "/system-administration/audit-logs", + "destination": "/administration-and-security/governance-and-compliance/audit-logs" + }, + { + "source": "/system-administration/authentication/", + "destination": "/administration-and-security/access-control/" + }, + { + "source": "/system-administration/rbac", + "destination": "/administration-and-security/access-control/rbac" + }, + { + "source": "/system-administration/security", + "destination": "/administration-and-security/governance-and-compliance/security" + }, + { + "source": "/system-administration/system-limits", + "destination": "/administration-and-security/governance-and-compliance/system-limits" + }, + { + "source": "/clients/", + "destination": "/integrating-with-flagsmith/sdks/" + }, + { + "source": "/clients/server-side", + "destination": "/integrating-with-flagsmith/sdks/server-side" } ] } From 552412b12ffa30e3a2f122f1bf7d5b4bf5c2d1b9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 12:19:03 +0000 Subject: [PATCH 06/11] chore: Track flagsmith-python-sdk 5.0.0 (#6204) Co-authored-by: flagsmith-engineering[bot] Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- api/app_analytics/constants.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/app_analytics/constants.py b/api/app_analytics/constants.py index 7f2fa16b3b12..e145c90d5d57 100644 --- a/api/app_analytics/constants.py +++ b/api/app_analytics/constants.py @@ -31,7 +31,10 @@ "flagsmith-kotlin-android-sdk": ["unknown"], "flagsmith-nodejs-sdk": ["unknown"], "flagsmith-php-sdk": ["unknown"], - "flagsmith-python-sdk": ["unknown"], + "flagsmith-python-sdk": [ + "unknown", + "5.0.0", + ], "flagsmith-ruby-sdk": ["unknown"], "flagsmith-rust-sdk": ["unknown"], "flagsmith-swift-ios-sdk": ["unknown"], From 82f2652b165a14081721e84d299fa593fb903e0d Mon Sep 17 00:00:00 2001 From: Zaimwa9 Date: Tue, 28 Oct 2025 15:30:17 +0100 Subject: [PATCH 07/11] feat: added-query-param-to-get-segment-feature-states (#6156) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- api/features/serializers.py | 24 +++++++ api/features/views.py | 26 ++++++- .../unit/features/test_unit_features_views.py | 72 ++++++++++++++++++- frontend/web/components/modals/CreateFlag.js | 5 +- frontend/web/components/tags/Tag.tsx | 2 +- 5 files changed, 121 insertions(+), 8 deletions(-) diff --git a/api/features/serializers.py b/api/features/serializers.py index 55785e1bda5a..d922dd11ae47 100644 --- a/api/features/serializers.py +++ b/api/features/serializers.py @@ -90,6 +90,10 @@ class FeatureQuerySerializer(serializers.Serializer): # type: ignore[type-arg] required=False, help_text="Integer ID of the environment to view features in the context of.", ) + segment = serializers.IntegerField( + required=False, + help_text="Integer ID of the segment to retrieve segment overrides for.", + ) is_enabled = serializers.BooleanField( allow_null=True, required=False, @@ -141,6 +145,7 @@ class CreateFeatureSerializer(DeleteBeforeUpdateWritableNestedModelSerializer): group_owners = UserPermissionGroupSummarySerializer(many=True, read_only=True) environment_feature_state = serializers.SerializerMethodField() + segment_feature_state = serializers.SerializerMethodField() num_segment_overrides = serializers.SerializerMethodField( help_text="Number of segment overrides that exist for the given feature " @@ -188,6 +193,7 @@ class Meta: "uuid", "project", "environment_feature_state", + "segment_feature_state", "num_segment_overrides", "num_identity_overrides", "is_num_identity_overrides_complete", @@ -296,6 +302,17 @@ def get_environment_feature_state( # type: ignore[return] ): return FeatureStateSerializerSmall(instance=feature_state).data + @swagger_serializer_method( # type: ignore[misc] + serializer_or_field=FeatureStateSerializerSmall(allow_null=True) + ) + def get_segment_feature_state( # type: ignore[return] + self, instance: Feature + ) -> dict[str, Any] | None: + if (segment_feature_states := self.context.get("segment_feature_states")) and ( + segment_feature_state := segment_feature_states.get(instance.id) + ): + return FeatureStateSerializerSmall(instance=segment_feature_state).data + def get_num_segment_overrides(self, instance: Feature) -> int: try: return self.context["overrides_data"][instance.id].num_segment_overrides # type: ignore[no-any-return] @@ -645,6 +662,13 @@ class SDKFeatureStatesQuerySerializer(serializers.Serializer): # type: ignore[t ) +class EnvironmentFeatureStatesQuerySerializer(serializers.Serializer): # type: ignore[type-arg] + segment = serializers.IntegerField( + required=False, + help_text="ID of the segment to filter segment overrides by.", + ) + + class CustomCreateSegmentOverrideFeatureStateSerializer( CreateSegmentOverrideFeatureStateSerializer ): diff --git a/api/features/views.py b/api/features/views.py index 30464c980859..410aa3c0068d 100644 --- a/api/features/views.py +++ b/api/features/views.py @@ -182,18 +182,33 @@ def get_queryset(self): # type: ignore[no-untyped-def] page = self.paginate_queryset(queryset) self.environment = Environment.objects.get(id=environment_id) self.feature_ids = [feature.id for feature in page] - q = Q( + feature_states_query = Q( feature_id__in=self.feature_ids, identity__isnull=True, feature_segment__isnull=True, ) feature_states = get_environment_flags_list( environment=self.environment, - additional_filters=q, + additional_filters=feature_states_query, additional_select_related_args=["feature_state_value", "feature"], ) self._feature_states = {fs.feature_id: fs for fs in feature_states} + if segment_id := query_data.get("segment"): + segment_query = Q( + feature_id__in=self.feature_ids, + identity__isnull=True, + feature_segment__segment_id=segment_id, + ) + segment_feature_states = get_environment_flags_list( + environment=self.environment, + additional_filters=segment_query, + additional_select_related_args=["feature_state_value", "feature"], + ) + self._segment_feature_states = { + fs.feature_id: fs for fs in segment_feature_states + } + return queryset def paginate_queryset(self, queryset: QuerySet[Feature]) -> list[Feature]: # type: ignore[override] @@ -225,9 +240,13 @@ def get_serializer_context(self): # type: ignore[no-untyped-def] return context feature_states = getattr(self, "_feature_states", {}) + segment_feature_states = getattr(self, "_segment_feature_states", {}) project = get_object_or_404(Project.objects.all(), pk=self.kwargs["project_pk"]) context.update( - project=project, user=self.request.user, feature_states=feature_states + project=project, + user=self.request.user, + feature_states=feature_states, + segment_feature_states=segment_feature_states, ) if self.action == "list" and "environment" in self.request.query_params: @@ -664,6 +683,7 @@ class EnvironmentFeatureStateViewSet(BaseFeatureStateViewSet): def get_queryset(self): # type: ignore[no-untyped-def] queryset = super().get_queryset().filter(feature_segment=None) # type: ignore[no-untyped-call] + if "anyIdentity" in self.request.query_params: # TODO: deprecate anyIdentity query parameter return queryset.exclude(identity=None) diff --git a/api/tests/unit/features/test_unit_features_views.py b/api/tests/unit/features/test_unit_features_views.py index e4d5de95dbb6..c53a59e1234a 100644 --- a/api/tests/unit/features/test_unit_features_views.py +++ b/api/tests/unit/features/test_unit_features_views.py @@ -1595,8 +1595,6 @@ def test_environment_feature_states_does_not_return_null_versions( assert len(response_json["results"]) == 1 assert response_json["results"][0]["id"] == feature_state.id - # Feature tests - def test_create_feature_default_is_archived_is_false( admin_client_new: APIClient, project: Project @@ -4077,6 +4075,76 @@ def test_get_multivariate_options_feature_not_found_responds_404( assert response.status_code == status.HTTP_404_NOT_FOUND +def test_list_features_segment_query_param_with_valid_segment( + admin_client_new: APIClient, + project: Project, + feature: Feature, + environment: Environment, + segment: Segment, +) -> None: + # Given + feature_segment = FeatureSegment.objects.create( + feature=feature, + segment=segment, + environment=environment, + ) + segment_override = FeatureState.objects.create( + feature=feature, + feature_segment=feature_segment, + environment=environment, + enabled=True, + ) + segment_override.feature_state_value.string_value = "segment_value" + segment_override.feature_state_value.save() + + base_url = reverse("api-v1:projects:project-features-list", args=[project.id]) + url = f"{base_url}?environment={environment.id}&segment={segment.id}" + + # When + response = admin_client_new.get(url) + + # Then + assert response.status_code == status.HTTP_200_OK + response_json = response.json() + feature_data = next( + filter(lambda r: r["id"] == feature.id, response_json["results"]) + ) + + assert "environment_feature_state" in feature_data + segment_state = feature_data["segment_feature_state"] + + assert segment_state is not None + assert segment_state["id"] == segment_override.id + assert segment_state["feature_state_value"] == "segment_value" + assert segment_state["enabled"] is True + + +def test_list_features_segment_query_param_with_invalid_segment( + admin_client_new: APIClient, + project: Project, + feature: Feature, + environment: Environment, + segment: Segment, +) -> None: + # Given + base_url = reverse("api-v1:projects:project-features-list", args=[project.id]) + invalid_segment_id = segment.id + 9999 + url = f"{base_url}?environment={environment.id}&segment={invalid_segment_id}" + + # When + response = admin_client_new.get(url) + + # Then + assert response.status_code == status.HTTP_200_OK + response_json = response.json() + feature_data = next( + filter(lambda r: r["id"] == feature.id, response_json["results"]) + ) + + assert "segment_feature_state" in feature_data + assert feature_data["segment_feature_state"] is None + + def test_create_multiple_features_with_metadata_keeps_metadata_isolated( admin_client_new: APIClient, project: Project, diff --git a/frontend/web/components/modals/CreateFlag.js b/frontend/web/components/modals/CreateFlag.js index 06caf5cbb209..7f22ce38ba7e 100644 --- a/frontend/web/components/modals/CreateFlag.js +++ b/frontend/web/components/modals/CreateFlag.js @@ -1757,9 +1757,10 @@ const CreateFlag = class extends Component { e.stopPropagation() removeUserOverride( { - cb: () => + cb: () => this.userOverridesPage( - 1, true + 1, + true, ), environmentId: this.props diff --git a/frontend/web/components/tags/Tag.tsx b/frontend/web/components/tags/Tag.tsx index 886b9c701eee..ba1739caa68b 100644 --- a/frontend/web/components/tags/Tag.tsx +++ b/frontend/web/components/tags/Tag.tsx @@ -66,7 +66,7 @@ const Tag: FC = ({ selected, tag, }) => { - const shouldLighten = (color: Color) => getDarkMode() && color.isDark(); + const shouldLighten = (color: Color) => getDarkMode() && color.isDark() const tagColor = Utils.colour(getTagColor(tag, selected)) if (isDot) { return ( From 610cb0a54c7b6bcf92ea3b3ee03d850f47aed0cc Mon Sep 17 00:00:00 2001 From: Zaimwa9 Date: Tue, 28 Oct 2025 17:50:32 +0100 Subject: [PATCH 08/11] fix: switched-allow-context-values-to-disabled (#6216) --- .../web/components/base/select/SearchableSelect.tsx | 2 +- frontend/web/components/segments/Rule/Rule.tsx | 6 ++++++ .../segments/Rule/components/RuleConditionRow.tsx | 12 ++++++------ 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/frontend/web/components/base/select/SearchableSelect.tsx b/frontend/web/components/base/select/SearchableSelect.tsx index 307f6e943904..cd89dd416c3f 100644 --- a/frontend/web/components/base/select/SearchableSelect.tsx +++ b/frontend/web/components/base/select/SearchableSelect.tsx @@ -1,7 +1,7 @@ import Icon from 'components/Icon' import React from 'react' export interface OptionType { - enabled?: boolean + disabled?: boolean label: string value: string } diff --git a/frontend/web/components/segments/Rule/Rule.tsx b/frontend/web/components/segments/Rule/Rule.tsx index 158940f66fcd..1e120485334f 100644 --- a/frontend/web/components/segments/Rule/Rule.tsx +++ b/frontend/web/components/segments/Rule/Rule.tsx @@ -97,6 +97,12 @@ const Rule: React.FC = ({ } else { updates.value = updates.value?.toString().split(':')[0] } + } else if ( + // If previous operator was PERCENTAGE_SPLIT and property was IDENTITY_KEY, reset property + condition?.operator === 'PERCENTAGE_SPLIT' && + condition?.property === RuleContextValues.IDENTITY_KEY + ) { + updates.property = '' } updateCondition(conditionIndex, updates) diff --git a/frontend/web/components/segments/Rule/components/RuleConditionRow.tsx b/frontend/web/components/segments/Rule/components/RuleConditionRow.tsx index f40e7163790a..af7b242d01a4 100644 --- a/frontend/web/components/segments/Rule/components/RuleConditionRow.tsx +++ b/frontend/web/components/segments/Rule/components/RuleConditionRow.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useEffect } from 'react' import Icon from 'components/Icon' import Utils from 'common/utils/utils' import { @@ -67,15 +67,15 @@ const RuleConditionRow: React.FC = ({ if (rule.delete) { return null } + const valuePlaceholder = operatorObj?.hideValue ? 'Value (N/A)' : operatorObj?.valuePlaceholder || 'Value' // TODO: Move this to the parent component in next iteration - const ALLOWED_CONTEXT_VALUES: OptionType[] = [ { - enabled: operator === 'PERCENTAGE_SPLIT', + disabled: operator !== 'PERCENTAGE_SPLIT', label: RuleContextLabels.IDENTITY_KEY, value: RuleContextValues.IDENTITY_KEY, }, @@ -87,7 +87,7 @@ const RuleConditionRow: React.FC = ({ label: RuleContextLabels.ENVIRONMENT_NAME, value: RuleContextValues.ENVIRONMENT_NAME, }, - ]?.filter((option) => !!option.enabled) + ]?.filter((option) => !option.disabled) const isValueFromContext = !!ALLOWED_CONTEXT_VALUES.find( (option) => option.value === rule.property, @@ -133,9 +133,9 @@ const RuleConditionRow: React.FC = ({ = ({ { - if (hasWarning) return openModal2( 'Edit Value', - , ) }} diff --git a/frontend/web/components/segments/Rule/components/TextAreaModal.tsx b/frontend/web/components/segments/Rule/components/TextAreaModal.tsx new file mode 100644 index 000000000000..e898533ceac5 --- /dev/null +++ b/frontend/web/components/segments/Rule/components/TextAreaModal.tsx @@ -0,0 +1,89 @@ +import React, { useState, FC } from 'react' +import InputGroup from 'components/base/forms/InputGroup' +import Button from 'components/base/forms/Button' +import Icon from 'components/Icon' +import Utils from 'common/utils/utils' +import ModalHR from 'components/modals/ModalHR' +import { checkWhitespaceIssues } from '../utils' + +type TextAreaModalProps = { + value: string | number | boolean + onChange?: (value: string) => void + operator?: string +} + +const TextAreaModal: FC = ({ onChange, value, operator }) => { + const [textAreaValue, setTextAreaValue] = useState(value) + const whitespaceCheck = checkWhitespaceIssues(textAreaValue, operator) + const hasWarning = !!whitespaceCheck + + return ( +
+
+ { + const value = Utils.safeParseEventValue(e) + setTextAreaValue(value.replace(/\n/g, '')) + }} + noMargin + inputProps={{ + className: hasWarning ? 'border-warning' : '', + }} + type='text' + className='w-100' + textarea + /> +
+ {hasWarning && ( +
+ + + + {whitespaceCheck?.message} +
+ )} +
+
+ +
+ + +
+
+ ) +} + +export default TextAreaModal diff --git a/frontend/web/components/segments/Rule/utils/index.ts b/frontend/web/components/segments/Rule/utils/index.ts new file mode 100644 index 000000000000..c8e7364dbb59 --- /dev/null +++ b/frontend/web/components/segments/Rule/utils/index.ts @@ -0,0 +1 @@ +export { checkWhitespaceIssues } from './whitespaceValidation' diff --git a/frontend/web/components/segments/Rule/utils/whitespaceValidation.ts b/frontend/web/components/segments/Rule/utils/whitespaceValidation.ts new file mode 100644 index 000000000000..f293fa9aa55b --- /dev/null +++ b/frontend/web/components/segments/Rule/utils/whitespaceValidation.ts @@ -0,0 +1,84 @@ +/** + * Checks for whitespace issues in rule condition values, particularly for IN operator + * @param value - The value to check for whitespace issues + * @param operator - The operator being used (e.g., 'IN') + * @returns Object with warning message if issues found, null otherwise + */ +export const checkWhitespaceIssues = ( + value: string | number | boolean, + operator?: string, +): { message: string } | null => { + if (operator !== 'IN') return null + if (typeof value !== 'string') return null + + const LEADING_WHITESPACE = /^\s/ + const TRAILING_WHITESPACE = /\s$/ + + if (value.length >= 1 && value.trim() === '') { + return { message: 'This value is only whitespaces' } + } + + const items = value.split(',') + + if (items.length > 1) { + const counts = items.reduce( + (acc, item) => { + const hasLeading = LEADING_WHITESPACE.test(item) + const hasTrailing = TRAILING_WHITESPACE.test(item) + + if (hasLeading && hasTrailing) acc.both++ + else if (hasLeading) acc.leading++ + else if (hasTrailing) acc.trailing++ + + return acc + }, + { both: 0, leading: 0, trailing: 0 }, + ) + + const totalIssues = counts.both + counts.leading + counts.trailing + const hasMultipleIssues = + [counts.both > 0, counts.leading > 0, counts.trailing > 0].filter(Boolean) + .length > 1 + + if (totalIssues > 0) { + if (hasMultipleIssues) { + return { + message: `${totalIssues} item(s) have whitespace issues`, + } + } + + if (counts.both > 0) { + return { + message: `${counts.both} item(s) have leading and trailing whitespaces`, + } + } + if (counts.leading > 0) { + return { + message: `${counts.leading} item(s) have leading whitespaces`, + } + } + if (counts.trailing > 0) { + return { + message: `${counts.trailing} item(s) have trailing whitespaces`, + } + } + } + } + + const hasLeading = LEADING_WHITESPACE.test(value) + const hasTrailing = TRAILING_WHITESPACE.test(value) + + if (hasLeading && hasTrailing) { + return { + message: 'This value starts and ends with whitespaces', + } + } + if (hasLeading) { + return { message: 'This value starts with whitespaces' } + } + if (hasTrailing) { + return { message: 'This value ends with whitespaces' } + } + + return null +} diff --git a/frontend/web/styles/project/_forms.scss b/frontend/web/styles/project/_forms.scss index 3ae3dc4c0a8d..9dcd50ca1618 100644 --- a/frontend/web/styles/project/_forms.scss +++ b/frontend/web/styles/project/_forms.scss @@ -123,6 +123,19 @@ font-weight: 500; } +.rule-value-icon { + transition: all 0.2s ease-in-out; + + &:hover { + transform: scale(1.05); + } + + &:active { + transform: scale(0.95); + transition: all 0.1s ease-in-out; + } +} + .setting { label { font-size: $font-size-base; From b532c1d9ab37adff20205486171693a484daa2e8 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Tue, 28 Oct 2025 18:23:26 +0000 Subject: [PATCH 10/11] docs: reinstate help and support page (#6218) --- docs/docs/support/index.mdx | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 docs/docs/support/index.mdx diff --git a/docs/docs/support/index.mdx b/docs/docs/support/index.mdx new file mode 100644 index 000000000000..3a1b3dc34d3d --- /dev/null +++ b/docs/docs/support/index.mdx @@ -0,0 +1,34 @@ +--- +title: Help and Support +sidebar_position: 130 +sidebar_label: Help and Support +--- + +We are here to help! You can contact us for support through any of these channels: + +* Click the chat bubble (💬) at the bottom right of this page, or from the Flagsmith dashboard. +* Email [support@flagsmith.com](mailto:support@flagsmith.com). + +## Enterprise support + +[Flagsmith Enterprise](https://www.flagsmith.com/pricing) customers can also use these support channels: + +* Dedicated Customer Success manager for personalised assistance and training. +* Shared Slack channel for real-time group support between your organisation and the Flagsmith team. + +## Bug reports and pull requests + +Flagsmith is open source, and we encourage contributions from the community. If you want to submit a specific bug +report or code change, you can open an issue or pull request directly in the relevant GitHub repository: + +* [Flagsmith API, dashboard and documentation](https://github.com/Flagsmith/flagsmith) +* Client-side SDKs: + - [JavaScript, React](https://github.com/Flagsmith/flagsmith-js-client) + - [iOS](https://github.com/Flagsmith/flagsmith-ios-client) + - [Android](https://github.com/Flagsmith/flagsmith-kotlin-android-client) + - [Flutter](https://github.com/flagsmith/flagsmith-flutter-client) +* [Server-side SDKs](/integrating-with-flagsmith/sdks/server-side) +* [Edge Proxy](https://github.com/Flagsmith/edge-proxy) +* [Terraform provider](https://github.com/Flagsmith/terraform-provider-flagsmith) +* [Flagsmith CLI](https://github.com/Flagsmith/flagsmith-cli) +* [Kubernetes Helm charts](https://github.com/Flagsmith/flagsmith-charts) From d7b8a6e16c3f21b7ec3b96d84ad4d5383071ac67 Mon Sep 17 00:00:00 2001 From: Zaimwa9 Date: Wed, 29 Oct 2025 17:52:12 +0100 Subject: [PATCH 11/11] fix: properly deal with context values dropdown (#6222) --- frontend/common/stores/base/_store.js | 1 - frontend/common/types/rules.types.ts | 14 ++++++ .../web/components/segments/Rule/Rule.tsx | 30 ++++++----- .../RuleConditionPropertySelect.tsx | 10 +--- .../Rule/components/RuleConditionRow.tsx | 50 ++++--------------- .../components/RuleConditionValueInput.tsx | 8 +-- .../Rule/components/TextAreaModal.tsx | 10 ++-- .../components/segments/Rule/hooks/index.ts | 2 + .../segments/Rule/hooks/useRuleContext.ts | 15 ++++++ .../segments/Rule/hooks/useRuleOperator.ts | 36 +++++++++++++ .../components/segments/Rule/utils/index.ts | 6 +++ .../segments/Rule/utils/segmentRules.ts | 46 +++++++++++++++++ .../segments/Rule/utils/splitOperator.ts | 5 ++ 13 files changed, 160 insertions(+), 73 deletions(-) create mode 100644 frontend/web/components/segments/Rule/hooks/index.ts create mode 100644 frontend/web/components/segments/Rule/hooks/useRuleContext.ts create mode 100644 frontend/web/components/segments/Rule/hooks/useRuleOperator.ts create mode 100644 frontend/web/components/segments/Rule/utils/segmentRules.ts create mode 100644 frontend/web/components/segments/Rule/utils/splitOperator.ts diff --git a/frontend/common/stores/base/_store.js b/frontend/common/stores/base/_store.js index 1f17ec97fff2..724776c253f2 100644 --- a/frontend/common/stores/base/_store.js +++ b/frontend/common/stores/base/_store.js @@ -15,7 +15,6 @@ const DEFAULT_ERROR_EVENT = 'problem' module.exports = Object.assign({}, EventEmitter.prototype, { _maxListeners: Number.MAX_VALUE, changed() { - // console.log('change', this.id) this.trigger(DEFAULT_CHANGE_EVENT) }, error: null, diff --git a/frontend/common/types/rules.types.ts b/frontend/common/types/rules.types.ts index 10dad45c7ca7..354a2183df76 100644 --- a/frontend/common/types/rules.types.ts +++ b/frontend/common/types/rules.types.ts @@ -9,3 +9,17 @@ export enum RuleContextValues { IDENTITY_KEY = '$.identity.key', ENVIRONMENT_NAME = '$.environment.name', } + +export type OperatorValue = + | 'EQUAL' + | 'NOT_EQUAL' + | 'PERCENTAGE_SPLIT' + | 'GREATER' + | 'LESS' + | 'IS_SET' + | 'IS_NOT_SET' + | 'CONTAINS' + | 'NOT_CONTAINS' + | 'REGEX' + | 'MODULO' + | 'IN' diff --git a/frontend/web/components/segments/Rule/Rule.tsx b/frontend/web/components/segments/Rule/Rule.tsx index 1e120485334f..6dc10df6999f 100644 --- a/frontend/web/components/segments/Rule/Rule.tsx +++ b/frontend/web/components/segments/Rule/Rule.tsx @@ -10,12 +10,12 @@ import { } from 'common/types/responses' import { RuleContextValues } from 'common/types/rules.types' import RuleConditionRow from './components/RuleConditionRow' - -const splitIfValue = (v: string | null | number, append: string) => - append && typeof v === 'string' ? v.split(append) : [v === null ? '' : v] - -const isInvalidPercentageSplit = (value: string | boolean | number) => - `${value}`?.match(/\D/) || (parseInt(value?.toString() || '0') as any) > 100 +import { + isContextValueValidForOperator, + shouldAutoSetIdentityKey, + splitIfValue, + isInvalidPercentageSplit, +} from './utils' interface RuleProps { index: number @@ -85,11 +85,15 @@ const Rule: React.FC = ({ updates.value = cleanValue + (newOperator?.append || '') } - if (operatorValue === 'PERCENTAGE_SPLIT') { - if (!condition.property) { - updates.property = RuleContextValues.IDENTITY_KEY - } + if (shouldAutoSetIdentityKey(operatorValue, condition.property)) { + updates.property = RuleContextValues.IDENTITY_KEY + } + if (!isContextValueValidForOperator(condition.property, operatorValue)) { + updates.property = '' + } + + if (operatorValue === 'PERCENTAGE_SPLIT') { const invalidPercentageSplit = condition?.value && isInvalidPercentageSplit(condition.value) if (invalidPercentageSplit) { @@ -97,12 +101,6 @@ const Rule: React.FC = ({ } else { updates.value = updates.value?.toString().split(':')[0] } - } else if ( - // If previous operator was PERCENTAGE_SPLIT and property was IDENTITY_KEY, reset property - condition?.operator === 'PERCENTAGE_SPLIT' && - condition?.property === RuleContextValues.IDENTITY_KEY - ) { - updates.property = '' } updateCondition(conditionIndex, updates) diff --git a/frontend/web/components/segments/Rule/components/RuleConditionPropertySelect.tsx b/frontend/web/components/segments/Rule/components/RuleConditionPropertySelect.tsx index 59c370328eb0..6f5f032f8f48 100644 --- a/frontend/web/components/segments/Rule/components/RuleConditionPropertySelect.tsx +++ b/frontend/web/components/segments/Rule/components/RuleConditionPropertySelect.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useState } from 'react' import { components } from 'react-select/lib/components' import Utils from 'common/utils/utils' -import { RuleContextValues } from 'common/types/rules.types' import Constants from 'common/constants' import { GroupLabel } from 'components/base/select/SearchableSelect' import SearchableSelect, { @@ -33,16 +32,9 @@ const RuleConditionPropertySelect = ({ }: RuleConditionPropertySelectProps) => { const [localCurrentValue, setLocalCurrentValue] = useState(propertyValue) - // TODO: Clean this up when enabled useEffect(() => { - if (operator === 'PERCENTAGE_SPLIT' && !propertyValue) { - setRuleProperty(ruleIndex, 'property', { - value: RuleContextValues.IDENTITY_KEY, - }) - } setLocalCurrentValue(propertyValue) - //eslint-disable-next-line - }, [propertyValue, operator, ruleIndex]) + }, [propertyValue]) // Filter invalid context values from flagsmith and format them as options const contextOptions = allowedContextValues?.map( diff --git a/frontend/web/components/segments/Rule/components/RuleConditionRow.tsx b/frontend/web/components/segments/Rule/components/RuleConditionRow.tsx index df48a9935e52..4c02181e2134 100644 --- a/frontend/web/components/segments/Rule/components/RuleConditionRow.tsx +++ b/frontend/web/components/segments/Rule/components/RuleConditionRow.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react' +import React from 'react' import Icon from 'components/Icon' import Utils from 'common/utils/utils' import { @@ -12,8 +12,7 @@ import ErrorMessage from 'components/ErrorMessage' import RuleConditionPropertySelect from './RuleConditionPropertySelect' import RuleConditionValueInput from './RuleConditionValueInput' import { RuleContextValues } from 'common/types/rules.types' -import { RuleContextLabels } from 'common/types/rules.types' -import { OptionType } from 'components/base/select/SearchableSelect' +import { useRuleOperator, useRuleContext } from 'components/segments/Rule/hooks' interface RuleConditionRowProps { rule: SegmentCondition @@ -57,46 +56,17 @@ const RuleConditionRow: React.FC = ({ const isLastRule = ruleIndex === lastIndex const hasOr = ruleIndex > 0 - const operatorObj = Utils.findOperator(rule.operator, rule.value, operators) - const operator = operatorObj && operatorObj.value - const value = - typeof rule.value === 'string' - ? rule.value.replace((operatorObj && operatorObj.append) || '', '') - : rule.value + + const { displayValue, operator, operatorObj, valuePlaceholder } = + useRuleOperator(rule, operators) + + const { allowedContextValues, isValueFromContext, showEnvironmentDropdown } = + useRuleContext(operator, rule.property) if (rule.delete) { return null } - const valuePlaceholder = operatorObj?.hideValue - ? 'Value (N/A)' - : operatorObj?.valuePlaceholder || 'Value' - - // TODO: Move this to the parent component in next iteration - const ALLOWED_CONTEXT_VALUES: OptionType[] = [ - { - disabled: operator !== 'PERCENTAGE_SPLIT', - label: RuleContextLabels.IDENTITY_KEY, - value: RuleContextValues.IDENTITY_KEY, - }, - { - label: RuleContextLabels.IDENTIFIER, - value: RuleContextValues.IDENTIFIER, - }, - { - label: RuleContextLabels.ENVIRONMENT_NAME, - value: RuleContextValues.ENVIRONMENT_NAME, - }, - ]?.filter((option) => !option.disabled) - - const isValueFromContext = !!ALLOWED_CONTEXT_VALUES.find( - (option) => option.value === rule.property, - )?.value - - const showEnvironmentDropdown = - ['EQUAL', 'NOT_EQUAL'].includes(rule.operator) && - rule.property === RuleContextValues.ENVIRONMENT_NAME - const showEvaluationContextWarning = isLastRule && isValueFromContext const isSkippingEvaluationContextWarning = operator === 'PERCENTAGE_SPLIT' && @@ -124,7 +94,7 @@ const RuleConditionRow: React.FC = ({ setRuleProperty={setRuleProperty} propertyValue={rule.property} operator={rule.operator} - allowedContextValues={ALLOWED_CONTEXT_VALUES || []} + allowedContextValues={allowedContextValues} isValueFromContext={isValueFromContext} /> {readOnly ? ( @@ -143,7 +113,7 @@ const RuleConditionRow: React.FC = ({ = ({ onClick={() => { openModal2( 'Edit Value', - , ) diff --git a/frontend/web/components/segments/Rule/components/TextAreaModal.tsx b/frontend/web/components/segments/Rule/components/TextAreaModal.tsx index e898533ceac5..f5f54af0d40e 100644 --- a/frontend/web/components/segments/Rule/components/TextAreaModal.tsx +++ b/frontend/web/components/segments/Rule/components/TextAreaModal.tsx @@ -4,7 +4,7 @@ import Button from 'components/base/forms/Button' import Icon from 'components/Icon' import Utils from 'common/utils/utils' import ModalHR from 'components/modals/ModalHR' -import { checkWhitespaceIssues } from '../utils' +import { checkWhitespaceIssues } from 'components/segments/Rule/utils' type TextAreaModalProps = { value: string | number | boolean @@ -12,7 +12,11 @@ type TextAreaModalProps = { operator?: string } -const TextAreaModal: FC = ({ onChange, value, operator }) => { +const TextAreaModal: FC = ({ + onChange, + operator, + value, +}) => { const [textAreaValue, setTextAreaValue] = useState(value) const whitespaceCheck = checkWhitespaceIssues(textAreaValue, operator) const hasWarning = !!whitespaceCheck @@ -39,8 +43,8 @@ const TextAreaModal: FC = ({ onChange, value, operator }) =>
{hasWarning && ( diff --git a/frontend/web/components/segments/Rule/hooks/index.ts b/frontend/web/components/segments/Rule/hooks/index.ts new file mode 100644 index 000000000000..790dfc48ad99 --- /dev/null +++ b/frontend/web/components/segments/Rule/hooks/index.ts @@ -0,0 +1,2 @@ +export { useRuleOperator } from './useRuleOperator' +export { useRuleContext } from './useRuleContext' diff --git a/frontend/web/components/segments/Rule/hooks/useRuleContext.ts b/frontend/web/components/segments/Rule/hooks/useRuleContext.ts new file mode 100644 index 000000000000..b26594c342c3 --- /dev/null +++ b/frontend/web/components/segments/Rule/hooks/useRuleContext.ts @@ -0,0 +1,15 @@ +import { OperatorValue, RuleContextValues } from 'common/types/rules.types' +import { getAllowedContextValuesForDropdown } from 'components/segments/Rule/utils' + +export const useRuleContext = (operator: OperatorValue, property: string) => { + const allowedContextValues = getAllowedContextValuesForDropdown(operator) + + const isValueFromContext = allowedContextValues.some( + (option) => option.value === property, + ) + const showEnvironmentDropdown = + ['EQUAL', 'NOT_EQUAL'].includes(operator) && + property === RuleContextValues.ENVIRONMENT_NAME + + return { allowedContextValues, isValueFromContext, showEnvironmentDropdown } +} diff --git a/frontend/web/components/segments/Rule/hooks/useRuleOperator.ts b/frontend/web/components/segments/Rule/hooks/useRuleOperator.ts new file mode 100644 index 000000000000..99c804850308 --- /dev/null +++ b/frontend/web/components/segments/Rule/hooks/useRuleOperator.ts @@ -0,0 +1,36 @@ +import Utils from 'common/utils/utils' +import { Operator, SegmentCondition } from 'common/types/responses' +import { OperatorValue } from 'common/types/rules.types' + +type UseRuleOperatorResult = { + displayValue: string | number | boolean | null + operator: OperatorValue + operatorObj: Operator + valuePlaceholder: string +} + +export const useRuleOperator = ( + rule: SegmentCondition, + operators: Operator[], +): UseRuleOperatorResult => { + const selectedOperatorObj = Utils.findOperator( + rule.operator, + rule.value, + operators, + ) + const operator = selectedOperatorObj?.value + const displayValue = + typeof rule.value === 'string' + ? rule.value.replace(selectedOperatorObj?.append || '', '') + : rule.value + const valuePlaceholder = selectedOperatorObj?.hideValue + ? 'Value (N/A)' + : selectedOperatorObj?.valuePlaceholder || 'Value' + + return { + displayValue, + operator, + operatorObj: selectedOperatorObj, + valuePlaceholder, + } +} diff --git a/frontend/web/components/segments/Rule/utils/index.ts b/frontend/web/components/segments/Rule/utils/index.ts index c8e7364dbb59..1525bd8e2fec 100644 --- a/frontend/web/components/segments/Rule/utils/index.ts +++ b/frontend/web/components/segments/Rule/utils/index.ts @@ -1 +1,7 @@ export { checkWhitespaceIssues } from './whitespaceValidation' +export { + getAllowedContextValuesForDropdown, + isContextValueValidForOperator, + shouldAutoSetIdentityKey, +} from './segmentRules' +export { splitIfValue, isInvalidPercentageSplit } from './splitOperator' diff --git a/frontend/web/components/segments/Rule/utils/segmentRules.ts b/frontend/web/components/segments/Rule/utils/segmentRules.ts new file mode 100644 index 000000000000..dee810cf8fca --- /dev/null +++ b/frontend/web/components/segments/Rule/utils/segmentRules.ts @@ -0,0 +1,46 @@ +import { RuleContextValues, RuleContextLabels } from 'common/types/rules.types' +import { OptionType } from 'components/base/select/SearchableSelect' + +export const getAllowedContextValuesForDropdown = ( + operator: string, +): OptionType[] => { + const baseValues: OptionType[] = [ + { + label: RuleContextLabels.IDENTIFIER, + value: RuleContextValues.IDENTIFIER, + }, + { + label: RuleContextLabels.ENVIRONMENT_NAME, + value: RuleContextValues.ENVIRONMENT_NAME, + }, + ] + + if (operator === 'PERCENTAGE_SPLIT') { + return [ + { + label: RuleContextLabels.IDENTITY_KEY, + value: RuleContextValues.IDENTITY_KEY, + }, + ...baseValues, + ] + } + + return baseValues +} + +export const isContextValueValidForOperator = ( + contextValue: string | undefined, + operator: string, +): boolean => { + if (contextValue && contextValue === RuleContextValues.IDENTITY_KEY) { + return operator === 'PERCENTAGE_SPLIT' + } + return true +} + +export const shouldAutoSetIdentityKey = ( + operator: string, + property: string | undefined, +): boolean => { + return operator === 'PERCENTAGE_SPLIT' && !property +} diff --git a/frontend/web/components/segments/Rule/utils/splitOperator.ts b/frontend/web/components/segments/Rule/utils/splitOperator.ts new file mode 100644 index 000000000000..4ece54eabab4 --- /dev/null +++ b/frontend/web/components/segments/Rule/utils/splitOperator.ts @@ -0,0 +1,5 @@ +export const splitIfValue = (v: string | null | number, append: string) => + append && typeof v === 'string' ? v.split(append) : [v === null ? '' : v] + +export const isInvalidPercentageSplit = (value: string | boolean | number) => + `${value}`?.match(/\D/) || (parseInt(value?.toString() || '0') as any) > 100