Skip to content

Commit a643d1a

Browse files
committed
feat(ui): configurable default theme
Add support for setting a default theme (light, dark, auto) via the argocd-cm ConfigMap using the ui.defaulttheme key. The backend setting is exposed through the /api/v1/settings API and consumed by the UI on initialization, allowing operators to configure the default theme without requiring users to manually select it. Closes: #18524 Signed-off-by: pasteley <ceasebeing@gmail.com>
1 parent 104cd72 commit a643d1a

File tree

6 files changed

+24
-1
lines changed

6 files changed

+24
-1
lines changed

server/settings/settings.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ func (s *Server) Get(ctx context.Context, _ *settingspkg.SettingsQuery) (*settin
116116
UserLoginsDisabled: userLoginsDisabled,
117117
KustomizeVersions: kustomizeVersions,
118118
UiCssURL: argoCDSettings.UiCssURL,
119+
UiDefaultTheme: argoCDSettings.UiDefaultTheme,
119120
TrackingMethod: trackingMethod,
120121
InstallationID: installationID,
121122
ExecEnabled: argoCDSettings.ExecEnabled,

server/settings/settings.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ message Settings {
4040
string uiBannerPosition = 20;
4141
string statusBadgeRootUrl = 21;
4242
bool execEnabled = 22;
43+
string uiDefaultTheme = 30;
4344
string controllerNamespace = 23;
4445
bool appsInAnyNamespaceEnabled = 24;
4546
bool impersonationEnabled = 25;

ui/src/app/app.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ export class App extends React.Component<{}, {popupProps: PopupProps; showVersio
122122
this.unauthorizedSubscription = subscription;
123123
});
124124
const authSettings = await services.authService.settings();
125+
if (authSettings.uiDefaultTheme) {
126+
services.viewPreferences.setBackendDefaultTheme(authSettings.uiDefaultTheme);
127+
}
125128
const {trackingID, anonymizeUsers} = authSettings.googleAnalytics || {trackingID: '', anonymizeUsers: true};
126129
const {loggedIn, username} = await services.users.get();
127130
if (trackingID) {

ui/src/app/shared/models.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,7 @@ export interface AuthSettings {
609609
userLoginsDisabled: boolean;
610610
kustomizeVersions: string[];
611611
uiCssURL: string;
612+
uiDefaultTheme: string;
612613
uiBannerContent: string;
613614
uiBannerURL: string;
614615
uiBannerPermanent: boolean;

ui/src/app/shared/services/view-preferences-service.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ const DEFAULT_PREFERENCES: ViewPreferences = {
204204

205205
export class ViewPreferencesService {
206206
private preferencesSubj: BehaviorSubject<ViewPreferences>;
207+
private backendDefaultTheme: string = '';
207208

208209
public init() {
209210
if (!this.preferencesSubj) {
@@ -214,6 +215,12 @@ export class ViewPreferencesService {
214215
}
215216
}
216217

218+
public setBackendDefaultTheme(theme: string) {
219+
this.backendDefaultTheme = theme;
220+
// Reload preferences with the new backend default
221+
this.preferencesSubj.next(this.loadPreferences());
222+
}
223+
217224
public getPreferences(): Observable<ViewPreferences> {
218225
return this.preferencesSubj;
219226
}
@@ -239,6 +246,11 @@ export class ViewPreferencesService {
239246
} else {
240247
preferences = DEFAULT_PREFERENCES;
241248
}
242-
return deepMerge(DEFAULT_PREFERENCES, preferences);
249+
const merged = deepMerge(DEFAULT_PREFERENCES, preferences);
250+
// If backend default theme is set and user hasn't explicitly set a theme, use backend default
251+
if (this.backendDefaultTheme && (!preferencesStr || !JSON.parse(preferencesStr).theme)) {
252+
merged.theme = this.backendDefaultTheme;
253+
}
254+
return merged;
243255
}
244256
}

util/settings/settings.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ type ArgoCDSettings struct {
115115
UiBannerPermanent bool `json:"uiBannerPermanent,omitempty"` //nolint:revive //FIXME(var-naming)
116116
// Position of UI Banner
117117
UiBannerPosition string `json:"uiBannerPosition,omitempty"` //nolint:revive //FIXME(var-naming)
118+
// UiDefaultTheme holds the default theme for the UI (light, dark, auto)
119+
UiDefaultTheme string `json:"uiDefaultTheme,omitempty"` //nolint:revive //FIXME(var-naming)
118120
// PasswordPattern for password regular expression
119121
PasswordPattern string `json:"passwordPattern,omitempty"`
120122
// BinaryUrls contains the URLs for downloading argocd binaries
@@ -505,6 +507,8 @@ const (
505507
settingUIBannerPermanentKey = "ui.bannerpermanent"
506508
// settingUIBannerPositionKey designates the key for the position of the banner
507509
settingUIBannerPositionKey = "ui.bannerposition"
510+
// settingUIDefaultThemeKey designates the key for the default theme (light, dark, auto)
511+
settingUIDefaultThemeKey = "ui.defaulttheme"
508512
// settingsBinaryUrlsKey designates the key for the argocd binary URLs
509513
settingsBinaryUrlsKey = "help.download"
510514
// settingsApplicationInstanceLabelKey is the key to configure injected app instance label key
@@ -1473,6 +1477,7 @@ func updateSettingsFromConfigMap(settings *ArgoCDSettings, argoCDCM *corev1.Conf
14731477
settings.UiBannerContent = argoCDCM.Data[settingUIBannerContentKey]
14741478
settings.UiBannerPermanent = argoCDCM.Data[settingUIBannerPermanentKey] == "true"
14751479
settings.UiBannerPosition = argoCDCM.Data[settingUIBannerPositionKey]
1480+
settings.UiDefaultTheme = argoCDCM.Data[settingUIDefaultThemeKey]
14761481
settings.BinaryUrls = getDownloadBinaryUrlsFromConfigMap(argoCDCM)
14771482
if err := ValidateExternalURL(argoCDCM.Data[settingURLKey]); err != nil {
14781483
log.Warnf("Failed to validate URL in configmap: %v", err)

0 commit comments

Comments
 (0)