mirror of
https://github.com/grafana/grafana.git
synced 2025-08-02 12:22:11 +08:00
Themes: Switch theme without reload using global shortcut (#32180)
* Themes: Switch theme without reload using global shortcut * Review updates
This commit is contained in:
@ -19,6 +19,7 @@ import { selectors } from '@grafana/e2e-selectors';
|
||||
|
||||
import { DashboardSearchHit, DashboardSearchItemType } from 'app/features/search/types';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { PreferencesService } from 'app/core/services/PreferencesService';
|
||||
|
||||
export interface Props {
|
||||
resourceUri: string;
|
||||
@ -38,11 +39,12 @@ const themes: SelectableValue[] = [
|
||||
];
|
||||
|
||||
export class SharedPreferences extends PureComponent<Props, State> {
|
||||
backendSrv = backendSrv;
|
||||
service: PreferencesService;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.service = new PreferencesService(props.resourceUri);
|
||||
this.state = {
|
||||
homeDashboardId: 0,
|
||||
theme: '',
|
||||
@ -52,7 +54,7 @@ export class SharedPreferences extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const prefs = await backendSrv.get(`/api/${this.props.resourceUri}/preferences`);
|
||||
const prefs = await this.service.load();
|
||||
const dashboards = await backendSrv.search({ starred: true });
|
||||
const defaultDashboardHit: DashboardSearchHit = {
|
||||
id: 0,
|
||||
@ -88,12 +90,7 @@ export class SharedPreferences extends PureComponent<Props, State> {
|
||||
|
||||
onSubmitForm = async () => {
|
||||
const { homeDashboardId, theme, timezone } = this.state;
|
||||
|
||||
await backendSrv.put(`/api/${this.props.resourceUri}/preferences`, {
|
||||
homeDashboardId,
|
||||
theme,
|
||||
timezone,
|
||||
});
|
||||
this.service.update({ homeDashboardId, theme, timezone });
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
|
14
public/app/core/services/PreferencesService.ts
Normal file
14
public/app/core/services/PreferencesService.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { UserPreferencesDTO } from 'app/types';
|
||||
import { backendSrv } from './backend_srv';
|
||||
|
||||
export class PreferencesService {
|
||||
constructor(private resourceUri: string) {}
|
||||
|
||||
update(preferences: UserPreferencesDTO): Promise<any> {
|
||||
return backendSrv.put(`/api/${this.resourceUri}/preferences`, preferences);
|
||||
}
|
||||
|
||||
load(): Promise<UserPreferencesDTO> {
|
||||
return backendSrv.get<UserPreferencesDTO>(`/api/${this.resourceUri}/preferences`);
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ import {
|
||||
import { contextSrv } from '../core';
|
||||
import { getDatasourceSrv } from '../../features/plugins/datasource_srv';
|
||||
import { getTimeSrv } from '../../features/dashboard/services/TimeSrv';
|
||||
import { toggleTheme } from './toggleTheme';
|
||||
|
||||
export class KeybindingSrv {
|
||||
modalOpen = false;
|
||||
@ -43,6 +44,9 @@ export class KeybindingSrv {
|
||||
this.bind('esc', this.exit);
|
||||
this.bindGlobal('esc', this.globalEsc);
|
||||
}
|
||||
|
||||
this.bind('t t', () => toggleTheme(false));
|
||||
this.bind('t r', () => toggleTheme(true));
|
||||
}
|
||||
|
||||
private globalEsc() {
|
||||
|
43
public/app/core/services/toggleTheme.ts
Normal file
43
public/app/core/services/toggleTheme.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { getTheme } from '@grafana/ui';
|
||||
import { ThemeChangedEvent } from 'app/types/events';
|
||||
import appEvents from '../app_events';
|
||||
import { config } from '../config';
|
||||
import { PreferencesService } from './PreferencesService';
|
||||
|
||||
export async function toggleTheme(runtimeOnly: boolean) {
|
||||
const currentTheme = config.theme;
|
||||
const newTheme = getTheme(currentTheme.isDark ? 'light' : 'dark');
|
||||
appEvents.publish(new ThemeChangedEvent(newTheme));
|
||||
|
||||
if (runtimeOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add css file for new theme
|
||||
const newCssLink = document.createElement('link');
|
||||
newCssLink.rel = 'stylesheet';
|
||||
newCssLink.href = config.bootData.themePaths[newTheme.type];
|
||||
document.body.appendChild(newCssLink);
|
||||
|
||||
// Remove old css file
|
||||
const bodyLinks = document.getElementsByTagName('link');
|
||||
for (let i = 0; i < bodyLinks.length; i++) {
|
||||
const link = bodyLinks[i];
|
||||
|
||||
if (link.href && link.href.indexOf(`build/grafana.${currentTheme.type}`) > 0) {
|
||||
// Remove existing link after a 500ms to allow new css to load to avoid flickering
|
||||
// If we add new css at the same time we remove current one the page will be rendered without css
|
||||
// As the new css file is loading
|
||||
setTimeout(() => link.remove(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Persist new theme
|
||||
const service = new PreferencesService('user');
|
||||
const currentPref = await service.load();
|
||||
|
||||
await service.update({
|
||||
...currentPref,
|
||||
theme: newTheme.type,
|
||||
});
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
import { coreModule, NavModelSrv } from 'app/core/core';
|
||||
import { dateTimeFormat, dateTimeFormatTimeAgo } from '@grafana/data';
|
||||
import { UserSession } from 'app/types';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { promiseToDigest } from 'app/core/utils/promiseToDigest';
|
||||
import { IScope } from 'angular';
|
||||
|
||||
export class ProfileCtrl {
|
||||
sessions: object[] = [];
|
||||
navModel: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private $scope: IScope, navModelSrv: NavModelSrv) {
|
||||
this.getUserSessions();
|
||||
this.navModel = navModelSrv.getNav('profile', 'profile-settings', 0);
|
||||
}
|
||||
|
||||
getUserSessions() {
|
||||
promiseToDigest(this.$scope)(
|
||||
getBackendSrv()
|
||||
.get('/api/user/auth-tokens')
|
||||
.then((sessions: UserSession[]) => {
|
||||
sessions.reverse();
|
||||
|
||||
const found = sessions.findIndex((session: UserSession) => {
|
||||
return session.isActive;
|
||||
});
|
||||
|
||||
if (found) {
|
||||
const now = sessions[found];
|
||||
sessions.splice(found, found);
|
||||
sessions.unshift(now);
|
||||
}
|
||||
|
||||
this.sessions = sessions.map((session: UserSession) => {
|
||||
return {
|
||||
id: session.id,
|
||||
isActive: session.isActive,
|
||||
seenAt: dateTimeFormatTimeAgo(session.seenAt),
|
||||
createdAt: dateTimeFormat(session.createdAt, { format: 'MMMM DD, YYYY' }),
|
||||
clientIp: session.clientIp,
|
||||
browser: session.browser,
|
||||
browserVersion: session.browserVersion,
|
||||
os: session.os,
|
||||
osVersion: session.osVersion,
|
||||
device: session.device,
|
||||
};
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
revokeUserSession(tokenId: number) {
|
||||
promiseToDigest(this.$scope)(
|
||||
getBackendSrv()
|
||||
.post('/api/user/revoke-auth-token', {
|
||||
authTokenId: tokenId,
|
||||
})
|
||||
.then(() => {
|
||||
this.sessions = this.sessions.filter((session: UserSession) => {
|
||||
if (session.id === tokenId) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
coreModule.controller('ProfileCtrl', ProfileCtrl);
|
@ -16,6 +16,7 @@ export * from './ldap';
|
||||
export * from './appEvent';
|
||||
export * from './angular';
|
||||
export * from './query';
|
||||
export * from './preferences';
|
||||
|
||||
import * as CoreEvents from './events';
|
||||
export { CoreEvents };
|
||||
|
7
public/app/types/preferences.ts
Normal file
7
public/app/types/preferences.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { TimeZone } from '@grafana/data';
|
||||
|
||||
export interface UserPreferencesDTO {
|
||||
timezone: TimeZone;
|
||||
homeDashboardId: number;
|
||||
theme: string;
|
||||
}
|
@ -257,7 +257,11 @@
|
||||
window.grafanaBootData = {
|
||||
user: [[.User]],
|
||||
settings: [[.Settings]],
|
||||
navTree: [[.NavTree]]
|
||||
navTree: [[.NavTree]],
|
||||
themePaths: {
|
||||
light: '[[.ContentDeliveryURL]]public/build/grafana.light.<%= webpack.hash %>.css',
|
||||
dark: '[[.ContentDeliveryURL]]public/build/grafana.dark.<%= webpack.hash %>.css'
|
||||
}
|
||||
};
|
||||
|
||||
// In case the js files fails to load the code below will show an info message.
|
||||
|
Reference in New Issue
Block a user