{onClick ? (
diff --git a/public/app/features/logs/components/panel/LogList.tsx b/public/app/features/logs/components/panel/LogList.tsx
index 9e42cb73214..ae8234bd060 100644
--- a/public/app/features/logs/components/panel/LogList.tsx
+++ b/public/app/features/logs/components/panel/LogList.tsx
@@ -1,12 +1,26 @@
+import { css } from '@emotion/css';
import { debounce } from 'lodash';
-import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
+import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { VariableSizeList } from 'react-window';
-import { AbsoluteTimeRange, CoreApp, EventBus, LogRowModel, LogsSortOrder, TimeRange } from '@grafana/data';
-import { useTheme2 } from '@grafana/ui';
+import {
+ AbsoluteTimeRange,
+ CoreApp,
+ DataFrame,
+ EventBus,
+ Field,
+ LinkModel,
+ LogRowModel,
+ LogsSortOrder,
+ TimeRange,
+} from '@grafana/data';
+import { PopoverContent, useTheme2 } from '@grafana/ui';
import { InfiniteScroll } from './InfiniteScroll';
-import { preProcessLogs, LogListModel } from './processing';
+import { getGridTemplateColumns } from './LogLine';
+import { GetRowContextQueryFn } from './LogLineMenu';
+import { LogListContext } from './LogListContext';
+import { preProcessLogs, LogListModel, calculateFieldDimensions, LogFieldDimension } from './processing';
import {
getLogLineSize,
init as initVirtualization,
@@ -15,14 +29,26 @@ import {
storeLogLineSize,
} from './virtualization';
+export type GetFieldLinksFn = (field: Field, rowIndex: number, dataFrame: DataFrame) => Array
>;
+
interface Props {
app: CoreApp;
- logs: LogRowModel[];
containerElement: HTMLDivElement;
+ displayedFields: string[];
eventBus: EventBus;
forceEscape?: boolean;
+ getFieldLinks?: GetFieldLinksFn;
+ getRowContextQuery?: GetRowContextQueryFn;
initialScrollPosition?: 'top' | 'bottom';
loadMore?: (range: AbsoluteTimeRange) => void;
+ logs: LogRowModel[];
+ logSupportsContext?: (row: LogRowModel) => boolean;
+ onPermalinkClick?: (row: LogRowModel) => Promise;
+ onPinLine?: (row: LogRowModel) => void;
+ onOpenContext?: (row: LogRowModel, onClose: () => void) => void;
+ onUnpinLine?: (row: LogRowModel) => void;
+ pinLineButtonTooltipTitle?: PopoverContent;
+ pinnedLogs?: string[];
showTime: boolean;
sortOrder: LogsSortOrder;
timeRange: TimeRange;
@@ -33,8 +59,10 @@ interface Props {
export const LogList = ({
app,
containerElement,
+ displayedFields = [],
eventBus,
forceEscape = false,
+ getFieldLinks,
initialScrollPosition = 'top',
loadMore,
logs,
@@ -43,6 +71,7 @@ export const LogList = ({
timeRange,
timeZone,
wrapLogMessage,
+ ...logListContext
}: Props) => {
const [processedLogs, setProcessedLogs] = useState([]);
const [listHeight, setListHeight] = useState(
@@ -52,6 +81,11 @@ export const LogList = ({
const listRef = useRef(null);
const widthRef = useRef(containerElement.clientWidth);
const scrollRef = useRef(null);
+ const dimensions = useMemo(
+ () => (wrapLogMessage ? [] : calculateFieldDimensions(processedLogs, displayedFields)),
+ [displayedFields, processedLogs, wrapLogMessage]
+ );
+ const styles = getStyles(dimensions, { showTime });
useEffect(() => {
initVirtualization(theme);
@@ -65,9 +99,11 @@ export const LogList = ({
}, [eventBus, logs.length]);
useEffect(() => {
- setProcessedLogs(preProcessLogs(logs, { wrap: wrapLogMessage, escape: forceEscape, order: sortOrder, timeZone }));
+ setProcessedLogs(
+ preProcessLogs(logs, { getFieldLinks, wrap: wrapLogMessage, escape: forceEscape, order: sortOrder, timeZone })
+ );
listRef.current?.resetAfterIndex(0);
- }, [forceEscape, logs, sortOrder, timeZone, wrapLogMessage]);
+ }, [forceEscape, getFieldLinks, logs, sortOrder, timeZone, wrapLogMessage]);
useEffect(() => {
const handleResize = debounce(() => {
@@ -109,38 +145,57 @@ export const LogList = ({
}
return (
-
- {({ getItemKey, itemCount, onItemsRendered, Renderer }) => (
-
- {Renderer}
-
- )}
-
+
+
+ {({ getItemKey, itemCount, onItemsRendered, Renderer }) => (
+
+ {Renderer}
+
+ )}
+
+
);
};
+function getStyles(dimensions: LogFieldDimension[], { showTime }: { showTime: boolean }) {
+ const columns = showTime ? dimensions : dimensions.filter((_, index) => index > 0);
+ return {
+ logList: css({
+ '& .unwrapped-log-line': {
+ display: 'grid',
+ gridTemplateColumns: getGridTemplateColumns(columns),
+ },
+ }),
+ };
+}
+
function handleScrollToEvent(event: ScrollToLogsEvent, logsCount: number, list: VariableSizeList | null) {
if (event.payload.scrollTo === 'top') {
list?.scrollTo(0);
diff --git a/public/app/features/logs/components/panel/LogListContext.test.tsx b/public/app/features/logs/components/panel/LogListContext.test.tsx
new file mode 100644
index 00000000000..3505c554797
--- /dev/null
+++ b/public/app/features/logs/components/panel/LogListContext.test.tsx
@@ -0,0 +1,46 @@
+import { renderHook } from '@testing-library/react';
+import { ReactNode } from 'react';
+
+import { createLogLine } from '../__mocks__/logRow';
+
+import { useLogListContextData, useLogListContext, useLogIsPinned, LogListContext } from './LogListContext';
+
+const log = createLogLine({ rowId: 'yep' });
+const value = {
+ getRowContextQuery: jest.fn(),
+ logSupportsContext: jest.fn(),
+ onPermalinkClick: jest.fn(),
+ onPinLine: jest.fn(),
+ onOpenContext: jest.fn(),
+ onUnpinLine: jest.fn(),
+ pinLineButtonTooltipTitle: 'test',
+ pinnedLogs: ['yep'],
+};
+const wrapper = ({ children }: { children: ReactNode }) => (
+ {children}
+);
+
+test('Provides the Log List Context data', () => {
+ const { result } = renderHook(() => useLogListContext(), { wrapper });
+
+ expect(result.current).toEqual(value);
+});
+
+test('Allows to access context attributes', () => {
+ const { result } = renderHook(() => useLogListContextData('pinnedLogs'), { wrapper });
+
+ expect(result.current).toEqual(value.pinnedLogs);
+});
+
+test('Allows to tell if a log is pinned', () => {
+ const { result } = renderHook(() => useLogIsPinned(log), { wrapper });
+
+ expect(result.current).toBe(true);
+});
+
+test('Allows to tell if a log is pinned', () => {
+ const otherLog = createLogLine({ rowId: 'nope' });
+ const { result } = renderHook(() => useLogIsPinned(otherLog), { wrapper });
+
+ expect(result.current).toBe(false);
+});
diff --git a/public/app/features/logs/components/panel/LogListContext.tsx b/public/app/features/logs/components/panel/LogListContext.tsx
new file mode 100644
index 00000000000..9f654cf4dce
--- /dev/null
+++ b/public/app/features/logs/components/panel/LogListContext.tsx
@@ -0,0 +1,33 @@
+import { createContext, useContext } from 'react';
+
+import { LogRowModel } from '@grafana/data';
+import { PopoverContent } from '@grafana/ui';
+
+import { GetRowContextQueryFn } from './LogLineMenu';
+
+export interface LogListContextData {
+ getRowContextQuery?: GetRowContextQueryFn;
+ logSupportsContext?: (row: LogRowModel) => boolean;
+ onPermalinkClick?: (row: LogRowModel) => Promise;
+ onPinLine?: (row: LogRowModel) => void;
+ onOpenContext?: (row: LogRowModel, onClose: () => void) => void;
+ onUnpinLine?: (row: LogRowModel) => void;
+ pinLineButtonTooltipTitle?: PopoverContent;
+ pinnedLogs?: string[];
+}
+
+export const LogListContext = createContext({});
+
+export const useLogListContextData = (key: keyof LogListContextData) => {
+ const data: LogListContextData = useContext(LogListContext);
+ return data[key];
+};
+
+export const useLogListContext = (): LogListContextData => {
+ return useContext(LogListContext);
+};
+
+export const useLogIsPinned = (log: LogRowModel) => {
+ const { pinnedLogs } = useContext(LogListContext);
+ return pinnedLogs?.some((logId) => logId === log.rowId);
+};
diff --git a/public/app/features/logs/components/panel/processing.ts b/public/app/features/logs/components/panel/processing.ts
index 77f525614b5..ef2c2c81b12 100644
--- a/public/app/features/logs/components/panel/processing.ts
+++ b/public/app/features/logs/components/panel/processing.ts
@@ -1,22 +1,28 @@
-import { dateTimeFormat, LogRowModel, LogsSortOrder } from '@grafana/data';
+import { dateTimeFormat, LogLevel, LogRowModel, LogsSortOrder } from '@grafana/data';
import { escapeUnescapedString, sortLogRows } from '../../utils';
+import { LOG_LINE_BODY_FIELD_NAME } from '../LogDetailsBody';
+import { FieldDef, getAllFields } from '../logParser';
+import { getDisplayedFieldValue } from './LogLine';
+import { GetFieldLinksFn } from './LogList';
import { measureTextWidth } from './virtualization';
export interface LogListModel extends LogRowModel {
body: string;
+ displayLevel: string;
+ fields: FieldDef[];
timestamp: string;
- dimensions: LogDimensions;
}
-export interface LogDimensions {
- timestampWidth: number;
- levelWidth: number;
+export interface LogFieldDimension {
+ field: string;
+ width: number;
}
-interface PreProcessOptions {
+export interface PreProcessOptions {
escape: boolean;
+ getFieldLinks?: GetFieldLinksFn;
order: LogsSortOrder;
timeZone: string;
wrap: boolean;
@@ -24,19 +30,23 @@ interface PreProcessOptions {
export const preProcessLogs = (
logs: LogRowModel[],
- { escape, order, timeZone, wrap }: PreProcessOptions
+ { escape, getFieldLinks, order, timeZone, wrap }: PreProcessOptions
): LogListModel[] => {
const orderedLogs = sortLogRows(logs, order);
- return orderedLogs.map((log) => preProcessLog(log, { wrap, escape, timeZone, expanded: false }));
+ return orderedLogs.map((log) => preProcessLog(log, { escape, expanded: false, getFieldLinks, timeZone, wrap }));
};
interface PreProcessLogOptions {
escape: boolean;
expanded: boolean; // Not yet implemented
+ getFieldLinks?: GetFieldLinksFn;
timeZone: string;
wrap: boolean;
}
-const preProcessLog = (log: LogRowModel, { escape, expanded, timeZone, wrap }: PreProcessLogOptions): LogListModel => {
+const preProcessLog = (
+ log: LogRowModel,
+ { escape, expanded, getFieldLinks, timeZone, wrap }: PreProcessLogOptions
+): LogListModel => {
let body = log.entry;
const timestamp = dateTimeFormat(log.timeEpochMs, {
timeZone,
@@ -54,10 +64,65 @@ const preProcessLog = (log: LogRowModel, { escape, expanded, timeZone, wrap }: P
return {
...log,
body,
+ displayLevel: logLevelToDisplayLevel(log.logLevel),
+ fields: getAllFields(log, getFieldLinks),
timestamp,
- dimensions: {
- timestampWidth: measureTextWidth(timestamp),
- levelWidth: measureTextWidth(log.logLevel),
- },
};
};
+
+function logLevelToDisplayLevel(level = '') {
+ switch (level) {
+ case LogLevel.critical:
+ return 'crit';
+ case LogLevel.warning:
+ return 'warn';
+ case LogLevel.unknown:
+ return '';
+ default:
+ return level;
+ }
+}
+
+export const calculateFieldDimensions = (logs: LogListModel[], displayedFields: string[] = []) => {
+ if (!logs.length) {
+ return [];
+ }
+ let timestampWidth = 0;
+ let levelWidth = 0;
+ const fieldWidths: Record = {};
+ for (let i = 0; i < logs.length; i++) {
+ let width = measureTextWidth(logs[i].timestamp);
+ if (width > timestampWidth) {
+ timestampWidth = Math.round(width);
+ }
+ width = measureTextWidth(logs[i].displayLevel);
+ if (width > levelWidth) {
+ levelWidth = Math.round(width);
+ }
+ for (const field of displayedFields) {
+ width = measureTextWidth(getDisplayedFieldValue(field, logs[i]));
+ fieldWidths[field] = !fieldWidths[field] || width > fieldWidths[field] ? Math.round(width) : fieldWidths[field];
+ }
+ }
+ const dimensions: LogFieldDimension[] = [
+ {
+ field: 'timestamp',
+ width: timestampWidth,
+ },
+ {
+ field: 'level',
+ width: levelWidth,
+ },
+ ];
+ for (const field in fieldWidths) {
+ // Skip the log line when it's a displayed field
+ if (field === LOG_LINE_BODY_FIELD_NAME) {
+ continue;
+ }
+ dimensions.push({
+ field,
+ width: fieldWidths[field],
+ });
+ }
+ return dimensions;
+};
diff --git a/public/app/features/logs/components/panel/virtualization.ts b/public/app/features/logs/components/panel/virtualization.ts
index 201cc4c82ed..873e7555c58 100644
--- a/public/app/features/logs/components/panel/virtualization.ts
+++ b/public/app/features/logs/components/panel/virtualization.ts
@@ -1,5 +1,6 @@
import { BusEventWithPayload, GrafanaTheme2 } from '@grafana/data';
+import { getDisplayedFieldValue } from './LogLine';
import { LogListModel } from './processing';
let ctx: CanvasRenderingContext2D | null = null;
@@ -7,6 +8,12 @@ let gridSize = 8;
let paddingBottom = gridSize * 0.75;
let lineHeight = 22;
let measurementMode: 'canvas' | 'dom' = 'canvas';
+const iconWidth = 24;
+
+// Controls the space between fields in the log line, timestamp, level, displayed fields, and log line body
+export const FIELD_GAP_MULTIPLIER = 1.5;
+
+export const getLineHeight = () => lineHeight;
export function init(theme: GrafanaTheme2) {
const font = `${theme.typography.fontSize}px ${theme.typography.fontFamilyMonospace}`;
@@ -146,6 +153,7 @@ interface DisplayOptions {
export function getLogLineSize(
logs: LogListModel[],
container: HTMLDivElement | null,
+ displayedFields: string[],
{ wrap, showTime }: DisplayOptions,
index: number
) {
@@ -160,15 +168,26 @@ export function getLogLineSize(
if (storedSize) {
return storedSize;
}
- const gap = gridSize;
+
+ let textToMeasure = '';
+ const gap = gridSize * FIELD_GAP_MULTIPLIER;
let optionsWidth = 0;
if (showTime) {
- optionsWidth += logs[index].dimensions.timestampWidth + gap;
+ optionsWidth += gap;
+ textToMeasure += logs[index].timestamp;
}
if (logs[index].logLevel) {
- optionsWidth += logs[index].dimensions.levelWidth + gap;
+ optionsWidth += gap;
+ textToMeasure += logs[index].logLevel;
}
- const { height } = measureTextHeight(logs[index].body, getLogContainerWidth(container), optionsWidth);
+ for (const field of displayedFields) {
+ textToMeasure = getDisplayedFieldValue(field, logs[index]) + textToMeasure;
+ }
+ if (!displayedFields.length) {
+ textToMeasure += logs[index].body;
+ }
+
+ const { height } = measureTextHeight(textToMeasure, getLogContainerWidth(container), optionsWidth);
return height;
}
@@ -177,7 +196,7 @@ export function hasUnderOrOverflow(element: HTMLDivElement, calculatedHeight?: n
if (element.scrollHeight > height) {
return element.scrollHeight;
}
- const child = element.firstChild;
+ const child = element.children[1];
if (child instanceof HTMLDivElement && child.clientHeight < height) {
return child.clientHeight;
}
@@ -187,7 +206,7 @@ export function hasUnderOrOverflow(element: HTMLDivElement, calculatedHeight?: n
const scrollBarWidth = getScrollbarWidth();
export function getLogContainerWidth(container: HTMLDivElement) {
- return container.clientWidth - scrollBarWidth;
+ return container.clientWidth - scrollBarWidth - iconWidth;
}
export function getScrollbarWidth() {
diff --git a/public/app/features/logs/utils.ts b/public/app/features/logs/utils.ts
index c4a19c0aa6c..b55e2f7a3a2 100644
--- a/public/app/features/logs/utils.ts
+++ b/public/app/features/logs/utils.ts
@@ -1,4 +1,5 @@
import { countBy, chain } from 'lodash';
+import { MouseEvent } from 'react';
import {
LogLevel,
@@ -15,9 +16,14 @@ import {
LogsVolumeType,
NumericLogLevel,
getFieldDisplayName,
+ getDefaultTimeRange,
+ locationUtil,
+ urlUtil,
} from '@grafana/data';
+import { getConfig } from 'app/core/config';
import { getDataframeFields } from './components/logParser';
+import { GetRowContextQueryFn } from './components/panel/LogLineMenu';
/**
* Returns the log level of a log line.
@@ -303,6 +309,34 @@ export const copyText = async (text: string, buttonRef: React.MutableRefObject,
+ row: LogRowModel,
+ getRowContextQuery: GetRowContextQueryFn | undefined,
+ onOpenContext: (row: LogRowModel) => void
+) {
+ // if ctrl or meta key is pressed, open query in new Explore tab
+ if (getRowContextQuery && (event.nativeEvent.ctrlKey || event.nativeEvent.metaKey || event.nativeEvent.shiftKey)) {
+ const win = window.open('about:blank');
+ // for this request we don't want to use the cached filters from a context provider, but always want to refetch and clear
+ const query = await getRowContextQuery(row, undefined, false);
+ if (query && win) {
+ const url = urlUtil.renderUrl(locationUtil.assureBaseUrl(`${getConfig().appSubUrl}explore`), {
+ left: JSON.stringify({
+ datasource: query.datasource,
+ queries: [query],
+ range: getDefaultTimeRange(),
+ }),
+ });
+ win.location = url;
+
+ return;
+ }
+ win?.close();
+ }
+ onOpenContext(row);
+}
+
export function getLogLevelInfo(dataFrame: DataFrame, allDataFrames: DataFrame[]) {
const fieldCache = new FieldCache(dataFrame);
const timeField = fieldCache.getFirstFieldOfType(FieldType.time);
diff --git a/public/app/features/playlist/PlaylistSrv.test.ts b/public/app/features/playlist/PlaylistSrv.test.ts
index 687b5e39156..3620f3a1e02 100644
--- a/public/app/features/playlist/PlaylistSrv.test.ts
+++ b/public/app/features/playlist/PlaylistSrv.test.ts
@@ -1,4 +1,3 @@
-// @ts-ignore
import { Store } from 'redux';
import configureMockStore from 'redux-mock-store';
@@ -139,4 +138,29 @@ describe('PlaylistSrv', () => {
expect((srv as any).validPlaylistUrl).toBe('/url/to/bbb');
expect(srv.state.isPlaying).toBe(true);
});
+
+ it('should replace playlist start page in history when starting playlist', async () => {
+ // Start at playlists page
+ locationService.push('/playlists');
+
+ // Navigate to playlist start page
+ locationService.push('/playlists/play/foo');
+
+ // Start the playlist
+ await srv.start('foo');
+
+ // Get history entries
+ const history = locationService.getHistory();
+ const entries = (history as unknown as { entries: Location[] }).entries;
+
+ // The current entry should be the first dashboard
+ expect(entries[entries.length - 1].pathname).toBe('/url/to/aaa');
+
+ // The previous entry should be the playlists page, not the start page
+ expect(entries[entries.length - 2].pathname).toBe('/playlists');
+
+ // Verify the start page (/playlists/play/foo) is not in history
+ const hasStartPage = entries.some((entry: { pathname: string }) => entry.pathname === '/playlists/play/foo');
+ expect(hasStartPage).toBe(false);
+ });
});
diff --git a/public/app/features/playlist/PlaylistSrv.ts b/public/app/features/playlist/PlaylistSrv.ts
index d11f817a369..99b839c857c 100644
--- a/public/app/features/playlist/PlaylistSrv.ts
+++ b/public/app/features/playlist/PlaylistSrv.ts
@@ -39,6 +39,28 @@ export class PlaylistSrv extends StateManagerBase {
this.api = getPlaylistAPI();
}
+ private navigateToDashboard(replaceHistoryEntry = false) {
+ const url = this.urls[this.index];
+ const queryParams = locationService.getSearchObject();
+ const filteredParams = pickBy(queryParams, (value: unknown, key: string) => queryParamsToPreserve[key]);
+ const nextDashboardUrl = locationUtil.stripBaseFromUrl(url);
+
+ this.index++;
+ this.validPlaylistUrl = nextDashboardUrl;
+ this.nextTimeoutId = setTimeout(() => this.next(), this.interval);
+
+ const urlWithParams = nextDashboardUrl + '?' + urlUtil.toUrlParams(filteredParams);
+
+ // When starting the playlist from the PlaylistStartPage component using the playlist URL, we want to replace the
+ // history entry to support the back button
+ // When starting the playlist from the playlist modal, we want to push a new history entry
+ if (replaceHistoryEntry) {
+ locationService.getHistory().replace(urlWithParams);
+ } else {
+ locationService.push(urlWithParams);
+ }
+ }
+
next() {
clearTimeout(this.nextTimeoutId);
@@ -55,16 +77,7 @@ export class PlaylistSrv extends StateManagerBase {
this.index = 0;
}
- const url = this.urls[this.index];
- const queryParams = locationService.getSearchObject();
- const filteredParams = pickBy(queryParams, (value: unknown, key: string) => queryParamsToPreserve[key]);
- const nextDashboardUrl = locationUtil.stripBaseFromUrl(url);
-
- this.index++;
- this.validPlaylistUrl = nextDashboardUrl;
- this.nextTimeoutId = setTimeout(() => this.next(), this.interval);
-
- locationService.push(nextDashboardUrl + '?' + urlUtil.toUrlParams(filteredParams));
+ this.navigateToDashboard();
}
prev() {
@@ -115,7 +128,10 @@ export class PlaylistSrv extends StateManagerBase {
this.urls = urls;
this.setState({ isPlaying: true });
- this.next();
+
+ // Replace current history entry with first dashboard instead of pushing
+ // this is to avoid the back button to go back to the playlist start page which causes a redirection
+ this.navigateToDashboard(true);
return;
}
diff --git a/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx b/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx
index 6af5c5904ad..9824a5c998a 100644
--- a/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx
+++ b/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx
@@ -112,7 +112,7 @@ export function InstallControlsButton({
const onUpdate = async () => {
reportInteraction(PLUGIN_UPDATE_INTERACTION_EVENT_NAME, trackingProps);
- await install(plugin.id, latestCompatibleVersion?.version, true);
+ await install(plugin.id, latestCompatibleVersion?.version, PluginStatus.UPDATE);
if (!errorInstalling) {
appEvents.emit(AppEvents.alertSuccess, [`Updated ${plugin.name}`]);
}
@@ -126,24 +126,28 @@ export function InstallControlsButton({
uninstallTitle = 'Preinstalled plugin. Remove from Grafana config before uninstalling.';
}
+ const uninstallControls = (
+ <>
+
+
+ >
+ );
+
if (pluginStatus === PluginStatus.UNINSTALL) {
return (
- <>
-
-
-
-
- >
+
+ {uninstallControls}
+
);
}
@@ -162,9 +166,7 @@ export function InstallControlsButton({
{isInstalling ? 'Updating' : 'Update'}
)}
-
+ {uninstallControls}
);
}
diff --git a/public/app/features/plugins/admin/components/UpdateAllModal.tsx b/public/app/features/plugins/admin/components/UpdateAllModal.tsx
index f12113499b9..7830b1b253e 100644
--- a/public/app/features/plugins/admin/components/UpdateAllModal.tsx
+++ b/public/app/features/plugins/admin/components/UpdateAllModal.tsx
@@ -5,7 +5,7 @@ import { ConfirmModal } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { useInstall, useInstallStatus } from '../state/hooks';
-import { CatalogPlugin } from '../types';
+import { CatalogPlugin, PluginStatus } from '../types';
import { UpdateModalBody } from './UpdateAllModalBody';
const PLUGINS_UPDATE_ALL_INTERACTION_EVENT_NAME = 'plugins_update_all_clicked';
@@ -100,13 +100,13 @@ export const UpdateAllModal = ({ isOpen, onDismiss, isLoading, plugins }: Props)
if (config.pluginAdminExternalManageEnabled) {
for (let plugin of plugins) {
if (selectedPlugins?.has(plugin.id)) {
- await install(plugin.id, plugin.latestVersion, true);
+ await install(plugin.id, plugin.latestVersion, PluginStatus.UPDATE);
}
}
} else {
plugins.forEach((plugin) => {
if (selectedPlugins?.has(plugin.id)) {
- install(plugin.id, plugin.latestVersion, true);
+ install(plugin.id, plugin.latestVersion, PluginStatus.UPDATE);
}
});
}
diff --git a/public/app/features/plugins/admin/components/VersionInstallButton.test.tsx b/public/app/features/plugins/admin/components/VersionInstallButton.test.tsx
index 38511a2dfea..16ec1d3ca70 100644
--- a/public/app/features/plugins/admin/components/VersionInstallButton.test.tsx
+++ b/public/app/features/plugins/admin/components/VersionInstallButton.test.tsx
@@ -177,6 +177,46 @@ describe('VersionInstallButton', () => {
);
expect(screen.getByText('Downgrade')).not.toBeVisible();
});
+
+ it('should show the installation button if invalid semver version is provided', () => {
+ const version: Version = {
+ version: '1.0.a',
+ createdAt: '',
+ isCompatible: false,
+ grafanaDependency: null,
+ };
+ const installedVersion = '1.0.1';
+ renderWithStore(
+ {}}
+ />
+ );
+ expect(screen.getByText('Install')).toBeInTheDocument();
+ });
+
+ it('should show the installation button if invalid semver installed version is provided', () => {
+ const version: Version = {
+ version: '1.0.0',
+ createdAt: '',
+ isCompatible: false,
+ grafanaDependency: null,
+ };
+ const installedVersion = '1.0.a';
+ renderWithStore(
+ {}}
+ />
+ );
+ expect(screen.getByText('Install')).toBeInTheDocument();
+ });
});
function renderWithStore(component: JSX.Element) {
diff --git a/public/app/features/plugins/admin/components/VersionInstallButton.tsx b/public/app/features/plugins/admin/components/VersionInstallButton.tsx
index 1da8bfe01f7..7792ffc06a8 100644
--- a/public/app/features/plugins/admin/components/VersionInstallButton.tsx
+++ b/public/app/features/plugins/admin/components/VersionInstallButton.tsx
@@ -1,6 +1,6 @@
import { css } from '@emotion/css';
import { useEffect, useState } from 'react';
-import { gt } from 'semver';
+import { gt, valid } from 'semver';
import { GrafanaTheme2 } from '@grafana/data';
import { config, reportInteraction } from '@grafana/runtime';
@@ -9,11 +9,10 @@ import { t } from 'app/core/internationalization';
import { isPreinstalledPlugin } from '../helpers';
import { useInstall } from '../state/hooks';
-import { Version } from '../types';
+import { PluginStatus, Version } from '../types';
const PLUGINS_VERSION_PAGE_UPGRADE_INTERACTION_EVENT_NAME = 'plugins_upgrade_clicked';
const PLUGINS_VERSION_PAGE_CHANGE_INTERACTION_EVENT_NAME = 'plugins_downgrade_clicked';
-
interface Props {
pluginId: string;
version: Version;
@@ -38,7 +37,7 @@ export const VersionInstallButton = ({
const [isModalOpen, setIsModalOpen] = useState(false);
const styles = useStyles2(getStyles);
- const isDowngrade = installedVersion && gt(installedVersion, version.version);
+ const installState = getInstallState(installedVersion, version.version);
useEffect(() => {
if (installedVersion === version.version) {
@@ -61,7 +60,7 @@ export const VersionInstallButton = ({
schema_version: '1.0.0',
};
- if (!installedVersion || gt(version.version, installedVersion)) {
+ if (installState === PluginStatus.UPDATE) {
reportInteraction(PLUGINS_VERSION_PAGE_UPGRADE_INTERACTION_EVENT_NAME, trackProps);
} else {
reportInteraction(PLUGINS_VERSION_PAGE_CHANGE_INTERACTION_EVENT_NAME, {
@@ -70,13 +69,13 @@ export const VersionInstallButton = ({
});
}
- install(pluginId, version.version, true);
+ install(pluginId, version.version, installState);
setIsInstalling(true);
onConfirmInstallation();
};
const onInstallClick = () => {
- if (isDowngrade) {
+ if (installState === PluginStatus.DOWNGRADE) {
setIsModalOpen(true);
} else {
performInstallation();
@@ -91,24 +90,9 @@ export const VersionInstallButton = ({
setIsModalOpen(false);
};
- let label = 'Downgrade';
- let hidden = false;
const isPreinstalled = isPreinstalledPlugin(pluginId);
- if (!installedVersion) {
- label = 'Install';
- } else if (gt(version.version, installedVersion)) {
- label = 'Upgrade';
- if (isPreinstalled.withVersion) {
- // Hide button if the plugin is preinstalled with a specific version
- hidden = true;
- }
- } else {
- if (isPreinstalled.found && Boolean(config.featureToggles.preinstallAutoUpdate)) {
- // Hide the downgrade button if the plugin is preinstalled since it will be auto-updated
- hidden = true;
- }
- }
+ const hidden = getButtonHiddenState(installState, isPreinstalled);
return (
<>
@@ -124,7 +108,8 @@ export const VersionInstallButton = ({
tooltip={tooltip}
tooltipPlacement="bottom-start"
>
- {label} {isInstalling ? : getIcon(label)}
+ {getLabel(installState)}{' '}
+ {isInstalling ? : getIcon(installState)}
;
}
- if (label === 'Upgrade') {
+ if (installState === PluginStatus.UPDATE) {
return ;
}
return '';
}
+function getInstallState(installedVersion?: string, version?: string): PluginStatus {
+ if (!installedVersion || !version || !valid(installedVersion) || !valid(version)) {
+ return PluginStatus.INSTALL;
+ }
+ return gt(installedVersion, version) ? PluginStatus.DOWNGRADE : PluginStatus.UPDATE;
+}
+
+function getButtonHiddenState(installState: PluginStatus, isPreinstalled: { found: boolean; withVersion: boolean }) {
+ // Default state for initial install
+ if (installState === PluginStatus.INSTALL) {
+ return false;
+ }
+
+ // Handle downgrade case
+ if (installState === PluginStatus.DOWNGRADE) {
+ return isPreinstalled.found && Boolean(config.featureToggles.preinstallAutoUpdate);
+ }
+
+ // Handle upgrade case
+ return isPreinstalled.withVersion;
+}
+
const getStyles = (theme: GrafanaTheme2) => ({
spinner: css({
marginLeft: theme.spacing(1),
diff --git a/public/app/features/plugins/admin/state/actions.ts b/public/app/features/plugins/admin/state/actions.ts
index eab93de18fa..e08e7241c42 100644
--- a/public/app/features/plugins/admin/state/actions.ts
+++ b/public/app/features/plugins/admin/state/actions.ts
@@ -19,7 +19,7 @@ import {
} from '../api';
import { STATE_PREFIX } from '../constants';
import { mapLocalToCatalog, mergeLocalsAndRemotes, updatePanels } from '../helpers';
-import { CatalogPlugin, RemotePlugin, LocalPlugin, InstancePlugin, ProvisionedPlugin } from '../types';
+import { CatalogPlugin, RemotePlugin, LocalPlugin, InstancePlugin, ProvisionedPlugin, PluginStatus } from '../types';
// Fetches
export const fetchAll = createAsyncThunk(`${STATE_PREFIX}/fetchAll`, async (_, thunkApi) => {
@@ -188,17 +188,23 @@ export const install = createAsyncThunk<
{
id: string;
version?: string;
- isUpdating?: boolean;
+ installType?: PluginStatus;
}
->(`${STATE_PREFIX}/install`, async ({ id, version, isUpdating = false }, thunkApi) => {
- const changes = isUpdating
- ? { isInstalled: true, installedVersion: version, hasUpdate: false }
- : { isInstalled: true, installedVersion: version };
+>(`${STATE_PREFIX}/install`, async ({ id, version, installType = PluginStatus.INSTALL }, thunkApi) => {
+ const changes: Partial = { isInstalled: true, installedVersion: version };
+
+ if (installType === PluginStatus.UPDATE) {
+ changes.hasUpdate = false;
+ }
+ if (installType === PluginStatus.DOWNGRADE) {
+ changes.hasUpdate = true;
+ }
+
try {
await installPlugin(id, version);
await updatePanels();
- if (isUpdating) {
+ if (installType !== PluginStatus.INSTALL) {
invalidatePluginInCache(id);
}
diff --git a/public/app/features/plugins/admin/state/hooks.ts b/public/app/features/plugins/admin/state/hooks.ts
index da0ba859a3a..457f2f1d73e 100644
--- a/public/app/features/plugins/admin/state/hooks.ts
+++ b/public/app/features/plugins/admin/state/hooks.ts
@@ -4,7 +4,7 @@ import { PluginError, PluginType } from '@grafana/data';
import { useDispatch, useSelector } from 'app/types';
import { sortPlugins, Sorters, isPluginUpdatable } from '../helpers';
-import { CatalogPlugin } from '../types';
+import { CatalogPlugin, PluginStatus } from '../types';
import { fetchAll, fetchDetails, fetchRemotePlugins, install, uninstall, fetchAllLocal, unsetInstall } from './actions';
import {
@@ -64,7 +64,7 @@ export const useGetErrors = (filterByPluginType?: PluginType): PluginError[] =>
export const useInstall = () => {
const dispatch = useDispatch();
- return (id: string, version?: string, isUpdating?: boolean) => dispatch(install({ id, version, isUpdating }));
+ return (id: string, version?: string, installType?: PluginStatus) => dispatch(install({ id, version, installType }));
};
export const useUnsetInstall = () => {
diff --git a/public/app/features/plugins/admin/types.ts b/public/app/features/plugins/admin/types.ts
index f3d5783ae3c..96e1d2451d2 100644
--- a/public/app/features/plugins/admin/types.ts
+++ b/public/app/features/plugins/admin/types.ts
@@ -257,6 +257,7 @@ export enum PluginStatus {
UNINSTALL = 'UNINSTALL',
UPDATE = 'UPDATE',
REINSTALL = 'REINSTALL',
+ DOWNGRADE = 'DOWNGRADE',
}
export enum PluginTabLabels {
diff --git a/public/app/features/visualization/data-hover/DataHoverView.tsx b/public/app/features/visualization/data-hover/DataHoverView.tsx
index adc593a9a14..79785c94eb2 100644
--- a/public/app/features/visualization/data-hover/DataHoverView.tsx
+++ b/public/app/features/visualization/data-hover/DataHoverView.tsx
@@ -23,6 +23,7 @@ export interface Props {
mode?: TooltipDisplayMode | null;
header?: string;
padding?: number;
+ maxHeight?: number;
}
export interface DisplayValue {
@@ -92,7 +93,16 @@ export function getDisplayValuesAndLinks(
return { displayValues, links };
}
-export const DataHoverView = ({ data, rowIndex, columnIndex, sortOrder, mode, header, padding = 0 }: Props) => {
+export const DataHoverView = ({
+ data,
+ rowIndex,
+ columnIndex,
+ sortOrder,
+ mode,
+ header,
+ padding = 0,
+ maxHeight,
+}: Props) => {
const styles = useStyles2(getStyles, padding);
if (!data || rowIndex == null) {
@@ -108,7 +118,7 @@ export const DataHoverView = ({ data, rowIndex, columnIndex, sortOrder, mode, he
const { displayValues, links } = dispValuesAndLinks;
if (header === 'Exemplar') {
- return ;
+ return ;
}
return (
diff --git a/public/app/features/visualization/data-hover/ExemplarHoverView.tsx b/public/app/features/visualization/data-hover/ExemplarHoverView.tsx
index b353b08abba..b2f17d17333 100644
--- a/public/app/features/visualization/data-hover/ExemplarHoverView.tsx
+++ b/public/app/features/visualization/data-hover/ExemplarHoverView.tsx
@@ -11,10 +11,11 @@ export interface Props {
displayValues: DisplayValue[];
links?: LinkModel[];
header?: string;
+ maxHeight?: number;
}
-export const ExemplarHoverView = ({ displayValues, links, header = 'Exemplar' }: Props) => {
- const styles = useStyles2(getStyles);
+export const ExemplarHoverView = ({ displayValues, links, header = 'Exemplar', maxHeight }: Props) => {
+ const styles = useStyles2(getStyles, 0, maxHeight);
const time = displayValues.find((val) => val.name === 'Time');
displayValues = displayValues.filter((val) => val.name !== 'Time'); // time?
@@ -49,7 +50,7 @@ export const ExemplarHoverView = ({ displayValues, links, header = 'Exemplar' }:
);
};
-const getStyles = (theme: GrafanaTheme2, padding = 0) => {
+const getStyles = (theme: GrafanaTheme2, padding = 0, maxHeight?: number) => {
return {
exemplarWrapper: css({
display: 'flex',
@@ -79,6 +80,8 @@ const getStyles = (theme: GrafanaTheme2, padding = 0) => {
gap: 4,
borderTop: `1px solid ${theme.colors.border.medium}`,
padding: theme.spacing(1),
+ overflowY: 'auto',
+ maxHeight: maxHeight,
}),
exemplarFooter: css({
display: 'flex',
diff --git a/public/app/plugins/panel/canvas/components/CanvasTooltip.tsx b/public/app/plugins/panel/canvas/components/CanvasTooltip.tsx
index f2a5959edc6..6234a4a7693 100644
--- a/public/app/plugins/panel/canvas/components/CanvasTooltip.tsx
+++ b/public/app/plugins/panel/canvas/components/CanvasTooltip.tsx
@@ -49,7 +49,7 @@ export const CanvasTooltip = ({ scene }: Props) => {
}
// Retrieve timestamp of the last data point if available
- const timeField = scene.data?.series[0].fields?.find((field) => field.type === FieldType.time);
+ const timeField = scene.data?.series[0]?.fields?.find((field) => field.type === FieldType.time);
const lastTimeValue = timeField?.values[timeField.values.length - 1];
const shouldDisplayTimeContentItem =
timeField && lastTimeValue && element.data.field && getFieldDisplayName(timeField) !== element.data.field;
diff --git a/public/app/plugins/panel/heatmap/HeatmapTooltip.tsx b/public/app/plugins/panel/heatmap/HeatmapTooltip.tsx
index 896dbeb1feb..d15b13cd50c 100644
--- a/public/app/plugins/panel/heatmap/HeatmapTooltip.tsx
+++ b/public/app/plugins/panel/heatmap/HeatmapTooltip.tsx
@@ -56,6 +56,7 @@ export const HeatmapTooltip = (props: HeatmapTooltipProps) => {
rowIndex={props.dataIdxs[2]}
header={'Exemplar'}
padding={8}
+ maxHeight={props.maxHeight}
/>
);
}
diff --git a/public/app/plugins/panel/heatmap/module.tsx b/public/app/plugins/panel/heatmap/module.tsx
index 6ec45e27d9a..ff3dfb38214 100644
--- a/public/app/plugins/panel/heatmap/module.tsx
+++ b/public/app/plugins/panel/heatmap/module.tsx
@@ -1,4 +1,4 @@
-import { FieldConfigProperty, FieldType, identityOverrideProcessor, PanelPlugin } from '@grafana/data';
+import { DataFrame, FieldConfigProperty, FieldType, identityOverrideProcessor, PanelPlugin } from '@grafana/data';
import { config } from '@grafana/runtime';
import {
AxisPlacement,
@@ -442,7 +442,9 @@ export const plugin = new PanelPlugin(HeatmapPanel)
settings: {
integer: true,
},
- showIf: (options) => options.tooltip?.mode === TooltipDisplayMode.Multi,
+ showIf: (options: Options, data: DataFrame[] | undefined, annotations: DataFrame[] | undefined) =>
+ options.tooltip?.mode === TooltipDisplayMode.Multi ||
+ annotations?.some((df) => df.meta?.custom?.resultType === 'exemplar'),
});
category = ['Legend'];
@@ -459,6 +461,8 @@ export const plugin = new PanelPlugin(HeatmapPanel)
name: 'Color',
defaultValue: defaultOptions.exemplars.color,
category,
+ showIf: (options: Options, data: DataFrame[] | undefined, annotations: DataFrame[] | undefined) =>
+ annotations?.some((df) => df.meta?.custom?.resultType === 'exemplar'),
});
})
.setSuggestionsSupplier(new HeatmapSuggestionsSupplier())
diff --git a/public/app/plugins/panel/logs-new/LogsPanel.tsx b/public/app/plugins/panel/logs-new/LogsPanel.tsx
index 44dea0e5f54..9b0b26b4c23 100644
--- a/public/app/plugins/panel/logs-new/LogsPanel.tsx
+++ b/public/app/plugins/panel/logs-new/LogsPanel.tsx
@@ -102,6 +102,7 @@ export const LogsPanel = ({
)}
{((canEditThresholds && onThresholdsChange) || showThresholds) && (
diff --git a/public/app/plugins/panel/timeseries/plugins/ExemplarMarker.tsx b/public/app/plugins/panel/timeseries/plugins/ExemplarMarker.tsx
index 54e79e06743..f41e5e3af7a 100644
--- a/public/app/plugins/panel/timeseries/plugins/ExemplarMarker.tsx
+++ b/public/app/plugins/panel/timeseries/plugins/ExemplarMarker.tsx
@@ -29,6 +29,7 @@ interface ExemplarMarkerProps {
exemplarColor?: string;
clickedExemplarFieldIndex: DataFrameFieldIndex | undefined;
setClickedExemplarFieldIndex: React.Dispatch;
+ maxHeight?: number;
}
export const ExemplarMarker = ({
@@ -39,6 +40,7 @@ export const ExemplarMarker = ({
exemplarColor,
clickedExemplarFieldIndex,
setClickedExemplarFieldIndex,
+ maxHeight,
}: ExemplarMarkerProps) => {
const styles = useStyles2(getExemplarMarkerStyles);
const [isOpen, setIsOpen] = useState(false);
@@ -163,7 +165,7 @@ export const ExemplarMarker = ({
return (
{isLocked && }
-
+
);
}, [
@@ -175,6 +177,7 @@ export const ExemplarMarker = ({
floatingStyles,
getFloatingProps,
refs.setFloating,
+ maxHeight,
]);
const seriesColor = config
diff --git a/public/app/plugins/panel/timeseries/plugins/ExemplarsPlugin.tsx b/public/app/plugins/panel/timeseries/plugins/ExemplarsPlugin.tsx
index c741f821f88..673f2288dc7 100644
--- a/public/app/plugins/panel/timeseries/plugins/ExemplarsPlugin.tsx
+++ b/public/app/plugins/panel/timeseries/plugins/ExemplarsPlugin.tsx
@@ -18,9 +18,10 @@ interface ExemplarsPluginProps {
exemplars: DataFrame[];
timeZone: TimeZone;
visibleSeries?: VisibleExemplarLabels;
+ maxHeight?: number;
}
-export const ExemplarsPlugin = ({ exemplars, timeZone, config, visibleSeries }: ExemplarsPluginProps) => {
+export const ExemplarsPlugin = ({ exemplars, timeZone, config, visibleSeries, maxHeight }: ExemplarsPluginProps) => {
const plotInstance = useRef();
const [lockedExemplarFieldIndex, setLockedExemplarFieldIndex] = useState();
@@ -83,10 +84,11 @@ export const ExemplarsPlugin = ({ exemplars, timeZone, config, visibleSeries }:
dataFrameFieldIndex={dataFrameFieldIndex}
config={config}
exemplarColor={markerColor}
+ maxHeight={maxHeight}
/>
);
},
- [config, timeZone, visibleSeries, setLockedExemplarFieldIndex, lockedExemplarFieldIndex]
+ [config, timeZone, visibleSeries, setLockedExemplarFieldIndex, lockedExemplarFieldIndex, maxHeight]
);
return (
diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json
index 1222cec2022..355610c995d 100644
--- a/public/locales/de-DE/grafana.json
+++ b/public/locales/de-DE/grafana.json
@@ -295,6 +295,9 @@
"export-all": "",
"loading": "",
"search-by-matchers": "",
+ "titles": {
+ "notification-templates": ""
+ },
"view": ""
},
"contact-points": {
@@ -404,6 +407,20 @@
"title": "",
"uninitialized": ""
},
+ "notification-templates": {
+ "duplicate": {
+ "subTitle": "",
+ "title": ""
+ },
+ "edit": {
+ "subTitle": "",
+ "title": ""
+ },
+ "new": {
+ "subTitle": "",
+ "title": ""
+ }
+ },
"policies": {
"default-policy": {
"description": "",
@@ -592,6 +609,7 @@
}
},
"rule-viewer": {
+ "error-loading": "",
"prometheus-consistency-check": {
"alert-message": "",
"alert-title": ""
@@ -1688,10 +1706,19 @@
"one-click-description": ""
}
},
+ "alert": {
+ "close-button": ""
+ },
"auto-save-field": {
"saved": "",
"saving": ""
},
+ "card": {
+ "option": ""
+ },
+ "cascader": {
+ "clear-button": ""
+ },
"color-picker-popover": {
"palette-tab": "",
"spectrum-tab": ""
@@ -1699,8 +1726,15 @@
"confirm-button": {
"cancel": ""
},
+ "confirm-content": {
+ "placeholder": ""
+ },
"data-link-editor": {
- "info": ""
+ "info": "",
+ "new-tab-label": "",
+ "title-label": "",
+ "title-placeholder": "",
+ "url-label": ""
},
"data-link-editor-modal": {
"cancel": "",
@@ -1720,32 +1754,77 @@
"tooltip-remove": "",
"url-not-provided": ""
},
+ "data-source-basic-auth-settings": {
+ "user-label": "",
+ "user-placeholder": ""
+ },
+ "data-source-http-proxy-settings": {
+ "oauth-identity-label": "",
+ "oauth-identity-tooltip": "",
+ "skip-tls-verify-label": "",
+ "ts-client-auth-label": "",
+ "with-ca-cert-label": "",
+ "with-ca-cert-tooltip": ""
+ },
"data-source-http-settings": {
"access-help": "",
"access-help-details": "",
+ "access-label": "",
+ "access-options-browser": "",
+ "access-options-proxy": "",
"allowed-cookies": "",
+ "allowed-cookies-tooltip": "",
"auth": "",
+ "azure-auth-label": "",
+ "azure-auth-tooltip": "",
"basic-auth": "",
+ "basic-auth-label": "",
"browser-mode-description": "",
"browser-mode-title": "",
+ "default-url-access-select": "",
"default-url-tooltip": "",
"direct-url-tooltip": "",
"heading": "",
"proxy-url-tooltip": "",
"server-mode-description": "",
- "server-mode-title": ""
+ "server-mode-title": "",
+ "timeout-form-label": "",
+ "timeout-label": "",
+ "timeout-tooltip": "",
+ "url-label": "",
+ "with-credential-label": "",
+ "with-credential-tooltip": ""
},
"data-source-settings": {
"alerting-settings-heading": "",
+ "alerting-settings-label": "",
+ "alerting-settings-tooltip": "",
"cert-key-reset": "",
"custom-headers-add": "",
+ "custom-headers-header": "",
+ "custom-headers-header-placeholder": "",
+ "custom-headers-header-remove": "",
+ "custom-headers-header-value": "",
"custom-headers-title": "",
"secure-socks-heading": "",
- "tls-heading": ""
+ "secure-socks-label": "",
+ "secure-socks-tooltip": "",
+ "tls-certification-label": "",
+ "tls-certification-placeholder": "",
+ "tls-client-certification-label": "",
+ "tls-client-key-label": "",
+ "tls-client-key-placeholder": "",
+ "tls-heading": "",
+ "tls-server-name-label": "",
+ "tls-tooltip": ""
},
"date-time-picker": {
"apply": "",
- "cancel": ""
+ "calendar-icon-label": "",
+ "cancel": "",
+ "next-label": "",
+ "previous-label": "",
+ "select-placeholder": ""
},
"drawer": {
"close": "Schließen"
@@ -1769,6 +1848,10 @@
"modal": {
"close-tooltip": "Schließen"
},
+ "named-colors-palette": {
+ "text-color-swatch": "",
+ "transparent-swatch": ""
+ },
"secret-form-field": {
"reset": ""
},
@@ -1782,6 +1865,9 @@
"no-options-label": "Keine Optionen gefunden",
"placeholder": "Auswählen"
},
+ "series-color-picker-popover": {
+ "y-axis-usage": ""
+ },
"spinner": {
"aria-label": ""
},
@@ -1801,6 +1887,9 @@
"user-icon": {
"active-text": ""
},
+ "value-pill": {
+ "remove-button": ""
+ },
"viz-legend": {
"right-axis-indicator": ""
},
@@ -3637,6 +3726,16 @@
"title": ""
}
},
+ "theme-preview": {
+ "breadcrumbs": {
+ "dashboards": "",
+ "home": ""
+ },
+ "panel": {
+ "form-label": "",
+ "title": ""
+ }
+ },
"time-picker": {
"absolute": {
"recent-title": "Kürzlich verwendete absolute Bereiche",
@@ -3693,10 +3792,13 @@
"example": "",
"example-details": "",
"example-title": "Zeitbereiche-Beispiel",
+ "from-label": "",
"from-to": "",
"more-info": "",
"specify": "Zeitbereich festlegen <1>1>",
- "supported-formats": ""
+ "submit-button-label": "",
+ "supported-formats": "",
+ "to-label": ""
},
"zone": {
"select-aria-label": "Zeitzonen-Auswähler",
diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json
index 2a6465e6357..fe2fea8fc02 100644
--- a/public/locales/en-US/grafana.json
+++ b/public/locales/en-US/grafana.json
@@ -315,6 +315,10 @@
"empty-state": {
"title": "You don't have any contact points yet"
},
+ "key-value-map": {
+ "add": "Add",
+ "confirm-add": "Confirm to add"
+ },
"last-delivery-attempt": "Last delivery attempt",
"last-delivery-failed": "Last delivery attempt failed",
"no-contact-points-found": "No contact points found",
@@ -1043,10 +1047,24 @@
},
"modal": {
"title": "Row options"
- }
+ },
+ "repeat": {
+ "title": "Repeat options",
+ "variable": {
+ "title": "Variable"
+ }
+ },
+ "title": "Row options"
}
},
"edit-pane": {
+ "elements": {
+ "dashboard": "Dashboard",
+ "objects": "Objects",
+ "panels": "Panels",
+ "rows": "Rows",
+ "tabs": "Tabs"
+ },
"objects": {
"multi-select": {
"selection-number": "No. of objects selected: {{length}}"
@@ -1151,6 +1169,15 @@
"title": "Dashboard options",
"title-option": "Title"
},
+ "outline": {
+ "tree": {
+ "item": {
+ "collapse": "Collapse item",
+ "empty": "(empty)",
+ "expand": "Expand item"
+ }
+ }
+ },
"panel-edit": {
"alerting-tab": {
"dashboard-not-saved": "Dashboard must be saved before alerts can be added.",
@@ -1161,6 +1188,12 @@
"description": "CSS layout that adjusts to the available space",
"item-options": {
"hide-no-data": "Hide when no data",
+ "repeat": {
+ "variable": {
+ "description": "Repeat this panel for each value in the selected variable. This is not visible while in edit mode. You need to go back to dashboard and then update the variable or reload the dashboard.",
+ "title": "Repeat by variable"
+ }
+ },
"title": "Layout options"
},
"name": "Responsive grid",
@@ -2224,6 +2257,14 @@
"log-line": "Log line",
"no-details": "No details available"
},
+ "log-line-menu": {
+ "copy-link": "Copy link to log line",
+ "copy-log": "Copy log line",
+ "icon-label": "Log menu",
+ "pin-to-outline": "Pin log",
+ "show-context": "Show context",
+ "unpin-from-outline": "Unpin log"
+ },
"log-row-message": {
"ellipsis": "… ",
"more": "more",
diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json
index e6d9efb5f9b..1fa664da890 100644
--- a/public/locales/es-ES/grafana.json
+++ b/public/locales/es-ES/grafana.json
@@ -295,6 +295,9 @@
"export-all": "",
"loading": "",
"search-by-matchers": "",
+ "titles": {
+ "notification-templates": ""
+ },
"view": ""
},
"contact-points": {
@@ -404,6 +407,20 @@
"title": "",
"uninitialized": ""
},
+ "notification-templates": {
+ "duplicate": {
+ "subTitle": "",
+ "title": ""
+ },
+ "edit": {
+ "subTitle": "",
+ "title": ""
+ },
+ "new": {
+ "subTitle": "",
+ "title": ""
+ }
+ },
"policies": {
"default-policy": {
"description": "",
@@ -592,6 +609,7 @@
}
},
"rule-viewer": {
+ "error-loading": "",
"prometheus-consistency-check": {
"alert-message": "",
"alert-title": ""
@@ -1688,10 +1706,19 @@
"one-click-description": ""
}
},
+ "alert": {
+ "close-button": ""
+ },
"auto-save-field": {
"saved": "",
"saving": ""
},
+ "card": {
+ "option": ""
+ },
+ "cascader": {
+ "clear-button": ""
+ },
"color-picker-popover": {
"palette-tab": "",
"spectrum-tab": ""
@@ -1699,8 +1726,15 @@
"confirm-button": {
"cancel": ""
},
+ "confirm-content": {
+ "placeholder": ""
+ },
"data-link-editor": {
- "info": ""
+ "info": "",
+ "new-tab-label": "",
+ "title-label": "",
+ "title-placeholder": "",
+ "url-label": ""
},
"data-link-editor-modal": {
"cancel": "",
@@ -1720,32 +1754,77 @@
"tooltip-remove": "",
"url-not-provided": ""
},
+ "data-source-basic-auth-settings": {
+ "user-label": "",
+ "user-placeholder": ""
+ },
+ "data-source-http-proxy-settings": {
+ "oauth-identity-label": "",
+ "oauth-identity-tooltip": "",
+ "skip-tls-verify-label": "",
+ "ts-client-auth-label": "",
+ "with-ca-cert-label": "",
+ "with-ca-cert-tooltip": ""
+ },
"data-source-http-settings": {
"access-help": "",
"access-help-details": "",
+ "access-label": "",
+ "access-options-browser": "",
+ "access-options-proxy": "",
"allowed-cookies": "",
+ "allowed-cookies-tooltip": "",
"auth": "",
+ "azure-auth-label": "",
+ "azure-auth-tooltip": "",
"basic-auth": "",
+ "basic-auth-label": "",
"browser-mode-description": "",
"browser-mode-title": "",
+ "default-url-access-select": "",
"default-url-tooltip": "",
"direct-url-tooltip": "",
"heading": "",
"proxy-url-tooltip": "",
"server-mode-description": "",
- "server-mode-title": ""
+ "server-mode-title": "",
+ "timeout-form-label": "",
+ "timeout-label": "",
+ "timeout-tooltip": "",
+ "url-label": "",
+ "with-credential-label": "",
+ "with-credential-tooltip": ""
},
"data-source-settings": {
"alerting-settings-heading": "",
+ "alerting-settings-label": "",
+ "alerting-settings-tooltip": "",
"cert-key-reset": "",
"custom-headers-add": "",
+ "custom-headers-header": "",
+ "custom-headers-header-placeholder": "",
+ "custom-headers-header-remove": "",
+ "custom-headers-header-value": "",
"custom-headers-title": "",
"secure-socks-heading": "",
- "tls-heading": ""
+ "secure-socks-label": "",
+ "secure-socks-tooltip": "",
+ "tls-certification-label": "",
+ "tls-certification-placeholder": "",
+ "tls-client-certification-label": "",
+ "tls-client-key-label": "",
+ "tls-client-key-placeholder": "",
+ "tls-heading": "",
+ "tls-server-name-label": "",
+ "tls-tooltip": ""
},
"date-time-picker": {
"apply": "",
- "cancel": ""
+ "calendar-icon-label": "",
+ "cancel": "",
+ "next-label": "",
+ "previous-label": "",
+ "select-placeholder": ""
},
"drawer": {
"close": "Cerrar"
@@ -1769,6 +1848,10 @@
"modal": {
"close-tooltip": "Cerrar"
},
+ "named-colors-palette": {
+ "text-color-swatch": "",
+ "transparent-swatch": ""
+ },
"secret-form-field": {
"reset": ""
},
@@ -1782,6 +1865,9 @@
"no-options-label": "No se ha encontrado ninguna opción",
"placeholder": "Elegir"
},
+ "series-color-picker-popover": {
+ "y-axis-usage": ""
+ },
"spinner": {
"aria-label": ""
},
@@ -1801,6 +1887,9 @@
"user-icon": {
"active-text": ""
},
+ "value-pill": {
+ "remove-button": ""
+ },
"viz-legend": {
"right-axis-indicator": ""
},
@@ -3637,6 +3726,16 @@
"title": ""
}
},
+ "theme-preview": {
+ "breadcrumbs": {
+ "dashboards": "",
+ "home": ""
+ },
+ "panel": {
+ "form-label": "",
+ "title": ""
+ }
+ },
"time-picker": {
"absolute": {
"recent-title": "Intervalos absolutos utilizados recientemente",
@@ -3693,10 +3792,13 @@
"example": "",
"example-details": "",
"example-title": "Ejemplos de intervalos de tiempo",
+ "from-label": "",
"from-to": "",
"more-info": "",
"specify": "Especificar el intervalo de tiempo <1>1>",
- "supported-formats": ""
+ "submit-button-label": "",
+ "supported-formats": "",
+ "to-label": ""
},
"zone": {
"select-aria-label": "Selector de huso horario",
diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json
index f6ab1a8be74..4def0fc0e73 100644
--- a/public/locales/fr-FR/grafana.json
+++ b/public/locales/fr-FR/grafana.json
@@ -295,6 +295,9 @@
"export-all": "",
"loading": "",
"search-by-matchers": "",
+ "titles": {
+ "notification-templates": ""
+ },
"view": ""
},
"contact-points": {
@@ -404,6 +407,20 @@
"title": "",
"uninitialized": ""
},
+ "notification-templates": {
+ "duplicate": {
+ "subTitle": "",
+ "title": ""
+ },
+ "edit": {
+ "subTitle": "",
+ "title": ""
+ },
+ "new": {
+ "subTitle": "",
+ "title": ""
+ }
+ },
"policies": {
"default-policy": {
"description": "",
@@ -592,6 +609,7 @@
}
},
"rule-viewer": {
+ "error-loading": "",
"prometheus-consistency-check": {
"alert-message": "",
"alert-title": ""
@@ -1688,10 +1706,19 @@
"one-click-description": ""
}
},
+ "alert": {
+ "close-button": ""
+ },
"auto-save-field": {
"saved": "",
"saving": ""
},
+ "card": {
+ "option": ""
+ },
+ "cascader": {
+ "clear-button": ""
+ },
"color-picker-popover": {
"palette-tab": "",
"spectrum-tab": ""
@@ -1699,8 +1726,15 @@
"confirm-button": {
"cancel": ""
},
+ "confirm-content": {
+ "placeholder": ""
+ },
"data-link-editor": {
- "info": ""
+ "info": "",
+ "new-tab-label": "",
+ "title-label": "",
+ "title-placeholder": "",
+ "url-label": ""
},
"data-link-editor-modal": {
"cancel": "",
@@ -1720,32 +1754,77 @@
"tooltip-remove": "",
"url-not-provided": ""
},
+ "data-source-basic-auth-settings": {
+ "user-label": "",
+ "user-placeholder": ""
+ },
+ "data-source-http-proxy-settings": {
+ "oauth-identity-label": "",
+ "oauth-identity-tooltip": "",
+ "skip-tls-verify-label": "",
+ "ts-client-auth-label": "",
+ "with-ca-cert-label": "",
+ "with-ca-cert-tooltip": ""
+ },
"data-source-http-settings": {
"access-help": "",
"access-help-details": "",
+ "access-label": "",
+ "access-options-browser": "",
+ "access-options-proxy": "",
"allowed-cookies": "",
+ "allowed-cookies-tooltip": "",
"auth": "",
+ "azure-auth-label": "",
+ "azure-auth-tooltip": "",
"basic-auth": "",
+ "basic-auth-label": "",
"browser-mode-description": "",
"browser-mode-title": "",
+ "default-url-access-select": "",
"default-url-tooltip": "",
"direct-url-tooltip": "",
"heading": "",
"proxy-url-tooltip": "",
"server-mode-description": "",
- "server-mode-title": ""
+ "server-mode-title": "",
+ "timeout-form-label": "",
+ "timeout-label": "",
+ "timeout-tooltip": "",
+ "url-label": "",
+ "with-credential-label": "",
+ "with-credential-tooltip": ""
},
"data-source-settings": {
"alerting-settings-heading": "",
+ "alerting-settings-label": "",
+ "alerting-settings-tooltip": "",
"cert-key-reset": "",
"custom-headers-add": "",
+ "custom-headers-header": "",
+ "custom-headers-header-placeholder": "",
+ "custom-headers-header-remove": "",
+ "custom-headers-header-value": "",
"custom-headers-title": "",
"secure-socks-heading": "",
- "tls-heading": ""
+ "secure-socks-label": "",
+ "secure-socks-tooltip": "",
+ "tls-certification-label": "",
+ "tls-certification-placeholder": "",
+ "tls-client-certification-label": "",
+ "tls-client-key-label": "",
+ "tls-client-key-placeholder": "",
+ "tls-heading": "",
+ "tls-server-name-label": "",
+ "tls-tooltip": ""
},
"date-time-picker": {
"apply": "",
- "cancel": ""
+ "calendar-icon-label": "",
+ "cancel": "",
+ "next-label": "",
+ "previous-label": "",
+ "select-placeholder": ""
},
"drawer": {
"close": "Fermer"
@@ -1769,6 +1848,10 @@
"modal": {
"close-tooltip": "Fermer"
},
+ "named-colors-palette": {
+ "text-color-swatch": "",
+ "transparent-swatch": ""
+ },
"secret-form-field": {
"reset": ""
},
@@ -1782,6 +1865,9 @@
"no-options-label": "Aucune option trouvée",
"placeholder": "Choisir"
},
+ "series-color-picker-popover": {
+ "y-axis-usage": ""
+ },
"spinner": {
"aria-label": ""
},
@@ -1801,6 +1887,9 @@
"user-icon": {
"active-text": ""
},
+ "value-pill": {
+ "remove-button": ""
+ },
"viz-legend": {
"right-axis-indicator": ""
},
@@ -3637,6 +3726,16 @@
"title": ""
}
},
+ "theme-preview": {
+ "breadcrumbs": {
+ "dashboards": "",
+ "home": ""
+ },
+ "panel": {
+ "form-label": "",
+ "title": ""
+ }
+ },
"time-picker": {
"absolute": {
"recent-title": "Périodes absolues récemment utilisées",
@@ -3693,10 +3792,13 @@
"example": "",
"example-details": "",
"example-title": "Exemple de plages de temps",
+ "from-label": "",
"from-to": "",
"more-info": "",
"specify": "Spécifiez la plage de temps <1>1>",
- "supported-formats": ""
+ "submit-button-label": "",
+ "supported-formats": "",
+ "to-label": ""
},
"zone": {
"select-aria-label": "Outil de sélection du fuseau horaire",
diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json
index 3eea1ed364a..d6af162cbf2 100644
--- a/public/locales/pseudo-LOCALE/grafana.json
+++ b/public/locales/pseudo-LOCALE/grafana.json
@@ -315,6 +315,10 @@
"empty-state": {
"title": "Ÿőū đőʼn'ŧ ĥävę äʼny čőʼnŧäčŧ pőįʼnŧş yęŧ"
},
+ "key-value-map": {
+ "add": "Åđđ",
+ "confirm-add": "Cőʼnƒįřm ŧő äđđ"
+ },
"last-delivery-attempt": "Ŀäşŧ đęľįvęřy äŧŧęmpŧ",
"last-delivery-failed": "Ŀäşŧ đęľįvęřy äŧŧęmpŧ ƒäįľęđ",
"no-contact-points-found": "Ńő čőʼnŧäčŧ pőįʼnŧş ƒőūʼnđ",
@@ -1043,10 +1047,24 @@
},
"modal": {
"title": "Ŗőŵ őpŧįőʼnş"
- }
+ },
+ "repeat": {
+ "title": "Ŗępęäŧ őpŧįőʼnş",
+ "variable": {
+ "title": "Väřįäþľę"
+ }
+ },
+ "title": "Ŗőŵ őpŧįőʼnş"
}
},
"edit-pane": {
+ "elements": {
+ "dashboard": "Đäşĥþőäřđ",
+ "objects": "Øþĵęčŧş",
+ "panels": "Päʼnęľş",
+ "rows": "Ŗőŵş",
+ "tabs": "Ŧäþş"
+ },
"objects": {
"multi-select": {
"selection-number": "Ńő. őƒ őþĵęčŧş şęľęčŧęđ: {{length}}"
@@ -1151,6 +1169,15 @@
"title": "Đäşĥþőäřđ őpŧįőʼnş",
"title-option": "Ŧįŧľę"
},
+ "outline": {
+ "tree": {
+ "item": {
+ "collapse": "Cőľľäpşę įŧęm",
+ "empty": "(ęmpŧy)",
+ "expand": "Ēχpäʼnđ įŧęm"
+ }
+ }
+ },
"panel-edit": {
"alerting-tab": {
"dashboard-not-saved": "Đäşĥþőäřđ mūşŧ þę şävęđ þęƒőřę äľęřŧş čäʼn þę äđđęđ.",
@@ -1161,6 +1188,12 @@
"description": "CŜŜ ľäyőūŧ ŧĥäŧ äđĵūşŧş ŧő ŧĥę äväįľäþľę şpäčę",
"item-options": {
"hide-no-data": "Ħįđę ŵĥęʼn ʼnő đäŧä",
+ "repeat": {
+ "variable": {
+ "description": "Ŗępęäŧ ŧĥįş päʼnęľ ƒőř ęäčĥ väľūę įʼn ŧĥę şęľęčŧęđ väřįäþľę. Ŧĥįş įş ʼnőŧ vįşįþľę ŵĥįľę įʼn ęđįŧ mőđę. Ÿőū ʼnęęđ ŧő ģő þäčĸ ŧő đäşĥþőäřđ äʼnđ ŧĥęʼn ūpđäŧę ŧĥę väřįäþľę őř řęľőäđ ŧĥę đäşĥþőäřđ.",
+ "title": "Ŗępęäŧ þy väřįäþľę"
+ }
+ },
"title": "Ŀäyőūŧ őpŧįőʼnş"
},
"name": "Ŗęşpőʼnşįvę ģřįđ",
@@ -2224,6 +2257,14 @@
"log-line": "Ŀőģ ľįʼnę",
"no-details": "Ńő đęŧäįľş äväįľäþľę"
},
+ "log-line-menu": {
+ "copy-link": "Cőpy ľįʼnĸ ŧő ľőģ ľįʼnę",
+ "copy-log": "Cőpy ľőģ ľįʼnę",
+ "icon-label": "Ŀőģ męʼnū",
+ "pin-to-outline": "Pįʼn ľőģ",
+ "show-context": "Ŝĥőŵ čőʼnŧęχŧ",
+ "unpin-from-outline": "Ůʼnpįʼn ľőģ"
+ },
"log-row-message": {
"ellipsis": "… ",
"more": "mőřę",
diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json
index c10c3c0eaa4..6e0f3ba0687 100644
--- a/public/locales/pt-BR/grafana.json
+++ b/public/locales/pt-BR/grafana.json
@@ -295,6 +295,9 @@
"export-all": "",
"loading": "",
"search-by-matchers": "",
+ "titles": {
+ "notification-templates": ""
+ },
"view": ""
},
"contact-points": {
@@ -404,6 +407,20 @@
"title": "",
"uninitialized": ""
},
+ "notification-templates": {
+ "duplicate": {
+ "subTitle": "",
+ "title": ""
+ },
+ "edit": {
+ "subTitle": "",
+ "title": ""
+ },
+ "new": {
+ "subTitle": "",
+ "title": ""
+ }
+ },
"policies": {
"default-policy": {
"description": "",
@@ -592,6 +609,7 @@
}
},
"rule-viewer": {
+ "error-loading": "",
"prometheus-consistency-check": {
"alert-message": "",
"alert-title": ""
@@ -1688,10 +1706,19 @@
"one-click-description": ""
}
},
+ "alert": {
+ "close-button": ""
+ },
"auto-save-field": {
"saved": "",
"saving": ""
},
+ "card": {
+ "option": ""
+ },
+ "cascader": {
+ "clear-button": ""
+ },
"color-picker-popover": {
"palette-tab": "",
"spectrum-tab": ""
@@ -1699,8 +1726,15 @@
"confirm-button": {
"cancel": ""
},
+ "confirm-content": {
+ "placeholder": ""
+ },
"data-link-editor": {
- "info": ""
+ "info": "",
+ "new-tab-label": "",
+ "title-label": "",
+ "title-placeholder": "",
+ "url-label": ""
},
"data-link-editor-modal": {
"cancel": "",
@@ -1720,32 +1754,77 @@
"tooltip-remove": "",
"url-not-provided": ""
},
+ "data-source-basic-auth-settings": {
+ "user-label": "",
+ "user-placeholder": ""
+ },
+ "data-source-http-proxy-settings": {
+ "oauth-identity-label": "",
+ "oauth-identity-tooltip": "",
+ "skip-tls-verify-label": "",
+ "ts-client-auth-label": "",
+ "with-ca-cert-label": "",
+ "with-ca-cert-tooltip": ""
+ },
"data-source-http-settings": {
"access-help": "",
"access-help-details": "",
+ "access-label": "",
+ "access-options-browser": "",
+ "access-options-proxy": "",
"allowed-cookies": "",
+ "allowed-cookies-tooltip": "",
"auth": "",
+ "azure-auth-label": "",
+ "azure-auth-tooltip": "",
"basic-auth": "",
+ "basic-auth-label": "",
"browser-mode-description": "",
"browser-mode-title": "",
+ "default-url-access-select": "",
"default-url-tooltip": "",
"direct-url-tooltip": "",
"heading": "",
"proxy-url-tooltip": "",
"server-mode-description": "",
- "server-mode-title": ""
+ "server-mode-title": "",
+ "timeout-form-label": "",
+ "timeout-label": "",
+ "timeout-tooltip": "",
+ "url-label": "",
+ "with-credential-label": "",
+ "with-credential-tooltip": ""
},
"data-source-settings": {
"alerting-settings-heading": "",
+ "alerting-settings-label": "",
+ "alerting-settings-tooltip": "",
"cert-key-reset": "",
"custom-headers-add": "",
+ "custom-headers-header": "",
+ "custom-headers-header-placeholder": "",
+ "custom-headers-header-remove": "",
+ "custom-headers-header-value": "",
"custom-headers-title": "",
"secure-socks-heading": "",
- "tls-heading": ""
+ "secure-socks-label": "",
+ "secure-socks-tooltip": "",
+ "tls-certification-label": "",
+ "tls-certification-placeholder": "",
+ "tls-client-certification-label": "",
+ "tls-client-key-label": "",
+ "tls-client-key-placeholder": "",
+ "tls-heading": "",
+ "tls-server-name-label": "",
+ "tls-tooltip": ""
},
"date-time-picker": {
"apply": "",
- "cancel": ""
+ "calendar-icon-label": "",
+ "cancel": "",
+ "next-label": "",
+ "previous-label": "",
+ "select-placeholder": ""
},
"drawer": {
"close": "Fechar"
@@ -1769,6 +1848,10 @@
"modal": {
"close-tooltip": "Fechar"
},
+ "named-colors-palette": {
+ "text-color-swatch": "",
+ "transparent-swatch": ""
+ },
"secret-form-field": {
"reset": ""
},
@@ -1782,6 +1865,9 @@
"no-options-label": "Nenhuma opção encontrada",
"placeholder": "Escolher"
},
+ "series-color-picker-popover": {
+ "y-axis-usage": ""
+ },
"spinner": {
"aria-label": ""
},
@@ -1801,6 +1887,9 @@
"user-icon": {
"active-text": ""
},
+ "value-pill": {
+ "remove-button": ""
+ },
"viz-legend": {
"right-axis-indicator": ""
},
@@ -3637,6 +3726,16 @@
"title": ""
}
},
+ "theme-preview": {
+ "breadcrumbs": {
+ "dashboards": "",
+ "home": ""
+ },
+ "panel": {
+ "form-label": "",
+ "title": ""
+ }
+ },
"time-picker": {
"absolute": {
"recent-title": "Intervalos absolutos usados recentemente",
@@ -3693,10 +3792,13 @@
"example": "",
"example-details": "",
"example-title": "Exemplos de intervalos de tempo",
+ "from-label": "",
"from-to": "",
"more-info": "",
"specify": "Especifique o intervalo de tempo <1>1>",
- "supported-formats": ""
+ "submit-button-label": "",
+ "supported-formats": "",
+ "to-label": ""
},
"zone": {
"select-aria-label": "Seletor de fuso horário",
diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json
index eb52a53913b..d1f6b954db5 100644
--- a/public/locales/zh-Hans/grafana.json
+++ b/public/locales/zh-Hans/grafana.json
@@ -295,6 +295,9 @@
"export-all": "",
"loading": "",
"search-by-matchers": "",
+ "titles": {
+ "notification-templates": ""
+ },
"view": ""
},
"contact-points": {
@@ -402,6 +405,20 @@
"title": "",
"uninitialized": ""
},
+ "notification-templates": {
+ "duplicate": {
+ "subTitle": "",
+ "title": ""
+ },
+ "edit": {
+ "subTitle": "",
+ "title": ""
+ },
+ "new": {
+ "subTitle": "",
+ "title": ""
+ }
+ },
"policies": {
"default-policy": {
"description": "",
@@ -588,6 +605,7 @@
}
},
"rule-viewer": {
+ "error-loading": "",
"prometheus-consistency-check": {
"alert-message": "",
"alert-title": ""
@@ -1679,10 +1697,19 @@
"one-click-description": ""
}
},
+ "alert": {
+ "close-button": ""
+ },
"auto-save-field": {
"saved": "",
"saving": ""
},
+ "card": {
+ "option": ""
+ },
+ "cascader": {
+ "clear-button": ""
+ },
"color-picker-popover": {
"palette-tab": "",
"spectrum-tab": ""
@@ -1690,8 +1717,15 @@
"confirm-button": {
"cancel": ""
},
+ "confirm-content": {
+ "placeholder": ""
+ },
"data-link-editor": {
- "info": ""
+ "info": "",
+ "new-tab-label": "",
+ "title-label": "",
+ "title-placeholder": "",
+ "url-label": ""
},
"data-link-editor-modal": {
"cancel": "",
@@ -1711,32 +1745,77 @@
"tooltip-remove": "",
"url-not-provided": ""
},
+ "data-source-basic-auth-settings": {
+ "user-label": "",
+ "user-placeholder": ""
+ },
+ "data-source-http-proxy-settings": {
+ "oauth-identity-label": "",
+ "oauth-identity-tooltip": "",
+ "skip-tls-verify-label": "",
+ "ts-client-auth-label": "",
+ "with-ca-cert-label": "",
+ "with-ca-cert-tooltip": ""
+ },
"data-source-http-settings": {
"access-help": "",
"access-help-details": "",
+ "access-label": "",
+ "access-options-browser": "",
+ "access-options-proxy": "",
"allowed-cookies": "",
+ "allowed-cookies-tooltip": "",
"auth": "",
+ "azure-auth-label": "",
+ "azure-auth-tooltip": "",
"basic-auth": "",
+ "basic-auth-label": "",
"browser-mode-description": "",
"browser-mode-title": "",
+ "default-url-access-select": "",
"default-url-tooltip": "",
"direct-url-tooltip": "",
"heading": "",
"proxy-url-tooltip": "",
"server-mode-description": "",
- "server-mode-title": ""
+ "server-mode-title": "",
+ "timeout-form-label": "",
+ "timeout-label": "",
+ "timeout-tooltip": "",
+ "url-label": "",
+ "with-credential-label": "",
+ "with-credential-tooltip": ""
},
"data-source-settings": {
"alerting-settings-heading": "",
+ "alerting-settings-label": "",
+ "alerting-settings-tooltip": "",
"cert-key-reset": "",
"custom-headers-add": "",
+ "custom-headers-header": "",
+ "custom-headers-header-placeholder": "",
+ "custom-headers-header-remove": "",
+ "custom-headers-header-value": "",
"custom-headers-title": "",
"secure-socks-heading": "",
- "tls-heading": ""
+ "secure-socks-label": "",
+ "secure-socks-tooltip": "",
+ "tls-certification-label": "",
+ "tls-certification-placeholder": "",
+ "tls-client-certification-label": "",
+ "tls-client-key-label": "",
+ "tls-client-key-placeholder": "",
+ "tls-heading": "",
+ "tls-server-name-label": "",
+ "tls-tooltip": ""
},
"date-time-picker": {
"apply": "",
- "cancel": ""
+ "calendar-icon-label": "",
+ "cancel": "",
+ "next-label": "",
+ "previous-label": "",
+ "select-placeholder": ""
},
"drawer": {
"close": "关闭"
@@ -1760,6 +1839,10 @@
"modal": {
"close-tooltip": "关闭"
},
+ "named-colors-palette": {
+ "text-color-swatch": "",
+ "transparent-swatch": ""
+ },
"secret-form-field": {
"reset": ""
},
@@ -1773,6 +1856,9 @@
"no-options-label": "未找到选项",
"placeholder": "选择"
},
+ "series-color-picker-popover": {
+ "y-axis-usage": ""
+ },
"spinner": {
"aria-label": ""
},
@@ -1792,6 +1878,9 @@
"user-icon": {
"active-text": ""
},
+ "value-pill": {
+ "remove-button": ""
+ },
"viz-legend": {
"right-axis-indicator": ""
},
@@ -3623,6 +3712,16 @@
"title": ""
}
},
+ "theme-preview": {
+ "breadcrumbs": {
+ "dashboards": "",
+ "home": ""
+ },
+ "panel": {
+ "form-label": "",
+ "title": ""
+ }
+ },
"time-picker": {
"absolute": {
"recent-title": "最近使用的绝对范围",
@@ -3679,10 +3778,13 @@
"example": "",
"example-details": "",
"example-title": "示例时间范围",
+ "from-label": "",
"from-to": "",
"more-info": "",
"specify": "指定时间范围 <1>1>",
- "supported-formats": ""
+ "submit-button-label": "",
+ "supported-formats": "",
+ "to-label": ""
},
"zone": {
"select-aria-label": "时区选择器",
diff --git a/yarn.lock b/yarn.lock
index eb8360924ac..d5a786396d4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3772,11 +3772,11 @@ __metadata:
languageName: unknown
linkType: soft
-"@grafana/scenes-react@npm:6.0.2":
- version: 6.0.2
- resolution: "@grafana/scenes-react@npm:6.0.2"
+"@grafana/scenes-react@npm:6.1.4":
+ version: 6.1.4
+ resolution: "@grafana/scenes-react@npm:6.1.4"
dependencies:
- "@grafana/scenes": "npm:6.0.2"
+ "@grafana/scenes": "npm:6.1.4"
lru-cache: "npm:^10.2.2"
react-use: "npm:^17.4.0"
peerDependencies:
@@ -3788,13 +3788,13 @@ __metadata:
react: ^18.0.0
react-dom: ^18.0.0
react-router-dom: ^6.28.0
- checksum: 10/9744e01f2ff912229e43cedfa41d626ccdfd034f5b9718b57c593bc90edadade960f76baf1d8ad19eed03709c17c62397df1871b89acc635172aa14f6a20e096
+ checksum: 10/69a344f30937a80e25201c8ce1261f85c10663e68d70103c28603abbee2496c7855d999b2a212132f48af597f3f528de65bade66c05b02c719ca25f8f8c682bd
languageName: node
linkType: hard
-"@grafana/scenes@npm:6.0.2":
- version: 6.0.2
- resolution: "@grafana/scenes@npm:6.0.2"
+"@grafana/scenes@npm:6.1.4":
+ version: 6.1.4
+ resolution: "@grafana/scenes@npm:6.1.4"
dependencies:
"@floating-ui/react": "npm:^0.26.16"
"@leeoniya/ufuzzy": "npm:^1.0.16"
@@ -3812,7 +3812,7 @@ __metadata:
react: ^18.0.0
react-dom: ^18.0.0
react-router-dom: ^6.28.0
- checksum: 10/2584f296db6299ef0a09d51f5c267ebcf7e44bd17b4d6516e38d3220f8f1d7aebc63c5fc6523979c4ac4d3f555416ca573e85e03bd36eb33a11941a5b3497149
+ checksum: 10/708652236c3b4a5bb0e1cd84739bef530b06244e2798e754347eabd8ab040b24c7f39376fd7b93d1049da6c0ee40785294882cba0125ce8171d5627a71ae63db
languageName: node
linkType: hard
@@ -18126,8 +18126,8 @@ __metadata:
"@grafana/prometheus": "workspace:*"
"@grafana/runtime": "workspace:*"
"@grafana/saga-icons": "workspace:*"
- "@grafana/scenes": "npm:6.0.2"
- "@grafana/scenes-react": "npm:6.0.2"
+ "@grafana/scenes": "npm:6.1.4"
+ "@grafana/scenes-react": "npm:6.1.4"
"@grafana/schema": "workspace:*"
"@grafana/sql": "workspace:*"
"@grafana/tsconfig": "npm:^2.0.0"