diff --git a/.github/images/console-dark.png b/.github/images/console-dark.png
new file mode 100644
index 0000000000..41519d0783
Binary files /dev/null and b/.github/images/console-dark.png differ
diff --git a/.github/images/console.png b/.github/images/console.png
index 37c5b9e865..0645a98d9c 100644
Binary files a/.github/images/console.png and b/.github/images/console.png differ
diff --git a/.github/images/darkmode.png b/.github/images/darkmode.png
deleted file mode 100644
index f9a5d7eb01..0000000000
Binary files a/.github/images/darkmode.png and /dev/null differ
diff --git a/.github/images/flags.png b/.github/images/flags.png
index d986924041..77cf7e005b 100644
Binary files a/.github/images/flags.png and b/.github/images/flags.png differ
diff --git a/README.md b/README.md
index 856aba0e69..6abc3b39a9 100644
--- a/README.md
+++ b/README.md
@@ -86,14 +86,13 @@ Flipt supports use cases such as:
- Ability to create advanced distribution rules to target segments of users
- Native [GRPC](https://grpc.io/) client [SDKs](#grpc-client-libraries) to integrate with your existing GRPC architecture
- Powerful REST API with native [SDKs](#rest-client-libraries) for easy integration
-- Modern, mobile-friendly 📱 UI and debug console
-- Dark mode 🌙
+- Modern, mobile-friendly 📱 UI and debug console with dark mode 🌙
- Supports multiple databases: Postgres, MySQL, SQLite, CockroachDB
- Import and export to allow storing your data as code
-- Cloud-ready :cloud:. Runs anywhere: bare metal, PaaS, K8s, with Docker or without
-- Works with [Prometheus](https://prometheus.io/) and [OpenTelemetry](https://opentelemetry.io/) out of the box
-- [Filesystem, S3, and Git storage backends](https://www.flipt.io/docs/experimental/filesystem-backends)
-- Audit logging to track changes to your data
+- Cloud-ready. Runs anywhere: bare metal, PaaS, K8s, with Docker or without
+- Works with [Prometheus](https://prometheus.io/) and [OpenTelemetry](https://opentelemetry.io/) out of the box 🔋
+- [Filesystem, S3, and Git storage backends](https://www.flipt.io/docs/configuration/storage#filesystem) to support GitOps workflows
+- Audit logging with webhooks support to track changes to your data
Are we missing a feature that you'd like to see? [Let us know!](https://features.flipt.io)
@@ -101,7 +100,7 @@ Are we missing a feature that you'd like to see? [Let us know!](https://features
@@ -180,6 +179,15 @@ docker run --rm -p 8080:8080 -p 9000:9000 -t flipt/flipt:nightly
+## Release Cadence
+
+Flipt follows [semantic versioning](https://semver.org/) for versioning.
+
+We aim to release a new minor version of Flipt every 2-3 weeks. This allows us to quickly iterate on new features.
+Bug fixes and security patches (patch versions) will be released as needed.
+
+
+
## Integration
Check out the [integration documentation](https://flipt.io/docs/integration/) for more info on how to integrate Flipt into your existing applications.
@@ -251,7 +259,7 @@ There are currently two types of licenses in place for Flipt:
### Client License
-All of the code required to generate GRPC clients in other languages as well as the existing GRPC Go client are licensed under the [MIT License](https://spdx.org/licenses/MIT.html).
+All of the code required to generate GRPC clients in other languages as well as the [Go SDK](/sdk/go) are licensed under the [MIT License](https://spdx.org/licenses/MIT.html).
This code exists in the [rpc/](rpc/) directory.
diff --git a/build/generate/screenshots.go b/build/generate/screenshots.go
index 65ae117e54..5e7477ce00 100644
--- a/build/generate/screenshots.go
+++ b/build/generate/screenshots.go
@@ -14,6 +14,9 @@ import (
)
func Screenshots(ctx context.Context, client *dagger.Client, flipt *dagger.Container) error {
+ if err := os.RemoveAll("./screenshots"); err != nil {
+ return err
+ }
src := client.Host().Directory("./ui/", dagger.HostDirectoryOpts{
Include: []string{
"./package.json",
@@ -78,28 +81,31 @@ func Screenshots(ctx context.Context, client *dagger.Client, flipt *dagger.Conta
close(containers)
}()
- for _, entry := range entries {
- entry := entry
- g.Go(func() error {
- test, err := buildUI(ctx, ui, flipt)
- if err != nil {
- return err
- }
+ for _, theme := range []string{"", "dark"} {
+ theme := theme
+ for _, entry := range entries {
+ entry := entry
+ g.Go(func() error {
+ test, err := buildUI(ctx, ui, flipt, theme)
+ if err != nil {
+ return err
+ }
- if ext := path.Ext(entry); ext != ".js" {
- return nil
- }
+ if ext := path.Ext(entry); ext != ".js" {
+ return nil
+ }
- c, err := test.WithExec([]string{"node", path.Join("screenshot", dir, entry)}).Sync(ctx)
- if err != nil {
- return err
- }
+ c, err := test.WithExec([]string{"node", path.Join("screenshot", dir, entry)}).Sync(ctx)
+ if err != nil {
+ return err
+ }
- containers <- c
- log.Printf("Generating screenshot for %s/%s\n", dir, entry)
+ containers <- c
+ log.Printf("Generating screenshot for %s %s/%s\n", theme, dir, entry)
- return err
- })
+ return err
+ })
+ }
}
for c := range containers {
@@ -113,7 +119,7 @@ func Screenshots(ctx context.Context, client *dagger.Client, flipt *dagger.Conta
return err
}
-func buildUI(ctx context.Context, ui, flipt *dagger.Container) (_ *dagger.Container, err error) {
+func buildUI(ctx context.Context, ui, flipt *dagger.Container, theme string) (_ *dagger.Container, err error) {
flipt, err = flipt.Sync(ctx)
if err != nil {
return nil, err
@@ -124,13 +130,17 @@ func buildUI(ctx context.Context, ui, flipt *dagger.Container) (_ *dagger.Contai
return nil, err
}
+ flipt = flipt.
+ WithEnvVariable("CI", os.Getenv("CI")).
+ WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_TOKEN_ENABLED", "true").
+ WithEnvVariable("UNIQUE", time.Now().String()).
+ WithExposedPort(8080)
+
+ if theme != "" {
+ flipt = flipt.WithEnvVariable("FLIPT_UI_DEFAULT_THEME", theme)
+ }
+
return ui.
- WithServiceBinding("flipt", flipt.
- WithEnvVariable("CI", os.Getenv("CI")).
- WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_TOKEN_ENABLED", "true").
- WithEnvVariable("UNIQUE", time.Now().String()).
- WithExposedPort(8080).
- WithExec(nil)).
- WithFile("/usr/bin/flipt", flipt.File("/flipt")).
+ WithServiceBinding("flipt", flipt.WithExec(nil)).WithFile("/usr/bin/flipt", flipt.File("/flipt")).
WithEnvVariable("FLIPT_ADDRESS", "http://flipt:8080"), nil
}
diff --git a/sdk/go/LICENSE b/sdk/go/LICENSE
new file mode 100644
index 0000000000..340b4171ff
--- /dev/null
+++ b/sdk/go/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Flipt Software Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/ui/screenshot.js b/ui/screenshot.js
index 2ccd9646ef..c7f5cb0e57 100644
--- a/ui/screenshot.js
+++ b/ui/screenshot.js
@@ -18,15 +18,11 @@ const scrollToBottom = async (page) => {
const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay));
const capture = async function (folder, name, fn, opts = {}) {
- if (!opts.namespace) {
- opts.namespace = 'default';
- }
-
try {
const path = `${__dirname}/screenshot/${folder}/fixtures/${name}.yml`;
if (fs.existsSync(path)) {
exec(
- `flipt import --create-namespace --namespace=${opts.namespace} --address=${fliptAddr} ${path}`,
+ `flipt import --address=${fliptAddr} ${path}`,
(error, stdout, stderr) => {
if (error) {
console.error(`error: ${error.message}`);
@@ -56,8 +52,9 @@ const capture = async function (folder, name, fn, opts = {}) {
await page.goto(fliptAddr);
await fn(page);
- await sleep(4000);
- await screenshot(page, `${folder}/${name}.png`);
+ await sleep(5000);
+ let random = Math.floor(Math.random() * 100000);
+ await screenshot(page, `${folder}/${name}${random}.png`);
await context.close();
await browser.close();
diff --git a/ui/screenshot/concepts/distributions.js b/ui/screenshot/concepts/distributions.js
index 045849f7d6..2861ed91f2 100644
--- a/ui/screenshot/concepts/distributions.js
+++ b/ui/screenshot/concepts/distributions.js
@@ -3,6 +3,6 @@ const { capture } = require('../../screenshot.js');
(async () => {
await capture('concepts', 'distributions', async (page) => {
await page.getByRole('link', { name: 'colorscheme' }).click();
- await page.getByRole('link', { name: 'Evaluation' }).click();
+ await page.getByRole('link', { name: 'Rules' }).click();
});
})();
diff --git a/ui/screenshot/concepts/namespaces_production.js b/ui/screenshot/concepts/namespaces_production.js
index c21dff0ff4..55455badf4 100644
--- a/ui/screenshot/concepts/namespaces_production.js
+++ b/ui/screenshot/concepts/namespaces_production.js
@@ -1,13 +1,8 @@
const { capture } = require('../../screenshot.js');
(async () => {
- await capture(
- 'concepts',
- 'namespaces_production',
- async (page) => {
- await page.getByRole('button', { name: 'Default' }).click();
- await page.getByText('production').click();
- },
- { namespace: 'production' }
- );
+ await capture('concepts', 'namespaces_production', async (page) => {
+ await page.getByRole('button', { name: 'Default' }).click();
+ await page.getByText('production').click();
+ });
})();
diff --git a/ui/screenshot/concepts/rules.js b/ui/screenshot/concepts/rules.js
index f55c2d0fba..9582a9b5d3 100644
--- a/ui/screenshot/concepts/rules.js
+++ b/ui/screenshot/concepts/rules.js
@@ -3,6 +3,6 @@ const { capture } = require('../../screenshot.js');
(async () => {
await capture('concepts', 'rules', async (page) => {
await page.getByRole('link', { name: 'colorscheme' }).click();
- await page.getByRole('link', { name: 'Evaluation' }).click();
+ await page.getByRole('link', { name: 'Rules' }).click();
});
})();
diff --git a/ui/screenshot/extra/darkmode.js b/ui/screenshot/extra/darkmode.js
deleted file mode 100644
index 89f7510db8..0000000000
--- a/ui/screenshot/extra/darkmode.js
+++ /dev/null
@@ -1,12 +0,0 @@
-const { capture } = require('../../screenshot.js');
-
-(async () => {
- await capture('extra', 'darkmode', async (page) => {
- await page.getByRole('link', { name: 'Settings' }).click();
- await page.getByLabel('Theme').selectOption('dark');
- await page
- .getByLabel('UTC TimezoneDisplay dates and times in UTC timezone')
- .click();
- await page.getByRole('link', { name: 'Flags' }).click();
- });
-})();
diff --git a/ui/screenshot/getting_started/create-rule.js b/ui/screenshot/getting_started/create-rule.js
index c232162cb3..d4943456a4 100644
--- a/ui/screenshot/getting_started/create-rule.js
+++ b/ui/screenshot/getting_started/create-rule.js
@@ -3,7 +3,7 @@ const { capture } = require('../../screenshot.js');
(async () => {
await capture('getting_started', 'create_rule', async (page) => {
await page.getByRole('link', { name: 'new-login' }).click();
- await page.getByRole('link', { name: 'Evaluation' }).click();
+ await page.getByRole('link', { name: 'Rules' }).click();
await page.getByRole('button', { name: 'New Rule' }).click();
await page.locator('#segmentKey-select-button').click();
await page.getByText('all-users').click();