diff --git a/console/src/app/modules/policies/private-labeling-policy/private-labeling-policy.component.html b/console/src/app/modules/policies/private-labeling-policy/private-labeling-policy.component.html index 6ec47521a8..b649ef7869 100644 --- a/console/src/app/modules/policies/private-labeling-policy/private-labeling-policy.component.html +++ b/console/src/app/modules/policies/private-labeling-policy/private-labeling-policy.component.html @@ -7,27 +7,11 @@
- - -
- - {{ 'POLICY.PRIVATELABELING.LIGHT' | translate }} -
-
-
- -
- - {{ 'POLICY.PRIVATELABELING.DARK' | translate }} -
-
-
-
- @@ -48,6 +32,109 @@
+ + +
+ + +
+ + {{ 'POLICY.PRIVATELABELING.THEMEMODE.THEME_MODE_AUTO' | translate }} +
+
+
+ +
+ + {{ 'POLICY.PRIVATELABELING.THEMEMODE.THEME_MODE_LIGHT' | translate }} +
+
+
+ +
+ + {{ 'POLICY.PRIVATELABELING.THEMEMODE.THEME_MODE_DARK' | translate }} +
+
+
+
+ + + +
+ + {{ 'POLICY.PRIVATELABELING.LIGHT' | translate }} +
+
+
+ +
+ + {{ 'POLICY.PRIVATELABELING.DARK' | translate }} +
+
+
+
diff --git a/console/src/app/modules/policies/private-labeling-policy/private-labeling-policy.component.ts b/console/src/app/modules/policies/private-labeling-policy/private-labeling-policy.component.ts index 6e579352de..d81a1eb5ab 100644 --- a/console/src/app/modules/policies/private-labeling-policy/private-labeling-policy.component.ts +++ b/console/src/app/modules/policies/private-labeling-policy/private-labeling-policy.component.ts @@ -15,7 +15,7 @@ import { UpdateCustomLabelPolicyRequest, } from 'src/app/proto/generated/zitadel/management_pb'; import { Org } from 'src/app/proto/generated/zitadel/org_pb'; -import { LabelPolicy } from 'src/app/proto/generated/zitadel/policy_pb'; +import { LabelPolicy, ThemeMode } from 'src/app/proto/generated/zitadel/policy_pb'; import { AdminService } from 'src/app/services/admin.service'; import { AssetEndpoint, AssetService, AssetType } from 'src/app/services/asset.service'; import { ManagementService } from 'src/app/services/mgmt.service'; @@ -88,6 +88,7 @@ export class PrivateLabelingPolicyComponent implements OnInit, OnDestroy { public View: any = View; public ColorType: any = ColorType; public AssetType: any = AssetType; + public ThemeMode: any = ThemeMode; public fontName = ''; @@ -106,6 +107,32 @@ export class PrivateLabelingPolicyComponent implements OnInit, OnDestroy { private dialog: MatDialog, ) {} + public toggleThemeMode(): void { + if (this.view === View.CURRENT) { + return; + } + if (this.previewData?.themeMode === ThemeMode.THEME_MODE_LIGHT) { + this.theme = Theme.LIGHT; + } + if (this.previewData?.themeMode === ThemeMode.THEME_MODE_DARK) { + this.theme = Theme.DARK; + } + this.savePolicy(); + } + + public toggleView(view: View): void { + let themeMode = this.data?.themeMode; + if (view === View.PREVIEW) { + themeMode = this.previewData?.themeMode; + } + if (themeMode === ThemeMode.THEME_MODE_LIGHT) { + this.theme = Theme.LIGHT; + } + if (themeMode === ThemeMode.THEME_MODE_DARK) { + this.theme = Theme.DARK; + } + } + public toggleHoverLogo(theme: Theme, isHovering: boolean): void { if (theme === Theme.DARK) { this.isHoveringOverDarkLogo = isHovering; @@ -614,6 +641,8 @@ export class PrivateLabelingPolicyComponent implements OnInit, OnDestroy { req.setDisableWatermark(this.previewData.disableWatermark); req.setHideLoginNameSuffix(this.previewData.hideLoginNameSuffix); + + req.setThemeMode(this.previewData.themeMode); } } diff --git a/console/src/app/modules/policies/private-labeling-policy/private-labeling-policy.module.ts b/console/src/app/modules/policies/private-labeling-policy/private-labeling-policy.module.ts index efbd49ebde..d0f0a30bc9 100644 --- a/console/src/app/modules/policies/private-labeling-policy/private-labeling-policy.module.ts +++ b/console/src/app/modules/policies/private-labeling-policy/private-labeling-policy.module.ts @@ -9,6 +9,7 @@ import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/lega import { MatLegacyCheckboxModule as MatCheckboxModule } from '@angular/material/legacy-checkbox'; import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog'; import { MatLegacyProgressSpinnerModule as MatProgressSpinnerModule } from '@angular/material/legacy-progress-spinner'; +import { MatLegacySelectModule as MatSelectModule } from '@angular/material/legacy-select'; import { MatLegacyTooltipModule as MatTooltipModule } from '@angular/material/legacy-tooltip'; import { TranslateModule } from '@ngx-translate/core'; import { ColorChromeModule } from 'ngx-color/chrome'; @@ -49,6 +50,7 @@ import { PrivateLabelingPolicyComponent } from './private-labeling-policy.compon WarnDialogModule, HasRolePipeModule, MatProgressSpinnerModule, + MatSelectModule, MatExpansionModule, InfoSectionModule, ], diff --git a/console/src/assets/i18n/bg.json b/console/src/assets/i18n/bg.json index 87141b3896..5baa2c5687 100644 --- a/console/src/assets/i18n/bg.json +++ b/console/src/assets/i18n/bg.json @@ -1197,6 +1197,11 @@ "ERROR": "Потребителят не може да бъде намерен!", "PRIMARYBUTTON": "следващия", "SECONDARYBUTTON": "регистрирам" + }, + "THEMEMODE": { + "THEME_MODE_AUTO": "Автоматичен режим", + "THEME_MODE_LIGHT": "Само светъл режим", + "THEME_MODE_DARK": "Само тъмен режим" } }, "PWD_AGE": { diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json index 0c67a27ba3..7a2e9a72a5 100644 --- a/console/src/assets/i18n/de.json +++ b/console/src/assets/i18n/de.json @@ -1203,6 +1203,11 @@ "ERROR": "Benutzer konnte nicht gefunden werden!", "PRIMARYBUTTON": "weiter", "SECONDARYBUTTON": "registrieren" + }, + "THEMEMODE": { + "THEME_MODE_AUTO": "Automatischer Modus", + "THEME_MODE_LIGHT": "Nur heller Modus", + "THEME_MODE_DARK": "Nur dunkler Modus" } }, "PWD_AGE": { diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json index 354b272115..f0f4537c10 100644 --- a/console/src/assets/i18n/en.json +++ b/console/src/assets/i18n/en.json @@ -1204,6 +1204,11 @@ "ERROR": "User could not be found!", "PRIMARYBUTTON": "next", "SECONDARYBUTTON": "register" + }, + "THEMEMODE": { + "THEME_MODE_AUTO": "Auto Mode", + "THEME_MODE_LIGHT": "Light Mode only", + "THEME_MODE_DARK": "Dark Mode only" } }, "PWD_AGE": { diff --git a/console/src/assets/i18n/es.json b/console/src/assets/i18n/es.json index 8475622457..acbeffa903 100644 --- a/console/src/assets/i18n/es.json +++ b/console/src/assets/i18n/es.json @@ -1204,6 +1204,11 @@ "ERROR": "¡No se encontró el usuario!", "PRIMARYBUTTON": "siguiente", "SECONDARYBUTTON": "registrar" + }, + "THEMEMODE": { + "THEME_MODE_AUTO": "Modo automático", + "THEME_MODE_LIGHT": "Sólo modo claro", + "THEME_MODE_DARK": "Sólo modo oscuro" } }, "PWD_AGE": { diff --git a/console/src/assets/i18n/fr.json b/console/src/assets/i18n/fr.json index 0ec28c504d..7821e4b51a 100644 --- a/console/src/assets/i18n/fr.json +++ b/console/src/assets/i18n/fr.json @@ -1203,6 +1203,11 @@ "ERROR": "L'utilisateur n'a pas pu être trouvé !", "PRIMARYBUTTON": "suivant", "SECONDARYBUTTON": "enregistrez-vous" + }, + "THEMEMODE": { + "THEME_MODE_AUTO": "Mode automatique", + "THEME_MODE_LIGHT": "Mode clair uniquement", + "THEME_MODE_DARK": "Mode sombre uniquement" } }, "PWD_AGE": { diff --git a/console/src/assets/i18n/it.json b/console/src/assets/i18n/it.json index 11150dc8dd..6c04858ac0 100644 --- a/console/src/assets/i18n/it.json +++ b/console/src/assets/i18n/it.json @@ -1203,6 +1203,11 @@ "ERROR": "L'utente non \u00e8 stato trovato!", "PRIMARYBUTTON": "Avanti", "SECONDARYBUTTON": "Registra" + }, + "THEMEMODE": { + "THEME_MODE_AUTO": "Modalità automatica", + "THEME_MODE_LIGHT": "Solo modalità luminosa", + "THEME_MODE_DARK": "Solo modalità oscura" } }, "PWD_AGE": { diff --git a/console/src/assets/i18n/ja.json b/console/src/assets/i18n/ja.json index 6ede49ed44..52dbfb8163 100644 --- a/console/src/assets/i18n/ja.json +++ b/console/src/assets/i18n/ja.json @@ -1200,6 +1200,11 @@ "ERROR": "ユーザーは見つかりません!", "PRIMARYBUTTON": "次へ", "SECONDARYBUTTON": "登録" + }, + "THEMEMODE": { + "THEME_MODE_AUTO": "自動モード", + "THEME_MODE_LIGHT": "ライトモードのみ", + "THEME_MODE_DARK": "ダークモードのみ" } }, "PWD_AGE": { diff --git a/console/src/assets/i18n/mk.json b/console/src/assets/i18n/mk.json index 8ce464ddf3..fe7be90bae 100644 --- a/console/src/assets/i18n/mk.json +++ b/console/src/assets/i18n/mk.json @@ -1205,6 +1205,11 @@ "ERROR": "Корисникот не е пронајден!", "PRIMARYBUTTON": "следно", "SECONDARYBUTTON": "регистрирај се" + }, + "THEMEMODE": { + "THEME_MODE_AUTO": "Автоматски режим", + "THEME_MODE_LIGHT": "Само светлосен режим", + "THEME_MODE_DARK": "Само темен режим" } }, "PWD_AGE": { diff --git a/console/src/assets/i18n/pl.json b/console/src/assets/i18n/pl.json index c622e94bd2..03e701e20a 100644 --- a/console/src/assets/i18n/pl.json +++ b/console/src/assets/i18n/pl.json @@ -1203,6 +1203,11 @@ "ERROR": "Nie znaleziono użytkownika!", "PRIMARYBUTTON": "Dalej", "SECONDARYBUTTON": "Zarejestruj" + }, + "THEMEMODE": { + "THEME_MODE_AUTO": "Tryb automatyczny", + "THEME_MODE_LIGHT": "Tylko tryb jasny", + "THEME_MODE_DARK": "Tylko tryb ciemny" } }, "PWD_AGE": { diff --git a/console/src/assets/i18n/pt.json b/console/src/assets/i18n/pt.json index 47f98ea910..add9cb30fa 100644 --- a/console/src/assets/i18n/pt.json +++ b/console/src/assets/i18n/pt.json @@ -1205,6 +1205,11 @@ "ERROR": "Usuário não encontrado!", "PRIMARYBUTTON": "próximo", "SECONDARYBUTTON": "registrar" + }, + "THEMEMODE": { + "THEME_MODE_AUTO": "Modo Automático", + "THEME_MODE_LIGHT": "Somente modo claro", + "THEME_MODE_DARK": "Somente modo escuro" } }, "PWD_AGE": { diff --git a/console/src/assets/i18n/zh.json b/console/src/assets/i18n/zh.json index 9fac71e227..5d1bd590c0 100644 --- a/console/src/assets/i18n/zh.json +++ b/console/src/assets/i18n/zh.json @@ -1202,6 +1202,11 @@ "ERROR": "找不到用户!", "PRIMARYBUTTON": "下一步", "SECONDARYBUTTON": "注册" + }, + "THEMEMODE": { + "THEME_MODE_AUTO": "自动模式", + "THEME_MODE_LIGHT": "仅限灯光模式", + "THEME_MODE_DARK": "仅限深色模式" } }, "PWD_AGE": { diff --git a/internal/api/grpc/admin/label_policy_converter.go b/internal/api/grpc/admin/label_policy_converter.go index e99ad7c0e3..e6f55124ed 100644 --- a/internal/api/grpc/admin/label_policy_converter.go +++ b/internal/api/grpc/admin/label_policy_converter.go @@ -3,6 +3,7 @@ package admin import ( "github.com/zitadel/zitadel/internal/domain" admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin" + policy_pb "github.com/zitadel/zitadel/pkg/grpc/policy" ) func updateLabelPolicyToDomain(policy *admin_pb.UpdateLabelPolicyRequest) *domain.LabelPolicy { @@ -17,5 +18,21 @@ func updateLabelPolicyToDomain(policy *admin_pb.UpdateLabelPolicyRequest) *domai FontColorDark: policy.FontColorDark, HideLoginNameSuffix: policy.HideLoginNameSuffix, DisableWatermark: policy.DisableWatermark, + ThemeMode: themeModeToDomain(policy.ThemeMode), + } +} + +func themeModeToDomain(theme policy_pb.ThemeMode) domain.LabelPolicyThemeMode { + switch theme { + case policy_pb.ThemeMode_THEME_MODE_AUTO: + return domain.LabelPolicyThemeAuto + case policy_pb.ThemeMode_THEME_MODE_DARK: + return domain.LabelPolicyThemeDark + case policy_pb.ThemeMode_THEME_MODE_LIGHT: + return domain.LabelPolicyThemeLight + case policy_pb.ThemeMode_THEME_MODE_UNSPECIFIED: + return domain.LabelPolicyThemeAuto + default: + return domain.LabelPolicyThemeAuto } } diff --git a/internal/api/grpc/management/policy_label_converter.go b/internal/api/grpc/management/policy_label_converter.go index 9c399cfe33..6d79ca652a 100644 --- a/internal/api/grpc/management/policy_label_converter.go +++ b/internal/api/grpc/management/policy_label_converter.go @@ -3,6 +3,7 @@ package management import ( "github.com/zitadel/zitadel/internal/domain" mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management" + policy_pb "github.com/zitadel/zitadel/pkg/grpc/policy" ) func AddLabelPolicyToDomain(p *mgmt_pb.AddCustomLabelPolicyRequest) *domain.LabelPolicy { @@ -17,6 +18,22 @@ func AddLabelPolicyToDomain(p *mgmt_pb.AddCustomLabelPolicyRequest) *domain.Labe FontColorDark: p.FontColorDark, HideLoginNameSuffix: p.HideLoginNameSuffix, DisableWatermark: p.DisableWatermark, + ThemeMode: themeModeToDomain(p.ThemeMode), + } +} + +func themeModeToDomain(theme policy_pb.ThemeMode) domain.LabelPolicyThemeMode { + switch theme { + case policy_pb.ThemeMode_THEME_MODE_AUTO: + return domain.LabelPolicyThemeAuto + case policy_pb.ThemeMode_THEME_MODE_DARK: + return domain.LabelPolicyThemeDark + case policy_pb.ThemeMode_THEME_MODE_LIGHT: + return domain.LabelPolicyThemeLight + case policy_pb.ThemeMode_THEME_MODE_UNSPECIFIED: + return domain.LabelPolicyThemeAuto + default: + return domain.LabelPolicyThemeAuto } } @@ -32,5 +49,6 @@ func updateLabelPolicyToDomain(p *mgmt_pb.UpdateCustomLabelPolicyRequest) *domai FontColorDark: p.FontColorDark, HideLoginNameSuffix: p.HideLoginNameSuffix, DisableWatermark: p.DisableWatermark, + ThemeMode: themeModeToDomain(p.ThemeMode), } } diff --git a/internal/api/grpc/policy/label_policy.go b/internal/api/grpc/policy/label_policy.go index 0b925c3720..1fade4c914 100644 --- a/internal/api/grpc/policy/label_policy.go +++ b/internal/api/grpc/policy/label_policy.go @@ -26,6 +26,7 @@ func ModelLabelPolicyToPb(policy *query.LabelPolicy, assetPrefix string) *policy DisableWatermark: policy.WatermarkDisabled, HideLoginNameSuffix: policy.HideLoginNameSuffix, + ThemeMode: themeModeToPb(policy.ThemeMode), Details: object.ToViewDetailsPb( policy.Sequence, policy.CreationDate, @@ -34,3 +35,16 @@ func ModelLabelPolicyToPb(policy *query.LabelPolicy, assetPrefix string) *policy ), } } + +func themeModeToPb(theme domain.LabelPolicyThemeMode) policy_pb.ThemeMode { + switch theme { + case domain.LabelPolicyThemeAuto: + return policy_pb.ThemeMode_THEME_MODE_AUTO + case domain.LabelPolicyThemeDark: + return policy_pb.ThemeMode_THEME_MODE_DARK + case domain.LabelPolicyThemeLight: + return policy_pb.ThemeMode_THEME_MODE_LIGHT + default: + return policy_pb.ThemeMode_THEME_MODE_AUTO + } +} diff --git a/internal/api/grpc/settings/v2/settings_converter.go b/internal/api/grpc/settings/v2/settings_converter.go index 7c28776299..69a494d027 100644 --- a/internal/api/grpc/settings/v2/settings_converter.go +++ b/internal/api/grpc/settings/v2/settings_converter.go @@ -107,6 +107,20 @@ func brandingSettingsToPb(current *query.LabelPolicy, assetPrefix string) *setti DisableWatermark: current.WatermarkDisabled, HideLoginNameSuffix: current.HideLoginNameSuffix, ResourceOwnerType: isDefaultToResourceOwnerTypePb(current.IsDefault), + ThemeMode: themeModeToPb(current.ThemeMode), + } +} + +func themeModeToPb(themeMode domain.LabelPolicyThemeMode) settings.ThemeMode { + switch themeMode { + case domain.LabelPolicyThemeAuto: + return settings.ThemeMode_THEME_MODE_AUTO + case domain.LabelPolicyThemeLight: + return settings.ThemeMode_THEME_MODE_LIGHT + case domain.LabelPolicyThemeDark: + return settings.ThemeMode_THEME_MODE_DARK + default: + return settings.ThemeMode_THEME_MODE_AUTO } } diff --git a/internal/api/grpc/settings/v2/settings_converter_test.go b/internal/api/grpc/settings/v2/settings_converter_test.go index 133715d8b1..6e441a33ba 100644 --- a/internal/api/grpc/settings/v2/settings_converter_test.go +++ b/internal/api/grpc/settings/v2/settings_converter_test.go @@ -258,6 +258,7 @@ func Test_brandingSettingsToPb(t *testing.T) { FontURL: "fonts", WatermarkDisabled: true, HideLoginNameSuffix: true, + ThemeMode: domain.LabelPolicyThemeDark, IsDefault: true, } want := &settings.BrandingSettings{ @@ -281,6 +282,7 @@ func Test_brandingSettingsToPb(t *testing.T) { DisableWatermark: true, HideLoginNameSuffix: true, ResourceOwnerType: settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_INSTANCE, + ThemeMode: settings.ThemeMode_THEME_MODE_DARK, } got := brandingSettingsToPb(arg, "http://example.com") diff --git a/internal/api/ui/login/renderer.go b/internal/api/ui/login/renderer.go index 85d54cd015..d81ab4567e 100644 --- a/internal/api/ui/login/renderer.go +++ b/internal/api/ui/login/renderer.go @@ -381,8 +381,6 @@ func (l *Login) getBaseData(r *http.Request, authReq *domain.AuthRequest, titleI Title: title, Description: description, Theme: l.getTheme(r), - ThemeMode: l.getThemeMode(r), - DarkMode: l.isDarkMode(r), PrivateLabelingOrgID: l.getPrivateLabelingID(r, authReq), OrgID: l.getOrgID(r, authReq), OrgName: l.getOrgName(authReq), @@ -412,6 +410,9 @@ func (l *Login) getBaseData(r *http.Request, authReq *domain.AuthRequest, titleI } privacyPolicy = policy.ToDomain() } + baseData.ThemeMode = l.getThemeMode(baseData.LabelPolicy) + baseData.ThemeClass = l.getThemeClass(r, baseData.LabelPolicy) + baseData.DarkMode = l.isDarkMode(r, baseData.LabelPolicy) baseData = l.setLinksOnBaseData(baseData, privacyPolicy) return baseData } @@ -480,14 +481,22 @@ func (l *Login) getTheme(r *http.Request) string { return "zitadel" } -func (l *Login) getThemeMode(r *http.Request) string { - if l.isDarkMode(r) { +// getThemeClass returns the css class for the login html. +// Possible values are `lgn-light-theme` and `lgn-dark-theme` and are based on the policy first +// and if it's set to auto the cookie is checked. +func (l *Login) getThemeClass(r *http.Request, policy *domain.LabelPolicy) string { + if l.isDarkMode(r, policy) { return "lgn-dark-theme" } return "lgn-light-theme" } -func (l *Login) isDarkMode(r *http.Request) bool { +// isDarkMode checks policy first and if not set to specifically use dark or light only, +// it will also check the cookie. +func (l *Login) isDarkMode(r *http.Request, policy *domain.LabelPolicy) bool { + if mode := l.getThemeMode(policy); mode != domain.LabelPolicyThemeAuto { + return mode == domain.LabelPolicyThemeDark + } cookie, err := r.Cookie("mode") if err != nil { return false @@ -495,6 +504,13 @@ func (l *Login) isDarkMode(r *http.Request) bool { return strings.HasSuffix(cookie.Value, "dark") } +func (l *Login) getThemeMode(policy *domain.LabelPolicy) domain.LabelPolicyThemeMode { + if policy != nil { + return policy.ThemeMode + } + return domain.LabelPolicyThemeAuto +} + func (l *Login) getOrgID(r *http.Request, authReq *domain.AuthRequest) string { if authReq == nil { return r.FormValue(queryOrgID) @@ -607,7 +623,8 @@ type baseData struct { Title string Description string Theme string - ThemeMode string + ThemeMode domain.LabelPolicyThemeMode + ThemeClass string DarkMode bool PrivateLabelingOrgID string OrgID string diff --git a/internal/api/ui/login/static/resources/scripts/theme.js b/internal/api/ui/login/static/resources/scripts/theme.js index c5b088f6ba..dce18a72b6 100644 --- a/internal/api/ui/login/static/resources/scripts/theme.js +++ b/internal/api/ui/login/static/resources/scripts/theme.js @@ -1,10 +1,16 @@ -const usesDarkTheme = hasDarkModeOverwriteCookie() || (!hasLightModeOverwriteCookie() && window.matchMedia('(prefers-color-scheme: dark)').matches); -if (usesDarkTheme) { - document.documentElement.classList.replace('lgn-light-theme', 'lgn-dark-theme'); - writeModeCookie('dark'); -} else { - document.documentElement.classList.replace('lgn-dark-theme', 'lgn-light-theme'); - writeModeCookie('light'); +if (isAutoMode()) { + const usesDarkTheme = hasDarkModeOverwriteCookie() || (!hasLightModeOverwriteCookie() && window.matchMedia('(prefers-color-scheme: dark)').matches); + if (usesDarkTheme) { + document.documentElement.classList.replace('lgn-light-theme', 'lgn-dark-theme'); + writeModeCookie('dark'); + } else { + document.documentElement.classList.replace('lgn-dark-theme', 'lgn-light-theme'); + writeModeCookie('light'); + } +} + +function isAutoMode() { + return document.documentElement.dataset["themeMode"] === "0" } function hasDarkModeOverwriteCookie() { diff --git a/internal/api/ui/login/static/templates/main.html b/internal/api/ui/login/static/templates/main.html index 04ec281ebe..f4ef0943fe 100644 --- a/internal/api/ui/login/static/templates/main.html +++ b/internal/api/ui/login/static/templates/main.html @@ -1,6 +1,6 @@ {{define "main-top"}} - + diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index 24ef2f15f9..6e40aff730 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -1335,6 +1335,7 @@ func labelPolicyToDomain(p *query.LabelPolicy) *domain.LabelPolicy { HideLoginNameSuffix: p.HideLoginNameSuffix, ErrorMsgPopup: p.ShouldErrorPopup, DisableWatermark: p.WatermarkDisabled, + ThemeMode: p.ThemeMode, } } diff --git a/internal/command/instance.go b/internal/command/instance.go index 4c5bb2faa5..df3420198c 100644 --- a/internal/command/instance.go +++ b/internal/command/instance.go @@ -97,6 +97,7 @@ type InstanceSetup struct { HideLoginNameSuffix bool ErrorMsgPopup bool DisableWatermark bool + ThemeMode domain.LabelPolicyThemeMode } LockoutPolicy struct { MaxAttempts uint64 @@ -276,6 +277,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str setup.LabelPolicy.HideLoginNameSuffix, setup.LabelPolicy.ErrorMsgPopup, setup.LabelPolicy.DisableWatermark, + setup.LabelPolicy.ThemeMode, ), prepareActivateDefaultLabelPolicy(instanceAgg), diff --git a/internal/command/instance_converter.go b/internal/command/instance_converter.go index 6d7f605751..2e44fc7a7f 100644 --- a/internal/command/instance_converter.go +++ b/internal/command/instance_converter.go @@ -59,6 +59,7 @@ func writeModelToLabelPolicy(wm *LabelPolicyWriteModel) *domain.LabelPolicy { HideLoginNameSuffix: wm.HideLoginNameSuffix, ErrorMsgPopup: wm.ErrorMsgPopup, DisableWatermark: wm.DisableWatermark, + ThemeMode: wm.ThemeMode, } } diff --git a/internal/command/instance_policy_label.go b/internal/command/instance_policy_label.go index 19bd63004d..4a083fce27 100644 --- a/internal/command/instance_policy_label.go +++ b/internal/command/instance_policy_label.go @@ -15,7 +15,7 @@ import ( func (c *Commands) AddDefaultLabelPolicy( ctx context.Context, primaryColor, backgroundColor, warnColor, fontColor, primaryColorDark, backgroundColorDark, warnColorDark, fontColorDark string, - hideLoginNameSuffix, errorMsgPopup, disableWatermark bool, + hideLoginNameSuffix, errorMsgPopup, disableWatermark bool, themeMode domain.LabelPolicyThemeMode, ) (*domain.ObjectDetails, error) { instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID()) cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, @@ -32,6 +32,7 @@ func (c *Commands) AddDefaultLabelPolicy( hideLoginNameSuffix, errorMsgPopup, disableWatermark, + themeMode, )) if err != nil { return nil, err @@ -69,7 +70,8 @@ func (c *Commands) ChangeDefaultLabelPolicy(ctx context.Context, policy *domain. policy.FontColorDark, policy.HideLoginNameSuffix, policy.ErrorMsgPopup, - policy.DisableWatermark) + policy.DisableWatermark, + policy.ThemeMode) if !hasChanged { return nil, caos_errs.ThrowPreconditionFailed(nil, "INSTANCE-28fHe", "Errors.IAM.LabelPolicy.NotChanged") } @@ -384,6 +386,7 @@ func prepareAddDefaultLabelPolicy( hideLoginNameSuffix, errorMsgPopup, disableWatermark bool, + themeMode domain.LabelPolicyThemeMode, ) preparation.Validation { return func() (preparation.CreateCommands, error) { return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { @@ -412,6 +415,7 @@ func prepareAddDefaultLabelPolicy( hideLoginNameSuffix, errorMsgPopup, disableWatermark, + themeMode, ), }, nil }, nil diff --git a/internal/command/instance_policy_label_model.go b/internal/command/instance_policy_label_model.go index 9dad068026..2b32ebe7cb 100644 --- a/internal/command/instance_policy_label_model.go +++ b/internal/command/instance_policy_label_model.go @@ -4,6 +4,7 @@ import ( "context" "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/instance" "github.com/zitadel/zitadel/internal/repository/policy" @@ -97,6 +98,7 @@ func (wm *InstanceLabelPolicyWriteModel) NewChangedEvent( hideLoginNameSuffix, errorMsgPopup, disableWatermark bool, + themeMode domain.LabelPolicyThemeMode, ) (*instance.LabelPolicyChangedEvent, bool) { changes := make([]policy.LabelPolicyChanges, 0) if wm.PrimaryColor != primaryColor { @@ -132,6 +134,9 @@ func (wm *InstanceLabelPolicyWriteModel) NewChangedEvent( if wm.DisableWatermark != disableWatermark { changes = append(changes, policy.ChangeDisableWatermark(disableWatermark)) } + if wm.ThemeMode != themeMode { + changes = append(changes, policy.ChangeThemeMode(themeMode)) + } if len(changes) == 0 { return nil, false } diff --git a/internal/command/instance_policy_label_test.go b/internal/command/instance_policy_label_test.go index 5b7b9dcbc5..908186a367 100644 --- a/internal/command/instance_policy_label_test.go +++ b/internal/command/instance_policy_label_test.go @@ -36,6 +36,7 @@ func TestCommandSide_AddDefaultLabelPolicy(t *testing.T) { hideLoginNameSuffix bool errorMsgPopup bool disableWatermark bool + themeMode domain.LabelPolicyThemeMode } type res struct { want *domain.ObjectDetails @@ -67,6 +68,7 @@ func TestCommandSide_AddDefaultLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -85,6 +87,7 @@ func TestCommandSide_AddDefaultLabelPolicy(t *testing.T) { hideLoginNameSuffix: true, errorMsgPopup: true, disableWatermark: true, + themeMode: domain.LabelPolicyThemeAuto, }, res: res{ err: caos_errs.IsErrorAlreadyExists, @@ -110,6 +113,7 @@ func TestCommandSide_AddDefaultLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeDark, ), ), ), @@ -127,6 +131,7 @@ func TestCommandSide_AddDefaultLabelPolicy(t *testing.T) { hideLoginNameSuffix: true, errorMsgPopup: true, disableWatermark: true, + themeMode: domain.LabelPolicyThemeDark, }, res: res{ want: &domain.ObjectDetails{ @@ -153,6 +158,7 @@ func TestCommandSide_AddDefaultLabelPolicy(t *testing.T) { tt.args.hideLoginNameSuffix, tt.args.errorMsgPopup, tt.args.disableWatermark, + tt.args.themeMode, ) if tt.res.err == nil { assert.NoError(t, err) @@ -225,6 +231,7 @@ func TestCommandSide_ChangeDefaultLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -244,6 +251,7 @@ func TestCommandSide_ChangeDefaultLabelPolicy(t *testing.T) { HideLoginNameSuffix: true, ErrorMsgPopup: true, DisableWatermark: true, + ThemeMode: domain.LabelPolicyThemeAuto, }, }, res: res{ @@ -270,6 +278,7 @@ func TestCommandSide_ChangeDefaultLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -286,7 +295,8 @@ func TestCommandSide_ChangeDefaultLabelPolicy(t *testing.T) { "#000000", false, false, - false), + false, + domain.LabelPolicyThemeDark), ), ), }, @@ -304,6 +314,7 @@ func TestCommandSide_ChangeDefaultLabelPolicy(t *testing.T) { HideLoginNameSuffix: false, ErrorMsgPopup: false, DisableWatermark: false, + ThemeMode: domain.LabelPolicyThemeDark, }, }, res: res{ @@ -324,6 +335,7 @@ func TestCommandSide_ChangeDefaultLabelPolicy(t *testing.T) { HideLoginNameSuffix: false, ErrorMsgPopup: false, DisableWatermark: false, + ThemeMode: domain.LabelPolicyThemeDark, }, }, }, @@ -399,6 +411,7 @@ func TestCommandSide_ActivateDefaultLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -500,6 +513,7 @@ func TestCommandSide_AddLogoDefaultLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -541,6 +555,7 @@ func TestCommandSide_AddLogoDefaultLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -645,6 +660,7 @@ func TestCommandSide_RemoveLogoDefaultLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), eventFromEventPusher( @@ -684,6 +700,7 @@ func TestCommandSide_RemoveLogoDefaultLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), eventFromEventPusher( @@ -793,6 +810,7 @@ func TestCommandSide_AddIconDefaultLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -834,6 +852,7 @@ func TestCommandSide_AddIconDefaultLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -938,6 +957,7 @@ func TestCommandSide_RemoveIconDefaultLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), eventFromEventPusher( @@ -1049,6 +1069,7 @@ func TestCommandSide_AddLogoDarkDefaultLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -1091,6 +1112,7 @@ func TestCommandSide_AddLogoDarkDefaultLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -1195,6 +1217,7 @@ func TestCommandSide_RemoveLogoDarkDefaultLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), eventFromEventPusher( @@ -1234,6 +1257,7 @@ func TestCommandSide_RemoveLogoDarkDefaultLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), eventFromEventPusher( @@ -1343,6 +1367,7 @@ func TestCommandSide_AddIconDarkDefaultLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -1384,6 +1409,7 @@ func TestCommandSide_AddIconDarkDefaultLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -1488,6 +1514,7 @@ func TestCommandSide_RemoveIconDarkDefaultLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), eventFromEventPusher( @@ -1527,6 +1554,7 @@ func TestCommandSide_RemoveIconDarkDefaultLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), eventFromEventPusher( @@ -1636,6 +1664,7 @@ func TestCommandSide_AddFontDefaultLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -1677,6 +1706,7 @@ func TestCommandSide_AddFontDefaultLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -1781,6 +1811,7 @@ func TestCommandSide_RemoveFontDefaultLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), eventFromEventPusher( @@ -1820,6 +1851,7 @@ func TestCommandSide_RemoveFontDefaultLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), eventFromEventPusher( @@ -1867,7 +1899,7 @@ func TestCommandSide_RemoveFontDefaultLabelPolicy(t *testing.T) { } } -func newDefaultLabelPolicyChangedEvent(ctx context.Context, primaryColor, backgroundColor, warnColor, fontColor, primaryColorDark, backgroundColorDark, warnColorDark, fontColorDark string, hideLoginNameSuffix, errMsgPopup, disableWatermark bool) *instance.LabelPolicyChangedEvent { +func newDefaultLabelPolicyChangedEvent(ctx context.Context, primaryColor, backgroundColor, warnColor, fontColor, primaryColorDark, backgroundColorDark, warnColorDark, fontColorDark string, hideLoginNameSuffix, errMsgPopup, disableWatermark bool, theme domain.LabelPolicyThemeMode) *instance.LabelPolicyChangedEvent { event, _ := instance.NewLabelPolicyChangedEvent(ctx, &instance.NewAggregate("INSTANCE").Aggregate, []policy.LabelPolicyChanges{ @@ -1882,6 +1914,7 @@ func newDefaultLabelPolicyChangedEvent(ctx context.Context, primaryColor, backgr policy.ChangeHideLoginNameSuffix(hideLoginNameSuffix), policy.ChangeErrorMsgPopup(errMsgPopup), policy.ChangeDisableWatermark(disableWatermark), + policy.ChangeThemeMode(theme), }, ) return event diff --git a/internal/command/org_policy_label.go b/internal/command/org_policy_label.go index 8d4535c528..6698baff61 100644 --- a/internal/command/org_policy_label.go +++ b/internal/command/org_policy_label.go @@ -39,7 +39,8 @@ func (c *Commands) AddLabelPolicy(ctx context.Context, resourceOwner string, pol policy.FontColorDark, policy.HideLoginNameSuffix, policy.ErrorMsgPopup, - policy.DisableWatermark)) + policy.DisableWatermark, + policy.ThemeMode)) if err != nil { return nil, err } @@ -80,7 +81,8 @@ func (c *Commands) ChangeLabelPolicy(ctx context.Context, resourceOwner string, policy.FontColorDark, policy.HideLoginNameSuffix, policy.ErrorMsgPopup, - policy.DisableWatermark) + policy.DisableWatermark, + policy.ThemeMode) if !hasChanged { return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-8nfSr", "Errors.Org.LabelPolicy.NotChanged") } diff --git a/internal/command/org_policy_label_model.go b/internal/command/org_policy_label_model.go index 83cd39d25a..d8e510f425 100644 --- a/internal/command/org_policy_label_model.go +++ b/internal/command/org_policy_label_model.go @@ -3,6 +3,7 @@ package command import ( "context" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/org" @@ -98,6 +99,7 @@ func (wm *OrgLabelPolicyWriteModel) NewChangedEvent( hideLoginNameSuffix, errorMsgPopup, disableWatermark bool, + themeMode domain.LabelPolicyThemeMode, ) (*org.LabelPolicyChangedEvent, bool) { changes := make([]policy.LabelPolicyChanges, 0) if wm.PrimaryColor != primaryColor { @@ -133,6 +135,9 @@ func (wm *OrgLabelPolicyWriteModel) NewChangedEvent( if wm.DisableWatermark != disableWatermark { changes = append(changes, policy.ChangeDisableWatermark(disableWatermark)) } + if wm.ThemeMode != themeMode { + changes = append(changes, policy.ChangeThemeMode(themeMode)) + } if len(changes) == 0 { return nil, false } diff --git a/internal/command/org_policy_label_test.go b/internal/command/org_policy_label_test.go index d317b8cd81..3cd3ea81d5 100644 --- a/internal/command/org_policy_label_test.go +++ b/internal/command/org_policy_label_test.go @@ -75,6 +75,7 @@ func TestCommandSide_AddLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -121,6 +122,7 @@ func TestCommandSide_AddLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeDark, ), ), ), @@ -140,6 +142,7 @@ func TestCommandSide_AddLabelPolicy(t *testing.T) { HideLoginNameSuffix: true, ErrorMsgPopup: true, DisableWatermark: true, + ThemeMode: domain.LabelPolicyThemeDark, }, }, res: res{ @@ -159,6 +162,7 @@ func TestCommandSide_AddLabelPolicy(t *testing.T) { HideLoginNameSuffix: true, ErrorMsgPopup: true, DisableWatermark: true, + ThemeMode: domain.LabelPolicyThemeDark, }, }, }, @@ -260,6 +264,7 @@ func TestCommandSide_ChangeLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -280,6 +285,7 @@ func TestCommandSide_ChangeLabelPolicy(t *testing.T) { HideLoginNameSuffix: true, ErrorMsgPopup: true, DisableWatermark: true, + ThemeMode: domain.LabelPolicyThemeAuto, }, }, res: res{ @@ -306,6 +312,7 @@ func TestCommandSide_ChangeLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -323,7 +330,8 @@ func TestCommandSide_ChangeLabelPolicy(t *testing.T) { "#000000", false, false, - false), + false, + domain.LabelPolicyThemeDark), ), ), }, @@ -342,6 +350,7 @@ func TestCommandSide_ChangeLabelPolicy(t *testing.T) { HideLoginNameSuffix: false, ErrorMsgPopup: false, DisableWatermark: false, + ThemeMode: domain.LabelPolicyThemeDark, }, }, res: res{ @@ -361,6 +370,7 @@ func TestCommandSide_ChangeLabelPolicy(t *testing.T) { HideLoginNameSuffix: false, ErrorMsgPopup: false, DisableWatermark: false, + ThemeMode: domain.LabelPolicyThemeDark, }, }, }, @@ -451,6 +461,7 @@ func TestCommandSide_ActivateLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -551,6 +562,7 @@ func TestCommandSide_RemoveLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -672,6 +684,7 @@ func TestCommandSide_AddLogoLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -714,6 +727,7 @@ func TestCommandSide_AddLogoLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -838,6 +852,7 @@ func TestCommandSide_RemoveLogoLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), eventFromEventPusher( @@ -974,6 +989,7 @@ func TestCommandSide_AddIconLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -1016,6 +1032,7 @@ func TestCommandSide_AddIconLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -1138,6 +1155,7 @@ func TestCommandSide_RemoveIconLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), eventFromEventPusher( @@ -1273,6 +1291,7 @@ func TestCommandSide_AddLogoDarkLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -1315,6 +1334,7 @@ func TestCommandSide_AddLogoDarkLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -1439,6 +1459,7 @@ func TestCommandSide_RemoveLogoDarkLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), eventFromEventPusher( @@ -1575,6 +1596,7 @@ func TestCommandSide_AddIconDarkLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -1617,6 +1639,7 @@ func TestCommandSide_AddIconDarkLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -1737,6 +1760,7 @@ func TestCommandSide_RemoveIconDarkLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), eventFromEventPusher( @@ -1864,6 +1888,7 @@ func TestCommandSide_AddFontLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -1906,6 +1931,7 @@ func TestCommandSide_AddFontLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), ), @@ -2026,6 +2052,7 @@ func TestCommandSide_RemoveFontLabelPolicy(t *testing.T) { true, true, true, + domain.LabelPolicyThemeAuto, ), ), eventFromEventPusher( @@ -2074,7 +2101,7 @@ func TestCommandSide_RemoveFontLabelPolicy(t *testing.T) { } } -func newLabelPolicyChangedEvent(ctx context.Context, orgID, primaryColor, backgroundColor, warnColor, fontColor, primaryColorDark, backgroundColorDark, warnColorDark, fontColorDark string, hideLoginNameSuffix, errMsgPopup, disableWatermark bool) *org.LabelPolicyChangedEvent { +func newLabelPolicyChangedEvent(ctx context.Context, orgID, primaryColor, backgroundColor, warnColor, fontColor, primaryColorDark, backgroundColorDark, warnColorDark, fontColorDark string, hideLoginNameSuffix, errMsgPopup, disableWatermark bool, theme domain.LabelPolicyThemeMode) *org.LabelPolicyChangedEvent { event, _ := org.NewLabelPolicyChangedEvent(ctx, &org.NewAggregate(orgID).Aggregate, []policy.LabelPolicyChanges{ @@ -2089,6 +2116,7 @@ func newLabelPolicyChangedEvent(ctx context.Context, orgID, primaryColor, backgr policy.ChangeHideLoginNameSuffix(hideLoginNameSuffix), policy.ChangeErrorMsgPopup(errMsgPopup), policy.ChangeDisableWatermark(disableWatermark), + policy.ChangeThemeMode(theme), }, ) return event diff --git a/internal/command/policy_label_model.go b/internal/command/policy_label_model.go index a44d0e1b56..f620b2aa61 100644 --- a/internal/command/policy_label_model.go +++ b/internal/command/policy_label_model.go @@ -28,6 +28,7 @@ type LabelPolicyWriteModel struct { HideLoginNameSuffix bool ErrorMsgPopup bool DisableWatermark bool + ThemeMode domain.LabelPolicyThemeMode State domain.PolicyState } @@ -47,6 +48,7 @@ func (wm *LabelPolicyWriteModel) Reduce() error { wm.HideLoginNameSuffix = e.HideLoginNameSuffix wm.ErrorMsgPopup = e.ErrorMsgPopup wm.DisableWatermark = e.DisableWatermark + wm.ThemeMode = e.ThemeMode wm.State = domain.PolicyStateActive case *policy.LabelPolicyChangedEvent: if e.PrimaryColor != nil { @@ -82,6 +84,9 @@ func (wm *LabelPolicyWriteModel) Reduce() error { if e.DisableWatermark != nil { wm.DisableWatermark = *e.DisableWatermark } + if e.ThemeMode != nil { + wm.ThemeMode = *e.ThemeMode + } case *policy.LabelPolicyLogoAddedEvent: wm.LogoKey = e.StoreKey case *policy.LabelPolicyLogoRemovedEvent: diff --git a/internal/domain/policy_label.go b/internal/domain/policy_label.go index f3d660a91d..b5517bc54c 100644 --- a/internal/domain/policy_label.go +++ b/internal/domain/policy_label.go @@ -34,6 +34,7 @@ type LabelPolicy struct { HideLoginNameSuffix bool ErrorMsgPopup bool DisableWatermark bool + ThemeMode LabelPolicyThemeMode } type LabelPolicyState int32 @@ -47,6 +48,14 @@ const ( labelPolicyStateCount ) +type LabelPolicyThemeMode int32 + +const ( + LabelPolicyThemeAuto LabelPolicyThemeMode = iota + LabelPolicyThemeLight + LabelPolicyThemeDark +) + func (f LabelPolicy) IsValid() error { if !colorRegex.MatchString(f.PrimaryColor) { return caos_errs.ThrowInvalidArgument(nil, "POLICY-391dG", "Errors.Policy.Label.Invalid.PrimaryColor") diff --git a/internal/query/label_policy.go b/internal/query/label_policy.go index 476c8de595..fe21987adb 100644 --- a/internal/query/label_policy.go +++ b/internal/query/label_policy.go @@ -28,6 +28,7 @@ type LabelPolicy struct { FontURL string WatermarkDisabled bool ShouldErrorPopup bool + ThemeMode domain.LabelPolicyThemeMode Dark Theme Light Theme @@ -234,6 +235,9 @@ var ( LabelPolicyOwnerRemoved = Column{ name: projection.LabelPolicyOwnerRemovedCol, } + LabelPolicyThemeMode = Column{ + name: projection.LabelPolicyThemeModeCol, + } ) func prepareLabelPolicyQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*LabelPolicy, error)) { @@ -250,6 +254,7 @@ func prepareLabelPolicyQuery(ctx context.Context, db prepareDatabase) (sq.Select LabelPolicyColFontURL.identifier(), LabelPolicyColWatermarkDisabled.identifier(), LabelPolicyColShouldErrorPopup.identifier(), + LabelPolicyThemeMode.identifier(), LabelPolicyColLightPrimaryColor.identifier(), LabelPolicyColLightWarnColor.identifier(), @@ -299,6 +304,7 @@ func prepareLabelPolicyQuery(ctx context.Context, db prepareDatabase) (sq.Select &fontURL, &policy.WatermarkDisabled, &policy.ShouldErrorPopup, + &policy.ThemeMode, &lightPrimaryColor, &lightWarnColor, @@ -358,5 +364,6 @@ func (p *LabelPolicy) ToDomain() *domain.LabelPolicy { HideLoginNameSuffix: p.HideLoginNameSuffix, ErrorMsgPopup: p.ShouldErrorPopup, DisableWatermark: p.WatermarkDisabled, + ThemeMode: p.ThemeMode, } } diff --git a/internal/query/projection/label_policy.go b/internal/query/projection/label_policy.go index f8f6b03d5d..49f69738e4 100644 --- a/internal/query/projection/label_policy.go +++ b/internal/query/projection/label_policy.go @@ -14,7 +14,7 @@ import ( ) const ( - LabelPolicyTable = "projections.label_policies2" + LabelPolicyTable = "projections.label_policies3" LabelPolicyIDCol = "id" LabelPolicyCreationDateCol = "creation_date" @@ -29,6 +29,7 @@ const ( LabelPolicyShouldErrorPopupCol = "should_error_popup" LabelPolicyFontURLCol = "font_url" LabelPolicyOwnerRemovedCol = "owner_removed" + LabelPolicyThemeModeCol = "theme_mode" LabelPolicyLightPrimaryColorCol = "light_primary_color" LabelPolicyLightWarnColorCol = "light_warn_color" @@ -83,6 +84,7 @@ func (*labelPolicyProjection) Init() *old_handler.Check { handler.NewColumn(LabelPolicyDarkLogoURLCol, handler.ColumnTypeText, handler.Nullable()), handler.NewColumn(LabelPolicyDarkIconURLCol, handler.ColumnTypeText, handler.Nullable()), handler.NewColumn(LabelPolicyOwnerRemovedCol, handler.ColumnTypeBool, handler.Default(false)), + handler.NewColumn(LabelPolicyThemeModeCol, handler.ColumnTypeEnum, handler.Default(0)), }, handler.NewPrimaryKey(LabelPolicyInstanceIDCol, LabelPolicyIDCol, LabelPolicyStateCol), handler.WithIndex(handler.NewIndex("owner_removed", []string{LabelPolicyOwnerRemovedCol})), @@ -264,6 +266,7 @@ func (p *labelPolicyProjection) reduceAdded(event eventstore.Event) (*handler.St handler.NewCol(LabelPolicyHideLoginNameSuffixCol, policyEvent.HideLoginNameSuffix), handler.NewCol(LabelPolicyShouldErrorPopupCol, policyEvent.ErrorMsgPopup), handler.NewCol(LabelPolicyWatermarkDisabledCol, policyEvent.DisableWatermark), + handler.NewCol(LabelPolicyThemeModeCol, policyEvent.ThemeMode), }), nil } @@ -314,6 +317,9 @@ func (p *labelPolicyProjection) reduceChanged(event eventstore.Event) (*handler. if policyEvent.DisableWatermark != nil { cols = append(cols, handler.NewCol(LabelPolicyWatermarkDisabledCol, *policyEvent.DisableWatermark)) } + if policyEvent.ThemeMode != nil { + cols = append(cols, handler.NewCol(LabelPolicyThemeModeCol, *policyEvent.ThemeMode)) + } return handler.NewUpdateStatement( &policyEvent, cols, @@ -376,6 +382,7 @@ func (p *labelPolicyProjection) reduceActivated(event eventstore.Event) (*handle handler.NewCol(LabelPolicyDarkFontColorCol, nil), handler.NewCol(LabelPolicyDarkLogoURLCol, nil), handler.NewCol(LabelPolicyDarkIconURLCol, nil), + handler.NewCol(LabelPolicyThemeModeCol, nil), }, []handler.Column{ handler.NewCol(LabelPolicyChangeDateCol, nil), @@ -402,6 +409,7 @@ func (p *labelPolicyProjection) reduceActivated(event eventstore.Event) (*handle handler.NewCol(LabelPolicyDarkFontColorCol, nil), handler.NewCol(LabelPolicyDarkLogoURLCol, nil), handler.NewCol(LabelPolicyDarkIconURLCol, nil), + handler.NewCol(LabelPolicyThemeModeCol, nil), }, []handler.NamespacedCondition{ handler.NewNamespacedCondition(LabelPolicyIDCol, event.Aggregate().ID), diff --git a/internal/query/projection/label_policy_test.go b/internal/query/projection/label_policy_test.go index 553b67fc38..202e8cd32c 100644 --- a/internal/query/projection/label_policy_test.go +++ b/internal/query/projection/label_policy_test.go @@ -28,7 +28,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { testEvent( org.LabelPolicyAddedEventType, org.AggregateType, - []byte(`{"backgroundColor": "#141735", "fontColor": "#ffffff", "primaryColor": "#5282c1", "warnColor": "#ff3b5b"}`), + []byte(`{"backgroundColor": "#141735", "fontColor": "#ffffff", "primaryColor": "#5282c1", "warnColor": "#ff3b5b", "themeMode": 1}`), ), org.LabelPolicyAddedEventMapper), }, reduce: (&labelPolicyProjection{}).reduceAdded, @@ -38,7 +38,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.label_policies2 (creation_date, change_date, sequence, id, state, is_default, resource_owner, instance_id, light_primary_color, light_background_color, light_warn_color, light_font_color, dark_primary_color, dark_background_color, dark_warn_color, dark_font_color, hide_login_name_suffix, should_error_popup, watermark_disabled) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)", + expectedStmt: "INSERT INTO projections.label_policies3 (creation_date, change_date, sequence, id, state, is_default, resource_owner, instance_id, light_primary_color, light_background_color, light_warn_color, light_font_color, dark_primary_color, dark_background_color, dark_warn_color, dark_font_color, hide_login_name_suffix, should_error_popup, watermark_disabled, theme_mode) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)", expectedArgs: []interface{}{ anyArg{}, anyArg{}, @@ -59,6 +59,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { false, false, false, + domain.LabelPolicyThemeLight, }, }, }, @@ -72,7 +73,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { testEvent( org.LabelPolicyChangedEventType, org.AggregateType, - []byte(`{"backgroundColor": "#141735", "fontColor": "#ffffff", "primaryColor": "#5282c1", "warnColor": "#ff3b5b"}`), + []byte(`{"backgroundColor": "#141735", "fontColor": "#ffffff", "primaryColor": "#5282c1", "warnColor": "#ff3b5b", "themeMode": 1}`), ), org.LabelPolicyChangedEventMapper), }, reduce: (&labelPolicyProjection{}).reduceChanged, @@ -82,7 +83,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.label_policies2 SET (change_date, sequence, light_primary_color, light_background_color, light_warn_color, light_font_color) = ($1, $2, $3, $4, $5, $6) WHERE (id = $7) AND (state = $8) AND (instance_id = $9)", + expectedStmt: "UPDATE projections.label_policies3 SET (change_date, sequence, light_primary_color, light_background_color, light_warn_color, light_font_color, theme_mode) = ($1, $2, $3, $4, $5, $6, $7) WHERE (id = $8) AND (state = $9) AND (instance_id = $10)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -90,6 +91,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { "#141735", "#ff3b5b", "#ffffff", + domain.LabelPolicyThemeLight, "agg-id", domain.LabelPolicyStatePreview, "instance-id", @@ -116,7 +118,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.label_policies2 WHERE (id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.label_policies3 WHERE (id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -143,7 +145,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.label_policies2 WHERE (instance_id = $1)", + expectedStmt: "DELETE FROM projections.label_policies3 WHERE (instance_id = $1)", expectedArgs: []interface{}{ "agg-id", }, @@ -169,7 +171,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.label_policies2 (change_date, sequence, state, creation_date, resource_owner, instance_id, id, is_default, hide_login_name_suffix, font_url, watermark_disabled, should_error_popup, light_primary_color, light_warn_color, light_background_color, light_font_color, light_logo_url, light_icon_url, dark_primary_color, dark_warn_color, dark_background_color, dark_font_color, dark_logo_url, dark_icon_url) SELECT $1, $2, $3, creation_date, resource_owner, instance_id, id, is_default, hide_login_name_suffix, font_url, watermark_disabled, should_error_popup, light_primary_color, light_warn_color, light_background_color, light_font_color, light_logo_url, light_icon_url, dark_primary_color, dark_warn_color, dark_background_color, dark_font_color, dark_logo_url, dark_icon_url FROM projections.label_policies2 AS copy_table WHERE (copy_table.id = $4) AND (copy_table.state = $5) AND (copy_table.instance_id = $6) ON CONFLICT (instance_id, id, state) DO UPDATE SET (change_date, sequence, state, creation_date, resource_owner, instance_id, id, is_default, hide_login_name_suffix, font_url, watermark_disabled, should_error_popup, light_primary_color, light_warn_color, light_background_color, light_font_color, light_logo_url, light_icon_url, dark_primary_color, dark_warn_color, dark_background_color, dark_font_color, dark_logo_url, dark_icon_url) = ($1, $2, $3, EXCLUDED.creation_date, EXCLUDED.resource_owner, EXCLUDED.instance_id, EXCLUDED.id, EXCLUDED.is_default, EXCLUDED.hide_login_name_suffix, EXCLUDED.font_url, EXCLUDED.watermark_disabled, EXCLUDED.should_error_popup, EXCLUDED.light_primary_color, EXCLUDED.light_warn_color, EXCLUDED.light_background_color, EXCLUDED.light_font_color, EXCLUDED.light_logo_url, EXCLUDED.light_icon_url, EXCLUDED.dark_primary_color, EXCLUDED.dark_warn_color, EXCLUDED.dark_background_color, EXCLUDED.dark_font_color, EXCLUDED.dark_logo_url, EXCLUDED.dark_icon_url)", + expectedStmt: "INSERT INTO projections.label_policies3 (change_date, sequence, state, creation_date, resource_owner, instance_id, id, is_default, hide_login_name_suffix, font_url, watermark_disabled, should_error_popup, light_primary_color, light_warn_color, light_background_color, light_font_color, light_logo_url, light_icon_url, dark_primary_color, dark_warn_color, dark_background_color, dark_font_color, dark_logo_url, dark_icon_url, theme_mode) SELECT $1, $2, $3, creation_date, resource_owner, instance_id, id, is_default, hide_login_name_suffix, font_url, watermark_disabled, should_error_popup, light_primary_color, light_warn_color, light_background_color, light_font_color, light_logo_url, light_icon_url, dark_primary_color, dark_warn_color, dark_background_color, dark_font_color, dark_logo_url, dark_icon_url, theme_mode FROM projections.label_policies3 AS copy_table WHERE (copy_table.id = $4) AND (copy_table.state = $5) AND (copy_table.instance_id = $6) ON CONFLICT (instance_id, id, state) DO UPDATE SET (change_date, sequence, state, creation_date, resource_owner, instance_id, id, is_default, hide_login_name_suffix, font_url, watermark_disabled, should_error_popup, light_primary_color, light_warn_color, light_background_color, light_font_color, light_logo_url, light_icon_url, dark_primary_color, dark_warn_color, dark_background_color, dark_font_color, dark_logo_url, dark_icon_url, theme_mode) = ($1, $2, $3, EXCLUDED.creation_date, EXCLUDED.resource_owner, EXCLUDED.instance_id, EXCLUDED.id, EXCLUDED.is_default, EXCLUDED.hide_login_name_suffix, EXCLUDED.font_url, EXCLUDED.watermark_disabled, EXCLUDED.should_error_popup, EXCLUDED.light_primary_color, EXCLUDED.light_warn_color, EXCLUDED.light_background_color, EXCLUDED.light_font_color, EXCLUDED.light_logo_url, EXCLUDED.light_icon_url, EXCLUDED.dark_primary_color, EXCLUDED.dark_warn_color, EXCLUDED.dark_background_color, EXCLUDED.dark_font_color, EXCLUDED.dark_logo_url, EXCLUDED.dark_icon_url, EXCLUDED.theme_mode)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -200,7 +202,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.label_policies2 SET (change_date, sequence, light_logo_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.label_policies3 SET (change_date, sequence, light_logo_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -231,7 +233,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.label_policies2 SET (change_date, sequence, dark_logo_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.label_policies3 SET (change_date, sequence, dark_logo_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -262,7 +264,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.label_policies2 SET (change_date, sequence, light_icon_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.label_policies3 SET (change_date, sequence, light_icon_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -293,7 +295,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.label_policies2 SET (change_date, sequence, dark_icon_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.label_policies3 SET (change_date, sequence, dark_icon_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -324,7 +326,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.label_policies2 SET (change_date, sequence, light_logo_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.label_policies3 SET (change_date, sequence, light_logo_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -355,7 +357,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.label_policies2 SET (change_date, sequence, dark_logo_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.label_policies3 SET (change_date, sequence, dark_logo_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -386,7 +388,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.label_policies2 SET (change_date, sequence, light_icon_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.label_policies3 SET (change_date, sequence, light_icon_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -417,7 +419,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.label_policies2 SET (change_date, sequence, dark_icon_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.label_policies3 SET (change_date, sequence, dark_icon_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -448,7 +450,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.label_policies2 SET (change_date, sequence, font_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.label_policies3 SET (change_date, sequence, font_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -479,7 +481,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.label_policies2 SET (change_date, sequence, font_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.label_policies3 SET (change_date, sequence, font_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -510,7 +512,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.label_policies2 SET (change_date, sequence, light_logo_url, light_icon_url, dark_logo_url, dark_icon_url, font_url) = ($1, $2, $3, $4, $5, $6, $7) WHERE (id = $8) AND (state = $9) AND (instance_id = $10)", + expectedStmt: "UPDATE projections.label_policies3 SET (change_date, sequence, light_logo_url, light_icon_url, dark_logo_url, dark_icon_url, font_url) = ($1, $2, $3, $4, $5, $6, $7) WHERE (id = $8) AND (state = $9) AND (instance_id = $10)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -535,7 +537,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { testEvent( instance.LabelPolicyAddedEventType, instance.AggregateType, - []byte(`{"backgroundColor": "#141735", "fontColor": "#ffffff", "primaryColor": "#5282c1", "warnColor": "#ff3b5b"}`), + []byte(`{"backgroundColor": "#141735", "fontColor": "#ffffff", "primaryColor": "#5282c1", "warnColor": "#ff3b5b", "themeMode": 1}`), ), instance.LabelPolicyAddedEventMapper), }, reduce: (&labelPolicyProjection{}).reduceAdded, @@ -545,7 +547,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.label_policies2 (creation_date, change_date, sequence, id, state, is_default, resource_owner, instance_id, light_primary_color, light_background_color, light_warn_color, light_font_color, dark_primary_color, dark_background_color, dark_warn_color, dark_font_color, hide_login_name_suffix, should_error_popup, watermark_disabled) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)", + expectedStmt: "INSERT INTO projections.label_policies3 (creation_date, change_date, sequence, id, state, is_default, resource_owner, instance_id, light_primary_color, light_background_color, light_warn_color, light_font_color, dark_primary_color, dark_background_color, dark_warn_color, dark_font_color, hide_login_name_suffix, should_error_popup, watermark_disabled, theme_mode) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)", expectedArgs: []interface{}{ anyArg{}, anyArg{}, @@ -566,6 +568,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { false, false, false, + domain.LabelPolicyThemeLight, }, }, }, @@ -579,7 +582,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { testEvent( instance.LabelPolicyChangedEventType, instance.AggregateType, - []byte(`{"backgroundColor": "#141735", "fontColor": "#ffffff", "primaryColor": "#5282c1", "warnColor": "#ff3b5b", "primaryColorDark": "#ffffff","backgroundColorDark": "#ffffff", "warnColorDark": "#ffffff", "fontColorDark": "#ffffff", "hideLoginNameSuffix": true, "errorMsgPopup": true, "disableWatermark": true}`), + []byte(`{"backgroundColor": "#141735", "fontColor": "#ffffff", "primaryColor": "#5282c1", "warnColor": "#ff3b5b", "primaryColorDark": "#ffffff","backgroundColorDark": "#ffffff", "warnColorDark": "#ffffff", "fontColorDark": "#ffffff", "hideLoginNameSuffix": true, "errorMsgPopup": true, "disableWatermark": true, "themeMode": 1}`), ), instance.LabelPolicyChangedEventMapper), }, reduce: (&labelPolicyProjection{}).reduceChanged, @@ -589,7 +592,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.label_policies2 SET (change_date, sequence, light_primary_color, light_background_color, light_warn_color, light_font_color, dark_primary_color, dark_background_color, dark_warn_color, dark_font_color, hide_login_name_suffix, should_error_popup, watermark_disabled) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) WHERE (id = $14) AND (state = $15) AND (instance_id = $16)", + expectedStmt: "UPDATE projections.label_policies3 SET (change_date, sequence, light_primary_color, light_background_color, light_warn_color, light_font_color, dark_primary_color, dark_background_color, dark_warn_color, dark_font_color, hide_login_name_suffix, should_error_popup, watermark_disabled, theme_mode) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) WHERE (id = $15) AND (state = $16) AND (instance_id = $17)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -604,6 +607,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { true, true, true, + domain.LabelPolicyThemeLight, "agg-id", domain.LabelPolicyStatePreview, "instance-id", @@ -630,7 +634,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.label_policies2 (change_date, sequence, state, creation_date, resource_owner, instance_id, id, is_default, hide_login_name_suffix, font_url, watermark_disabled, should_error_popup, light_primary_color, light_warn_color, light_background_color, light_font_color, light_logo_url, light_icon_url, dark_primary_color, dark_warn_color, dark_background_color, dark_font_color, dark_logo_url, dark_icon_url) SELECT $1, $2, $3, creation_date, resource_owner, instance_id, id, is_default, hide_login_name_suffix, font_url, watermark_disabled, should_error_popup, light_primary_color, light_warn_color, light_background_color, light_font_color, light_logo_url, light_icon_url, dark_primary_color, dark_warn_color, dark_background_color, dark_font_color, dark_logo_url, dark_icon_url FROM projections.label_policies2 AS copy_table WHERE (copy_table.id = $4) AND (copy_table.state = $5) AND (copy_table.instance_id = $6) ON CONFLICT (instance_id, id, state) DO UPDATE SET (change_date, sequence, state, creation_date, resource_owner, instance_id, id, is_default, hide_login_name_suffix, font_url, watermark_disabled, should_error_popup, light_primary_color, light_warn_color, light_background_color, light_font_color, light_logo_url, light_icon_url, dark_primary_color, dark_warn_color, dark_background_color, dark_font_color, dark_logo_url, dark_icon_url) = ($1, $2, $3, EXCLUDED.creation_date, EXCLUDED.resource_owner, EXCLUDED.instance_id, EXCLUDED.id, EXCLUDED.is_default, EXCLUDED.hide_login_name_suffix, EXCLUDED.font_url, EXCLUDED.watermark_disabled, EXCLUDED.should_error_popup, EXCLUDED.light_primary_color, EXCLUDED.light_warn_color, EXCLUDED.light_background_color, EXCLUDED.light_font_color, EXCLUDED.light_logo_url, EXCLUDED.light_icon_url, EXCLUDED.dark_primary_color, EXCLUDED.dark_warn_color, EXCLUDED.dark_background_color, EXCLUDED.dark_font_color, EXCLUDED.dark_logo_url, EXCLUDED.dark_icon_url)", + expectedStmt: "INSERT INTO projections.label_policies3 (change_date, sequence, state, creation_date, resource_owner, instance_id, id, is_default, hide_login_name_suffix, font_url, watermark_disabled, should_error_popup, light_primary_color, light_warn_color, light_background_color, light_font_color, light_logo_url, light_icon_url, dark_primary_color, dark_warn_color, dark_background_color, dark_font_color, dark_logo_url, dark_icon_url, theme_mode) SELECT $1, $2, $3, creation_date, resource_owner, instance_id, id, is_default, hide_login_name_suffix, font_url, watermark_disabled, should_error_popup, light_primary_color, light_warn_color, light_background_color, light_font_color, light_logo_url, light_icon_url, dark_primary_color, dark_warn_color, dark_background_color, dark_font_color, dark_logo_url, dark_icon_url, theme_mode FROM projections.label_policies3 AS copy_table WHERE (copy_table.id = $4) AND (copy_table.state = $5) AND (copy_table.instance_id = $6) ON CONFLICT (instance_id, id, state) DO UPDATE SET (change_date, sequence, state, creation_date, resource_owner, instance_id, id, is_default, hide_login_name_suffix, font_url, watermark_disabled, should_error_popup, light_primary_color, light_warn_color, light_background_color, light_font_color, light_logo_url, light_icon_url, dark_primary_color, dark_warn_color, dark_background_color, dark_font_color, dark_logo_url, dark_icon_url, theme_mode) = ($1, $2, $3, EXCLUDED.creation_date, EXCLUDED.resource_owner, EXCLUDED.instance_id, EXCLUDED.id, EXCLUDED.is_default, EXCLUDED.hide_login_name_suffix, EXCLUDED.font_url, EXCLUDED.watermark_disabled, EXCLUDED.should_error_popup, EXCLUDED.light_primary_color, EXCLUDED.light_warn_color, EXCLUDED.light_background_color, EXCLUDED.light_font_color, EXCLUDED.light_logo_url, EXCLUDED.light_icon_url, EXCLUDED.dark_primary_color, EXCLUDED.dark_warn_color, EXCLUDED.dark_background_color, EXCLUDED.dark_font_color, EXCLUDED.dark_logo_url, EXCLUDED.dark_icon_url, EXCLUDED.theme_mode)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -661,7 +665,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.label_policies2 SET (change_date, sequence, light_logo_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.label_policies3 SET (change_date, sequence, light_logo_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -692,7 +696,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.label_policies2 SET (change_date, sequence, dark_logo_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.label_policies3 SET (change_date, sequence, dark_logo_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -723,7 +727,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.label_policies2 SET (change_date, sequence, light_icon_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.label_policies3 SET (change_date, sequence, light_icon_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -754,7 +758,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.label_policies2 SET (change_date, sequence, dark_icon_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.label_policies3 SET (change_date, sequence, dark_icon_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -785,7 +789,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.label_policies2 SET (change_date, sequence, light_logo_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.label_policies3 SET (change_date, sequence, light_logo_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -816,7 +820,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.label_policies2 SET (change_date, sequence, dark_logo_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.label_policies3 SET (change_date, sequence, dark_logo_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -847,7 +851,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.label_policies2 SET (change_date, sequence, light_icon_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.label_policies3 SET (change_date, sequence, light_icon_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -878,7 +882,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.label_policies2 SET (change_date, sequence, dark_icon_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.label_policies3 SET (change_date, sequence, dark_icon_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -909,7 +913,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.label_policies2 SET (change_date, sequence, font_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.label_policies3 SET (change_date, sequence, font_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -940,7 +944,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.label_policies2 SET (change_date, sequence, font_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.label_policies3 SET (change_date, sequence, font_url) = ($1, $2, $3) WHERE (id = $4) AND (state = $5) AND (instance_id = $6)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -971,7 +975,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.label_policies2 SET (change_date, sequence, light_logo_url, light_icon_url, dark_logo_url, dark_icon_url, font_url) = ($1, $2, $3, $4, $5, $6, $7) WHERE (id = $8) AND (state = $9) AND (instance_id = $10)", + expectedStmt: "UPDATE projections.label_policies3 SET (change_date, sequence, light_logo_url, light_icon_url, dark_logo_url, dark_icon_url, font_url) = ($1, $2, $3, $4, $5, $6, $7) WHERE (id = $8) AND (state = $9) AND (instance_id = $10)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1006,7 +1010,7 @@ func TestLabelPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.label_policies2 WHERE (instance_id = $1) AND (resource_owner = $2)", + expectedStmt: "DELETE FROM projections.label_policies3 WHERE (instance_id = $1) AND (resource_owner = $2)", expectedArgs: []interface{}{ "instance-id", "agg-id", diff --git a/internal/repository/instance/policy_label.go b/internal/repository/instance/policy_label.go index 6b14b1cef0..f76013b3ed 100644 --- a/internal/repository/instance/policy_label.go +++ b/internal/repository/instance/policy_label.go @@ -3,6 +3,7 @@ package instance import ( "context" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/policy" ) @@ -45,6 +46,7 @@ func NewLabelPolicyAddedEvent( hideLoginNameSuffix, errorMsgPopup, disableWatermark bool, + themeMode domain.LabelPolicyThemeMode, ) *LabelPolicyAddedEvent { return &LabelPolicyAddedEvent{ LabelPolicyAddedEvent: *policy.NewLabelPolicyAddedEvent( @@ -62,7 +64,8 @@ func NewLabelPolicyAddedEvent( fontColorDark, hideLoginNameSuffix, errorMsgPopup, - disableWatermark), + disableWatermark, + themeMode), } } diff --git a/internal/repository/org/policy_label.go b/internal/repository/org/policy_label.go index 2ca2c874ba..a467b6f5e4 100644 --- a/internal/repository/org/policy_label.go +++ b/internal/repository/org/policy_label.go @@ -3,6 +3,7 @@ package org import ( "context" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/policy" ) @@ -46,6 +47,7 @@ func NewLabelPolicyAddedEvent( hideLoginNameSuffix, errorMsgPopup, disableWatermark bool, + themeMode domain.LabelPolicyThemeMode, ) *LabelPolicyAddedEvent { return &LabelPolicyAddedEvent{ LabelPolicyAddedEvent: *policy.NewLabelPolicyAddedEvent( @@ -63,7 +65,8 @@ func NewLabelPolicyAddedEvent( fontColorDark, hideLoginNameSuffix, errorMsgPopup, - disableWatermark), + disableWatermark, + themeMode), } } diff --git a/internal/repository/policy/label.go b/internal/repository/policy/label.go index 39d6379180..d3025b5ba0 100644 --- a/internal/repository/policy/label.go +++ b/internal/repository/policy/label.go @@ -1,6 +1,7 @@ package policy import ( + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/errors" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/asset" @@ -32,17 +33,18 @@ const ( type LabelPolicyAddedEvent struct { eventstore.BaseEvent `json:"-"` - PrimaryColor string `json:"primaryColor,omitempty"` - BackgroundColor string `json:"backgroundColor,omitempty"` - WarnColor string `json:"warnColor,omitempty"` - FontColor string `json:"fontColor,omitempty"` - PrimaryColorDark string `json:"primaryColorDark,omitempty"` - BackgroundColorDark string `json:"backgroundColorDark,omitempty"` - WarnColorDark string `json:"warnColorDark,omitempty"` - FontColorDark string `json:"fontColorDark,omitempty"` - HideLoginNameSuffix bool `json:"hideLoginNameSuffix,omitempty"` - ErrorMsgPopup bool `json:"errorMsgPopup,omitempty"` - DisableWatermark bool `json:"disableMsgPopup,omitempty"` + PrimaryColor string `json:"primaryColor,omitempty"` + BackgroundColor string `json:"backgroundColor,omitempty"` + WarnColor string `json:"warnColor,omitempty"` + FontColor string `json:"fontColor,omitempty"` + PrimaryColorDark string `json:"primaryColorDark,omitempty"` + BackgroundColorDark string `json:"backgroundColorDark,omitempty"` + WarnColorDark string `json:"warnColorDark,omitempty"` + FontColorDark string `json:"fontColorDark,omitempty"` + HideLoginNameSuffix bool `json:"hideLoginNameSuffix,omitempty"` + ErrorMsgPopup bool `json:"errorMsgPopup,omitempty"` + DisableWatermark bool `json:"disableMsgPopup,omitempty"` + ThemeMode domain.LabelPolicyThemeMode `json:"themeMode,omitempty"` } func (e *LabelPolicyAddedEvent) Payload() interface{} { @@ -66,6 +68,7 @@ func NewLabelPolicyAddedEvent( hideLoginNameSuffix, errorMsgPopup, disableWatermark bool, + themeMode domain.LabelPolicyThemeMode, ) *LabelPolicyAddedEvent { return &LabelPolicyAddedEvent{ @@ -81,6 +84,7 @@ func NewLabelPolicyAddedEvent( HideLoginNameSuffix: hideLoginNameSuffix, ErrorMsgPopup: errorMsgPopup, DisableWatermark: disableWatermark, + ThemeMode: themeMode, } } @@ -100,17 +104,18 @@ func LabelPolicyAddedEventMapper(event eventstore.Event) (eventstore.Event, erro type LabelPolicyChangedEvent struct { eventstore.BaseEvent `json:"-"` - PrimaryColor *string `json:"primaryColor,omitempty"` - BackgroundColor *string `json:"backgroundColor,omitempty"` - WarnColor *string `json:"warnColor,omitempty"` - FontColor *string `json:"fontColor,omitempty"` - PrimaryColorDark *string `json:"primaryColorDark,omitempty"` - BackgroundColorDark *string `json:"backgroundColorDark,omitempty"` - WarnColorDark *string `json:"warnColorDark,omitempty"` - FontColorDark *string `json:"fontColorDark,omitempty"` - HideLoginNameSuffix *bool `json:"hideLoginNameSuffix,omitempty"` - ErrorMsgPopup *bool `json:"errorMsgPopup,omitempty"` - DisableWatermark *bool `json:"disableWatermark,omitempty"` + PrimaryColor *string `json:"primaryColor,omitempty"` + BackgroundColor *string `json:"backgroundColor,omitempty"` + WarnColor *string `json:"warnColor,omitempty"` + FontColor *string `json:"fontColor,omitempty"` + PrimaryColorDark *string `json:"primaryColorDark,omitempty"` + BackgroundColorDark *string `json:"backgroundColorDark,omitempty"` + WarnColorDark *string `json:"warnColorDark,omitempty"` + FontColorDark *string `json:"fontColorDark,omitempty"` + HideLoginNameSuffix *bool `json:"hideLoginNameSuffix,omitempty"` + ErrorMsgPopup *bool `json:"errorMsgPopup,omitempty"` + DisableWatermark *bool `json:"disableWatermark,omitempty"` + ThemeMode *domain.LabelPolicyThemeMode `json:"themeMode,omitempty"` } func (e *LabelPolicyChangedEvent) Payload() interface{} { @@ -205,6 +210,12 @@ func ChangeDisableWatermark(disableWatermark bool) func(*LabelPolicyChangedEvent } } +func ChangeThemeMode(themeMode domain.LabelPolicyThemeMode) func(*LabelPolicyChangedEvent) { + return func(e *LabelPolicyChangedEvent) { + e.ThemeMode = &themeMode + } +} + func LabelPolicyChangedEventMapper(event eventstore.Event) (eventstore.Event, error) { e := &LabelPolicyChangedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto index f750c4d27f..1a67f03a4f 100644 --- a/proto/zitadel/admin.proto +++ b/proto/zitadel/admin.proto @@ -6230,6 +6230,11 @@ message UpdateLabelPolicyRequest { } ]; bool disable_watermark = 11; + zitadel.policy.v1.ThemeMode theme_mode = 12 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "setting if there should be a restriction on which themes are available"; + } + ]; } message UpdateLabelPolicyResponse { diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto index 3c485f7a92..53caf0c2af 100644 --- a/proto/zitadel/management.proto +++ b/proto/zitadel/management.proto @@ -10583,6 +10583,11 @@ message AddCustomLabelPolicyRequest { } ]; bool disable_watermark = 11; + zitadel.policy.v1.ThemeMode theme_mode = 12 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "setting if there should be a restriction on which themes are available"; + } + ]; } message AddCustomLabelPolicyResponse { @@ -10653,6 +10658,11 @@ message UpdateCustomLabelPolicyRequest { } ]; bool disable_watermark = 11; + zitadel.policy.v1.ThemeMode theme_mode = 12 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "setting if there should be a restriction on which themes are available"; + } + ]; } message UpdateCustomLabelPolicyResponse { diff --git a/proto/zitadel/policy.proto b/proto/zitadel/policy.proto index 4b3aa9035e..dc1c0cf9e0 100644 --- a/proto/zitadel/policy.proto +++ b/proto/zitadel/policy.proto @@ -145,6 +145,14 @@ message LabelPolicy { } ]; string font_url = 18; + ThemeMode theme_mode = 19; +} + +enum ThemeMode { + THEME_MODE_UNSPECIFIED = 0; + THEME_MODE_AUTO = 1; + THEME_MODE_DARK = 2; + THEME_MODE_LIGHT = 3; } message LoginPolicy { diff --git a/proto/zitadel/settings/v2beta/branding_settings.proto b/proto/zitadel/settings/v2beta/branding_settings.proto index 5ddc4067ce..4c50847f48 100644 --- a/proto/zitadel/settings/v2beta/branding_settings.proto +++ b/proto/zitadel/settings/v2beta/branding_settings.proto @@ -33,6 +33,11 @@ message BrandingSettings { description: "resource_owner_type returns if the setting is managed on the organization or on the instance"; } ]; + ThemeMode theme_mode = 7 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "states whether both or only dark or light theme will be used"; + } + ]; } message Theme { @@ -79,3 +84,10 @@ message Theme { } ]; } + +enum ThemeMode { + THEME_MODE_UNSPECIFIED = 0; + THEME_MODE_AUTO = 1; + THEME_MODE_LIGHT = 2; + THEME_MODE_DARK = 3; +} \ No newline at end of file