diff --git a/public/app/core/components/SharedPreferences/SharedPreferences.tsx b/public/app/core/components/SharedPreferences/SharedPreferences.tsx index dcc1a250133f6..7e99bbef962f7 100644 --- a/public/app/core/components/SharedPreferences/SharedPreferences.tsx +++ b/public/app/core/components/SharedPreferences/SharedPreferences.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { FeatureState, getBuiltInThemes, ThemeRegistryItem } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { config, reportInteraction } from '@grafana/runtime'; +import { config, reportInteraction, getAppEvents, ThemeChangedEvent } from '@grafana/runtime'; import { Preferences as UserPreferencesDTO } from '@grafana/schema/src/raw/preferences/x/preferences_types.gen'; import { Button, @@ -26,11 +26,14 @@ import { t, Trans } from 'app/core/internationalization'; import { LANGUAGES, PSEUDO_LOCALE } from 'app/core/internationalization/constants'; import { PreferencesService } from 'app/core/services/PreferencesService'; import { changeTheme } from 'app/core/services/theme'; +import type { Unsubscribable } from 'rxjs'; + export interface Props { resourceUri: string; disabled?: boolean; preferenceType: 'org' | 'team' | 'user'; onConfirm?: () => Promise; + eventBus?: { subscribe: Function }; } export type State = UserPreferencesDTO & { @@ -67,6 +70,7 @@ function getLanguageOptions(): ComboboxOption[] { export class SharedPreferences extends PureComponent { service: PreferencesService; themeOptions: ComboboxOption[]; + private themeChangedSub?: Unsubscribable; constructor(props: Props) { super(props); @@ -110,6 +114,7 @@ export class SharedPreferences extends PureComponent { isLoading: true, }); const prefs = await this.service.load(); + console.log('Loaded preferences:'); this.setState({ isLoading: false, @@ -121,6 +126,32 @@ export class SharedPreferences extends PureComponent { queryHistory: prefs.queryHistory, navbar: prefs.navbar, }); + + // Listen to Grafana theme changes and reflect them in the dropdown immediately + const eventBus = this.props.eventBus ?? getAppEvents(); + if (eventBus && typeof eventBus.subscribe === 'function') { + this.themeChangedSub = eventBus.subscribe(ThemeChangedEvent, (evt: any) => { + try { + const payload = evt?.payload; + const isDark = typeof payload?.isDark === 'boolean' ? payload.isDark : payload?.colors?.mode === 'dark'; + const mode: 'light' | 'dark' = isDark ? 'dark' : 'light'; + + if (this.state.theme !== mode) { + this.setState({ theme: mode }); + } + } catch (err) { + console.warn('[SharedPreferences] Failed to sync theme from ThemeChangedEvent:', err); + } + }); + } + } + + componentWillUnmount() { + try { + this.themeChangedSub?.unsubscribe(); + } catch (err) { + console.warn('[SharedPreferences] Failed to unsubscribe ThemeChangedEvent:', err); + } } onSubmitForm = async (event: React.FormEvent) => { @@ -135,15 +166,37 @@ export class SharedPreferences extends PureComponent { }; onThemeChanged = (value: ComboboxOption) => { - this.setState({ theme: value.value }); + const raw = value?.value ?? ''; + const prev = this.state.theme; + + if (prev !== raw) { + this.setState({ theme: raw }); + } + reportInteraction('grafana_preferences_theme_changed', { - toTheme: value.value, + toTheme: raw, preferenceType: this.props.preferenceType, }); - if (value.value) { - changeTheme(value.value, true); + if (raw) { + try { + changeTheme(raw, true); + } catch (err) {} } + + try { + const theme2 = config?.theme2; + if (!theme2) { + console.warn('[SharedPreferences] publish skipped: config.theme2 is not available'); + } else { + const appEvents = getAppEvents(); + if (typeof appEvents.publish === 'function') { + appEvents.publish(new ThemeChangedEvent(theme2)); + } else { + console.warn('[SharedPreferences] publish ThemeChangedEvent skipped: publish is not a function'); + } + } + } catch (err) {} }; onTimeZoneChanged = (timezone?: string) => { diff --git a/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx b/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx index cf09a1978040c..e490915ff0be5 100644 --- a/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx +++ b/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx @@ -5,13 +5,11 @@ import { useAsync } from 'react-use'; import { GrafanaTheme2, ScopedVars } from '@grafana/data'; import { sanitize, sanitizeUrl } from '@grafana/data/src/text/sanitize'; import { selectors } from '@grafana/e2e-selectors'; -import { config } from '@grafana/runtime'; import { DashboardLink } from '@grafana/schema'; import { Dropdown, Icon, Button, Menu, ScrollContainer, useStyles2 } from '@grafana/ui'; import { ButtonLinkProps, LinkButton } from '@grafana/ui/src/components/Button'; import { getBackendSrv } from 'app/core/services/backend_srv'; import { DashboardSearchItem } from 'app/features/search/types'; -import { isPmmAdmin } from 'app/percona/shared/helpers/permissions'; import { getLinkSrv } from '../../../panel/panellinks/link_srv'; @@ -29,22 +27,7 @@ interface DashboardLinksMenuProps { function DashboardLinksMenu({ dashboardUID, link }: DashboardLinksMenuProps) { const styles = useStyles2(getStyles); - let resolvedLinks = useResolvedLinks({ dashboardUID, link }); - - // @PERCONA - // TODO: PMM-7736 remove it ASAP after migration transition period is finished - if (link.title === 'PMM') { - if (isPmmAdmin(config.bootData.user)) { - resolvedLinks = [ - { uid: '1000', url: '/graph/add-instance', title: 'PMM Add Instance' }, - { uid: '1001', url: '/graph/advisors/insights', title: 'PMM Advisors' }, - { uid: '1002', url: '/graph/inventory', title: 'PMM Inventory' }, - { uid: '1003', url: '/graph/settings', title: 'PMM Settings' }, - ]; - } else { - return <>; - } - } + const resolvedLinks = useResolvedLinks({ dashboardUID, link }); if (!resolvedLinks || resolveLinks.length === 0) { return null; diff --git a/public/app/percona/backup/components/AddBackupPage/AddBackupPage.tsx b/public/app/percona/backup/components/AddBackupPage/AddBackupPage.tsx index c61f88222e492..cb42664af42a7 100644 --- a/public/app/percona/backup/components/AddBackupPage/AddBackupPage.tsx +++ b/public/app/percona/backup/components/AddBackupPage/AddBackupPage.tsx @@ -192,7 +192,13 @@ const AddBackupPage: FC = () => { ); return ( - +
{ @@ -117,10 +118,11 @@ export const PerconaBootstrapper = ({ onReady }: PerconaBootstrapperProps) => { <> {isSignedIn && } - + {!isPmmNavEnabled() && } {updateAvailable && showUpdateModal && !isLoadingUpdates ? ( ) : ( + !isPmmNavEnabled() && isSignedIn && showTour && ( diff --git a/public/app/percona/shared/components/PerconaBootstrapper/PerconaNavigation/PerconaNavigation.constants.ts b/public/app/percona/shared/components/PerconaBootstrapper/PerconaNavigation/PerconaNavigation.constants.ts index 684f71501c729..21ccf05b8d65e 100644 --- a/public/app/percona/shared/components/PerconaBootstrapper/PerconaNavigation/PerconaNavigation.constants.ts +++ b/public/app/percona/shared/components/PerconaBootstrapper/PerconaNavigation/PerconaNavigation.constants.ts @@ -8,6 +8,14 @@ export const WEIGHTS = { config: -900, }; +export const PMM_BACKUP_ADD_EDIT: NavModelItem = { + id: 'backup-add-edit', + text: 'Create backup', + url: `${config.appSubUrl}/backup/new`, + hideFromBreadcrumbs: true, + isCreateAction: true, +}; + export const PMM_BACKUP_PAGE: NavModelItem = { id: 'backup', icon: 'history', @@ -36,6 +44,7 @@ export const PMM_BACKUP_PAGE: NavModelItem = { text: 'Storage Locations', url: `${config.appSubUrl}/backup/locations`, }, + PMM_BACKUP_ADD_EDIT, ], }; diff --git a/public/app/percona/shared/components/PerconaBootstrapper/PerconaUpdateVersion/PerconaUpdateVersion.tsx b/public/app/percona/shared/components/PerconaBootstrapper/PerconaUpdateVersion/PerconaUpdateVersion.tsx index 94c7f36795ace..140e98a41773d 100644 --- a/public/app/percona/shared/components/PerconaBootstrapper/PerconaUpdateVersion/PerconaUpdateVersion.tsx +++ b/public/app/percona/shared/components/PerconaBootstrapper/PerconaUpdateVersion/PerconaUpdateVersion.tsx @@ -10,11 +10,13 @@ import { } from 'app/percona/shared/core/reducers/updates'; import { setSnoozedVersion } from 'app/percona/shared/core/reducers/user/user'; import { getPerconaSettings, getPerconaUser, getUpdatesInfo } from 'app/percona/shared/core/selectors'; +import { isPmmNavEnabled } from 'app/percona/shared/helpers/plugin'; import { useAppDispatch } from 'app/store/store'; import { useSelector } from 'app/types'; import { Messages } from './PerconaUpdateVersion.constants'; import { getStyles } from './PerconaUpdateVersion.styles'; +import { locationService } from '@grafana/runtime'; const PerconaUpdateVersion = () => { const { updateAvailable, installed, latest, changeLogs, showUpdateModal, latestNewsUrl } = @@ -52,7 +54,12 @@ const PerconaUpdateVersion = () => { const onUpdateClick = () => { dispatch(setShowUpdateModal(false)); - window.location.assign(PMM_UPDATES_LINK.url!); + + if (isPmmNavEnabled()) { + locationService.push(PMM_UPDATES_LINK.url!); + } else { + window.location.assign(PMM_UPDATES_LINK.url!); + } }; return ( diff --git a/public/app/percona/shared/helpers/plugin.ts b/public/app/percona/shared/helpers/plugin.ts new file mode 100644 index 0000000000000..06d635ef76bf4 --- /dev/null +++ b/public/app/percona/shared/helpers/plugin.ts @@ -0,0 +1,3 @@ +import { config } from '@grafana/runtime'; + +export const isPmmNavEnabled = () => !!config.apps['pmm-compat-app']?.preload; diff --git a/public/app/plugins/panel/pmm-update/UpdatePanel.tsx b/public/app/plugins/panel/pmm-update/UpdatePanel.tsx index 3941e608c4b9a..e773e3eeb4e7c 100644 --- a/public/app/plugins/panel/pmm-update/UpdatePanel.tsx +++ b/public/app/plugins/panel/pmm-update/UpdatePanel.tsx @@ -4,6 +4,7 @@ import { Button, Spinner } from '@grafana/ui'; import { PMM_UPDATES_LINK } from 'app/percona/shared/components/PerconaBootstrapper/PerconaNavigation'; import { checkUpdatesAction } from 'app/percona/shared/core/reducers/updates'; import { getPerconaUser, getPerconaSettings, getUpdatesInfo } from 'app/percona/shared/core/selectors'; +import { isPmmNavEnabled } from 'app/percona/shared/helpers/plugin'; import { useAppDispatch } from 'app/store/store'; import { useSelector } from 'app/types'; @@ -11,6 +12,7 @@ import { Messages } from './UpdatePanel.messages'; import { styles } from './UpdatePanel.styles'; import { formatDateWithTime } from './UpdatePanel.utils'; import { AvailableUpdate, CurrentVersion, InfoBox, LastCheck } from './components'; +import { locationService } from '@grafana/runtime'; export const UpdatePanel: FC = () => { const isOnline = navigator.onLine; @@ -38,7 +40,11 @@ export const UpdatePanel: FC = () => { }; const handleOpenUpdates = () => { - window.location.assign(PMM_UPDATES_LINK.url!); + if (isPmmNavEnabled()) { + locationService.push(PMM_UPDATES_LINK.url!); + } else { + window.location.assign(PMM_UPDATES_LINK.url!); + } }; return (