mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
CommandPalette: Search for dashboards using API (#61090)
* CommandPalette: Search for dashboards using API * Fix ordering of dashboards * Put recent + search dashboards in root list, refactor actions into hook * limit recent dashboards to 5 * search debounce to 200ms * update priorities * extract i18n
This commit is contained in:
parent
9a14a7db03
commit
5d725d22ad
@ -91,7 +91,8 @@ export class AppWrapper extends React.Component<AppWrapperProps, AppWrapperState
|
|||||||
}
|
}
|
||||||
|
|
||||||
commandPaletteEnabled() {
|
commandPaletteEnabled() {
|
||||||
return config.featureToggles.commandPalette && !config.isPublicDashboardView;
|
const isLoginPage = locationService.getLocation().pathname === '/login';
|
||||||
|
return config.featureToggles.commandPalette && !config.isPublicDashboardView && !isLoginPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
searchBarEnabled() {
|
searchBarEnabled() {
|
||||||
|
@ -9,21 +9,18 @@ import {
|
|||||||
KBarResults,
|
KBarResults,
|
||||||
KBarSearch,
|
KBarSearch,
|
||||||
useMatches,
|
useMatches,
|
||||||
Action,
|
|
||||||
VisualState,
|
VisualState,
|
||||||
useRegisterActions,
|
useRegisterActions,
|
||||||
useKBar,
|
useKBar,
|
||||||
} from 'kbar';
|
} from 'kbar';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { reportInteraction, locationService } from '@grafana/runtime';
|
import { reportInteraction } from '@grafana/runtime';
|
||||||
import { useStyles2 } from '@grafana/ui';
|
import { useStyles2 } from '@grafana/ui';
|
||||||
import { useSelector } from 'app/types';
|
|
||||||
|
|
||||||
import { ResultItem } from './ResultItem';
|
import { ResultItem } from './ResultItem';
|
||||||
import getDashboardNavActions from './actions/dashboard.nav.actions';
|
import useActions from './actions/useActions';
|
||||||
import getGlobalActions from './actions/global.static.actions';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrap all the components from KBar here.
|
* Wrap all the components from KBar here.
|
||||||
@ -32,18 +29,14 @@ import getGlobalActions from './actions/global.static.actions';
|
|||||||
|
|
||||||
export const CommandPalette = () => {
|
export const CommandPalette = () => {
|
||||||
const styles = useStyles2(getSearchStyles);
|
const styles = useStyles2(getSearchStyles);
|
||||||
const [actions, setActions] = useState<Action[]>([]);
|
|
||||||
const [staticActions, setStaticActions] = useState<Action[]>([]);
|
|
||||||
const { query, showing } = useKBar((state) => ({
|
|
||||||
showing: state.visualState === VisualState.showing,
|
|
||||||
}));
|
|
||||||
const isNotLogin = locationService.getLocation().pathname !== '/login';
|
|
||||||
|
|
||||||
const { navBarTree } = useSelector((state) => {
|
const { query, showing, searchQuery } = useKBar((state) => ({
|
||||||
return {
|
showing: state.visualState === VisualState.showing,
|
||||||
navBarTree: state.navBarTree,
|
searchQuery: state.searchQuery,
|
||||||
};
|
}));
|
||||||
});
|
|
||||||
|
const actions = useActions(searchQuery, showing);
|
||||||
|
useRegisterActions(actions, [actions]);
|
||||||
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const { overlayProps } = useOverlay(
|
const { overlayProps } = useOverlay(
|
||||||
@ -52,26 +45,10 @@ export const CommandPalette = () => {
|
|||||||
);
|
);
|
||||||
const { dialogProps } = useDialog({}, ref);
|
const { dialogProps } = useDialog({}, ref);
|
||||||
|
|
||||||
|
// Report interaction when opened
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isNotLogin) {
|
showing && reportInteraction('command_palette_opened');
|
||||||
const staticActionsResp = getGlobalActions(navBarTree);
|
}, [showing]);
|
||||||
setStaticActions(staticActionsResp);
|
|
||||||
setActions([...staticActionsResp]);
|
|
||||||
}
|
|
||||||
}, [isNotLogin, navBarTree]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (showing) {
|
|
||||||
reportInteraction('command_palette_opened');
|
|
||||||
|
|
||||||
// Do dashboard search on demand
|
|
||||||
getDashboardNavActions('go/dashboard').then((dashAct) => {
|
|
||||||
setActions([...staticActions, ...dashAct]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [showing, staticActions]);
|
|
||||||
|
|
||||||
useRegisterActions(actions, [actions]);
|
|
||||||
|
|
||||||
return actions.length > 0 ? (
|
return actions.length > 0 ? (
|
||||||
<KBarPortal>
|
<KBarPortal>
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
import { Action } from 'kbar';
|
|
||||||
|
|
||||||
import { locationUtil } from '@grafana/data';
|
|
||||||
import { locationService } from '@grafana/runtime';
|
|
||||||
import { getGrafanaSearcher } from 'app/features/search/service';
|
|
||||||
|
|
||||||
async function getDashboardNav(parentId: string): Promise<Action[]> {
|
|
||||||
const data = await getGrafanaSearcher().search({
|
|
||||||
kind: ['dashboard'],
|
|
||||||
query: '*',
|
|
||||||
limit: 500,
|
|
||||||
});
|
|
||||||
|
|
||||||
const goToDashboardActions: Action[] = data.view.map((item) => {
|
|
||||||
const { url, name } = item; // items are backed by DataFrameView, so must hold the url in a closure
|
|
||||||
return {
|
|
||||||
parent: parentId,
|
|
||||||
id: `go/dashboard/${url}`,
|
|
||||||
name: `${name}`,
|
|
||||||
perform: () => {
|
|
||||||
locationService.push(locationUtil.stripBaseFromUrl(url));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return goToDashboardActions;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async (parentId: string) => {
|
|
||||||
const dashboardNav = await getDashboardNav(parentId);
|
|
||||||
return dashboardNav;
|
|
||||||
};
|
|
@ -0,0 +1,76 @@
|
|||||||
|
import { locationUtil } from '@grafana/data';
|
||||||
|
import { locationService } from '@grafana/runtime';
|
||||||
|
import { t } from 'app/core/internationalization';
|
||||||
|
import impressionSrv from 'app/core/services/impression_srv';
|
||||||
|
import { getGrafanaSearcher } from 'app/features/search/service';
|
||||||
|
|
||||||
|
import { CommandPaletteAction } from '../types';
|
||||||
|
import { RECENT_DASHBOARDS_PRORITY, SEARCH_RESULTS_PRORITY } from '../values';
|
||||||
|
|
||||||
|
const MAX_SEARCH_RESULTS = 100;
|
||||||
|
const MAX_RECENT_DASHBOARDS = 5;
|
||||||
|
|
||||||
|
export async function getRecentDashboardActions(): Promise<CommandPaletteAction[]> {
|
||||||
|
const recentUids = (await impressionSrv.getDashboardOpened()).slice(0, MAX_RECENT_DASHBOARDS);
|
||||||
|
const resultsDataFrame = await getGrafanaSearcher().search({
|
||||||
|
kind: ['dashboard'],
|
||||||
|
limit: MAX_RECENT_DASHBOARDS,
|
||||||
|
uid: recentUids,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Search results are alphabetical, so reorder them according to recently viewed
|
||||||
|
const recentResults = resultsDataFrame.view.toArray();
|
||||||
|
recentResults.sort((resultA, resultB) => {
|
||||||
|
const orderA = recentUids.indexOf(resultA.uid);
|
||||||
|
const orderB = recentUids.indexOf(resultB.uid);
|
||||||
|
return orderA - orderB;
|
||||||
|
});
|
||||||
|
|
||||||
|
const recentDashboardActions: CommandPaletteAction[] = recentResults.map((item) => {
|
||||||
|
const { url, name } = item; // items are backed by DataFrameView, so must hold the url in a closure
|
||||||
|
return {
|
||||||
|
id: `recent-dashboards/${url}`,
|
||||||
|
name: `${name}`,
|
||||||
|
section: t('command-palette.section.recent-dashboards', 'Recently viewed dashboards'),
|
||||||
|
priority: RECENT_DASHBOARDS_PRORITY,
|
||||||
|
perform: () => {
|
||||||
|
locationService.push(locationUtil.stripBaseFromUrl(url));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return recentDashboardActions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDashboardSearchResultActions(searchQuery: string): Promise<CommandPaletteAction[]> {
|
||||||
|
// Empty strings should not come through to here
|
||||||
|
if (searchQuery.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await getGrafanaSearcher().search({
|
||||||
|
kind: ['dashboard'],
|
||||||
|
query: searchQuery,
|
||||||
|
limit: MAX_SEARCH_RESULTS,
|
||||||
|
});
|
||||||
|
|
||||||
|
const goToDashboardActions: CommandPaletteAction[] = data.view.map((item) => {
|
||||||
|
const { url, name } = item; // items are backed by DataFrameView, so must hold the url in a closure
|
||||||
|
return {
|
||||||
|
id: `go/dashboard/${url}`,
|
||||||
|
name: `${name}`,
|
||||||
|
section: t('command-palette.section.dashboard-search-results', 'Dashboards'),
|
||||||
|
priority: SEARCH_RESULTS_PRORITY,
|
||||||
|
perform: () => {
|
||||||
|
locationService.push(locationUtil.stripBaseFromUrl(url));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return goToDashboardActions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// export default async (parentId: string) => {
|
||||||
|
// const dashboardNav = await getDashboardNav(parentId);
|
||||||
|
// return dashboardNav;
|
||||||
|
// };
|
@ -1,27 +1,21 @@
|
|||||||
import { Action, Priority } from 'kbar';
|
import { locationUtil, NavModelItem } from '@grafana/data';
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { isIconName, locationUtil, NavModelItem } from '@grafana/data';
|
|
||||||
import { locationService } from '@grafana/runtime';
|
import { locationService } from '@grafana/runtime';
|
||||||
import { Icon } from '@grafana/ui';
|
import { t } from 'app/core/internationalization';
|
||||||
import { changeTheme } from 'app/core/services/theme';
|
import { changeTheme } from 'app/core/services/theme';
|
||||||
|
|
||||||
const SECTION_PAGES = 'Pages';
|
import { CommandPaletteAction } from '../types';
|
||||||
const SECTION_ACTIONS = 'Actions';
|
import { DEFAULT_PRIORITY, PREFERENCES_PRIORITY } from '../values';
|
||||||
const SECTION_PREFERENCES = 'Preferences';
|
|
||||||
|
|
||||||
export interface NavBarActions {
|
// We reuse this, but translations cannot be in module scope (t must be called after i18n has set up,)
|
||||||
url: string;
|
const getPagesSectionTranslation = () => t('command-palette.section.pages', 'Pages');
|
||||||
actions: Action[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Clean this once ID is mandatory on nav items
|
// TODO: Clean this once ID is mandatory on nav items
|
||||||
function idForNavItem(navItem: NavModelItem) {
|
function idForNavItem(navItem: NavModelItem) {
|
||||||
return 'navModel.' + navItem.id ?? navItem.url ?? navItem.text ?? navItem.subTitle;
|
return 'navModel.' + navItem.id ?? navItem.url ?? navItem.text ?? navItem.subTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
function navTreeToActions(navTree: NavModelItem[], parent?: NavModelItem): Action[] {
|
function navTreeToActions(navTree: NavModelItem[], parent?: NavModelItem): CommandPaletteAction[] {
|
||||||
const navActions: Action[] = [];
|
const navActions: CommandPaletteAction[] = [];
|
||||||
|
|
||||||
for (const navItem of navTree) {
|
for (const navItem of navTree) {
|
||||||
const { url, text, isCreateAction, children } = navItem;
|
const { url, text, isCreateAction, children } = navItem;
|
||||||
@ -31,15 +25,15 @@ function navTreeToActions(navTree: NavModelItem[], parent?: NavModelItem): Actio
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const action: Action = {
|
const section = isCreateAction ? t('command-palette.section.actions', 'Actions') : getPagesSectionTranslation();
|
||||||
|
|
||||||
|
const action = {
|
||||||
id: idForNavItem(navItem),
|
id: idForNavItem(navItem),
|
||||||
name: text, // TODO: translate
|
name: text, // TODO: translate
|
||||||
section: isCreateAction ? SECTION_ACTIONS : SECTION_PAGES,
|
section: section,
|
||||||
perform: url ? () => locationService.push(locationUtil.stripBaseFromUrl(url)) : undefined,
|
perform: url ? () => locationService.push(locationUtil.stripBaseFromUrl(url)) : undefined,
|
||||||
parent: parent && idForNavItem(parent),
|
parent: parent && idForNavItem(parent),
|
||||||
|
priority: DEFAULT_PRIORITY,
|
||||||
// Only show icons for top level items
|
|
||||||
icon: !parent && iconForNavItem(navItem),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
navActions.push(action);
|
navActions.push(action);
|
||||||
@ -53,46 +47,40 @@ function navTreeToActions(navTree: NavModelItem[], parent?: NavModelItem): Actio
|
|||||||
return navActions;
|
return navActions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (navBarTree: NavModelItem[]) => {
|
export default (navBarTree: NavModelItem[]): CommandPaletteAction[] => {
|
||||||
const globalActions: Action[] = [
|
const globalActions: CommandPaletteAction[] = [
|
||||||
{
|
|
||||||
// TODO: Figure out what section, if any, to put this in
|
|
||||||
id: 'go/dashboard',
|
|
||||||
name: 'Dashboards...',
|
|
||||||
keywords: 'navigate',
|
|
||||||
priority: Priority.NORMAL,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'go/search',
|
id: 'go/search',
|
||||||
name: 'Search',
|
name: t('command-palette.action.search', 'Search'),
|
||||||
keywords: 'navigate',
|
keywords: 'navigate',
|
||||||
icon: <Icon name="search" size="md" />,
|
|
||||||
perform: () => locationService.push('?search=open'),
|
perform: () => locationService.push('?search=open'),
|
||||||
section: SECTION_PAGES,
|
section: t('command-palette.section.pages', 'Pages'),
|
||||||
shortcut: ['s', 'o'],
|
shortcut: ['s', 'o'],
|
||||||
|
priority: DEFAULT_PRIORITY,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'preferences/theme',
|
id: 'preferences/theme',
|
||||||
name: 'Change theme...',
|
name: t('command-palette.action.change-theme', 'Change theme...'),
|
||||||
keywords: 'interface color dark light',
|
keywords: 'interface color dark light',
|
||||||
section: SECTION_PREFERENCES,
|
section: t('command-palette.section.preferences', 'Preferences'),
|
||||||
shortcut: ['c', 't'],
|
shortcut: ['c', 't'],
|
||||||
|
priority: PREFERENCES_PRIORITY,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'preferences/dark-theme',
|
id: 'preferences/dark-theme',
|
||||||
name: 'Dark',
|
name: t('command-palette.action.dark-theme', 'Dark'),
|
||||||
keywords: 'dark theme',
|
keywords: 'dark theme',
|
||||||
section: '',
|
|
||||||
perform: () => changeTheme('dark'),
|
perform: () => changeTheme('dark'),
|
||||||
parent: 'preferences/theme',
|
parent: 'preferences/theme',
|
||||||
|
priority: PREFERENCES_PRIORITY,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'preferences/light-theme',
|
id: 'preferences/light-theme',
|
||||||
name: 'Light',
|
name: t('command-palette.action.light-theme', 'Light'),
|
||||||
keywords: 'light theme',
|
keywords: 'light theme',
|
||||||
section: '',
|
|
||||||
perform: () => changeTheme('light'),
|
perform: () => changeTheme('light'),
|
||||||
parent: 'preferences/theme',
|
parent: 'preferences/theme',
|
||||||
|
priority: PREFERENCES_PRIORITY,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -100,13 +88,3 @@ export default (navBarTree: NavModelItem[]) => {
|
|||||||
|
|
||||||
return [...globalActions, ...navBarActions];
|
return [...globalActions, ...navBarActions];
|
||||||
};
|
};
|
||||||
|
|
||||||
function iconForNavItem(navItem: NavModelItem) {
|
|
||||||
if (navItem.icon && isIconName(navItem.icon)) {
|
|
||||||
return <Icon name={navItem.icon} size="md" />;
|
|
||||||
} else if (navItem.img) {
|
|
||||||
return <img alt="" src={navItem.img} style={{ width: 16, height: 16 }} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
50
public/app/features/commandPalette/actions/useActions.ts
Normal file
50
public/app/features/commandPalette/actions/useActions.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import debounce from 'debounce-promise';
|
||||||
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { useSelector } from 'app/types';
|
||||||
|
|
||||||
|
import { CommandPaletteAction } from '../types';
|
||||||
|
|
||||||
|
import { getDashboardSearchResultActions, getRecentDashboardActions } from './dashboardActions';
|
||||||
|
import getStaticActions from './staticActions';
|
||||||
|
|
||||||
|
const debouncedDashboardSearch = debounce(getDashboardSearchResultActions, 200);
|
||||||
|
|
||||||
|
export default function useActions(searchQuery: string, isShowing: boolean) {
|
||||||
|
const [staticActions, setStaticActions] = useState<CommandPaletteAction[]>([]);
|
||||||
|
const [dashboardResultActions, setDashboardResultActions] = useState<CommandPaletteAction[]>([]);
|
||||||
|
|
||||||
|
const { navBarTree } = useSelector((state) => {
|
||||||
|
return {
|
||||||
|
navBarTree: state.navBarTree,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load standard static actions
|
||||||
|
useEffect(() => {
|
||||||
|
const staticActionsResp = getStaticActions(navBarTree);
|
||||||
|
setStaticActions(staticActionsResp);
|
||||||
|
}, [navBarTree]);
|
||||||
|
|
||||||
|
// Load recent dashboards - we don't want them to reload when the nav tree changes
|
||||||
|
useEffect(() => {
|
||||||
|
getRecentDashboardActions()
|
||||||
|
.then((recentDashboardActions) => setStaticActions((v) => [...v, ...recentDashboardActions]))
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('Error loading recent dashboard actions', err);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Hit dashboards API
|
||||||
|
useEffect(() => {
|
||||||
|
if (isShowing && searchQuery.length > 0) {
|
||||||
|
debouncedDashboardSearch(searchQuery).then((resultActions) => {
|
||||||
|
setDashboardResultActions(resultActions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [isShowing, searchQuery]);
|
||||||
|
|
||||||
|
const actions = useMemo(() => [...staticActions, ...dashboardResultActions], [staticActions, dashboardResultActions]);
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
18
public/app/features/commandPalette/types.ts
Normal file
18
public/app/features/commandPalette/types.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Action } from 'kbar';
|
||||||
|
|
||||||
|
type NotNullable<T> = Exclude<T, null | undefined>;
|
||||||
|
|
||||||
|
// Create our own action type to make priority mandatory.
|
||||||
|
// Parent actions require a section, but not child actions
|
||||||
|
export type CommandPaletteAction = RootCommandPaletteAction | ChildCommandPaletteAction;
|
||||||
|
|
||||||
|
type RootCommandPaletteAction = Omit<Action, 'parent'> & {
|
||||||
|
section: NotNullable<Action['section']>;
|
||||||
|
priority: NotNullable<Action['priority']>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ChildCommandPaletteAction = Action & {
|
||||||
|
parent: NotNullable<Action['parent']>;
|
||||||
|
|
||||||
|
priority: NotNullable<Action['priority']>;
|
||||||
|
};
|
4
public/app/features/commandPalette/values.ts
Normal file
4
public/app/features/commandPalette/values.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export const RECENT_DASHBOARDS_PRORITY = 4;
|
||||||
|
export const DEFAULT_PRIORITY = 3;
|
||||||
|
export const PREFERENCES_PRIORITY = 2;
|
||||||
|
export const SEARCH_RESULTS_PRORITY = 1; // Dynamic actions should be below static ones so the list doesn't 'jump' when they come in
|
@ -5,6 +5,21 @@
|
|||||||
"success": "Kopiert"
|
"success": "Kopiert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"command-palette": {
|
||||||
|
"action": {
|
||||||
|
"change-theme": "",
|
||||||
|
"dark-theme": "",
|
||||||
|
"light-theme": "",
|
||||||
|
"search": ""
|
||||||
|
},
|
||||||
|
"section": {
|
||||||
|
"actions": "",
|
||||||
|
"dashboard-search-results": "",
|
||||||
|
"pages": "",
|
||||||
|
"preferences": "",
|
||||||
|
"recent-dashboards": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"locale": {
|
"locale": {
|
||||||
"default": "Standard"
|
"default": "Standard"
|
||||||
@ -83,7 +98,6 @@
|
|||||||
"datasource-onboarding": {
|
"datasource-onboarding": {
|
||||||
"contact-admin": "Bitte wenden Sie sich an Ihren Administrator, um die Datenquellen zu konfigurieren.",
|
"contact-admin": "Bitte wenden Sie sich an Ihren Administrator, um die Datenquellen zu konfigurieren.",
|
||||||
"explanation": "Um Ihre Daten zu visualisieren, müssen Sie sie zunächst verknüpfen.",
|
"explanation": "Um Ihre Daten zu visualisieren, müssen Sie sie zunächst verknüpfen.",
|
||||||
"logo": "Logo für die Datenquelle {{datasourceName}}",
|
|
||||||
"new-dashboard": "Neues Dashboard",
|
"new-dashboard": "Neues Dashboard",
|
||||||
"preferred": "Verbinden Sie Ihre bevorzugte Datenquelle:",
|
"preferred": "Verbinden Sie Ihre bevorzugte Datenquelle:",
|
||||||
"sampleData": "Oder erstellen Sie ein neues Dashboard mit Beispieldaten",
|
"sampleData": "Oder erstellen Sie ein neues Dashboard mit Beispieldaten",
|
||||||
|
@ -5,6 +5,21 @@
|
|||||||
"success": "Copied"
|
"success": "Copied"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"command-palette": {
|
||||||
|
"action": {
|
||||||
|
"change-theme": "Change theme...",
|
||||||
|
"dark-theme": "Dark",
|
||||||
|
"light-theme": "Light",
|
||||||
|
"search": "Search"
|
||||||
|
},
|
||||||
|
"section": {
|
||||||
|
"actions": "Actions",
|
||||||
|
"dashboard-search-results": "Dashboards",
|
||||||
|
"pages": "Pages",
|
||||||
|
"preferences": "Preferences",
|
||||||
|
"recent-dashboards": "Recently viewed dashboards"
|
||||||
|
}
|
||||||
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"locale": {
|
"locale": {
|
||||||
"default": "Default"
|
"default": "Default"
|
||||||
@ -83,7 +98,6 @@
|
|||||||
"datasource-onboarding": {
|
"datasource-onboarding": {
|
||||||
"contact-admin": "Please contact your administrator to configure data sources.",
|
"contact-admin": "Please contact your administrator to configure data sources.",
|
||||||
"explanation": "To visualize your data, you'll need to connect it first.",
|
"explanation": "To visualize your data, you'll need to connect it first.",
|
||||||
"logo": "Logo for {{datasourceName}} data source",
|
|
||||||
"new-dashboard": "New dashboard",
|
"new-dashboard": "New dashboard",
|
||||||
"preferred": "Connect your preferred data source:",
|
"preferred": "Connect your preferred data source:",
|
||||||
"sampleData": "Or set up a new dashboard with sample data",
|
"sampleData": "Or set up a new dashboard with sample data",
|
||||||
@ -314,7 +328,7 @@
|
|||||||
},
|
},
|
||||||
"support-bundles": {
|
"support-bundles": {
|
||||||
"subtitle": "Download support bundles",
|
"subtitle": "Download support bundles",
|
||||||
"title": "Support Bundles"
|
"title": "Support bundles"
|
||||||
},
|
},
|
||||||
"teams": {
|
"teams": {
|
||||||
"subtitle": "Groups of users that have common dashboard and permission needs",
|
"subtitle": "Groups of users that have common dashboard and permission needs",
|
||||||
|
@ -5,6 +5,21 @@
|
|||||||
"success": "Copiado"
|
"success": "Copiado"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"command-palette": {
|
||||||
|
"action": {
|
||||||
|
"change-theme": "",
|
||||||
|
"dark-theme": "",
|
||||||
|
"light-theme": "",
|
||||||
|
"search": ""
|
||||||
|
},
|
||||||
|
"section": {
|
||||||
|
"actions": "",
|
||||||
|
"dashboard-search-results": "",
|
||||||
|
"pages": "",
|
||||||
|
"preferences": "",
|
||||||
|
"recent-dashboards": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"locale": {
|
"locale": {
|
||||||
"default": "Por defecto"
|
"default": "Por defecto"
|
||||||
@ -83,7 +98,6 @@
|
|||||||
"datasource-onboarding": {
|
"datasource-onboarding": {
|
||||||
"contact-admin": "Póngase en contacto con su administrador para configurar las fuentes de datos.",
|
"contact-admin": "Póngase en contacto con su administrador para configurar las fuentes de datos.",
|
||||||
"explanation": "Para visualizar sus datos, primero tendrá que conectar una fuente.",
|
"explanation": "Para visualizar sus datos, primero tendrá que conectar una fuente.",
|
||||||
"logo": "Logo para la fuente de datos {{datasourceName}}",
|
|
||||||
"new-dashboard": "Nuevo panel de control",
|
"new-dashboard": "Nuevo panel de control",
|
||||||
"preferred": "Conecte su fuente de datos preferida:",
|
"preferred": "Conecte su fuente de datos preferida:",
|
||||||
"sampleData": "O configure un nuevo panel de control con datos de muestra",
|
"sampleData": "O configure un nuevo panel de control con datos de muestra",
|
||||||
|
@ -5,6 +5,21 @@
|
|||||||
"success": "Copié"
|
"success": "Copié"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"command-palette": {
|
||||||
|
"action": {
|
||||||
|
"change-theme": "",
|
||||||
|
"dark-theme": "",
|
||||||
|
"light-theme": "",
|
||||||
|
"search": ""
|
||||||
|
},
|
||||||
|
"section": {
|
||||||
|
"actions": "",
|
||||||
|
"dashboard-search-results": "",
|
||||||
|
"pages": "",
|
||||||
|
"preferences": "",
|
||||||
|
"recent-dashboards": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"locale": {
|
"locale": {
|
||||||
"default": "Par défaut"
|
"default": "Par défaut"
|
||||||
@ -21,7 +36,7 @@
|
|||||||
"query-tab": "Requête",
|
"query-tab": "Requête",
|
||||||
"stats-tab": "Statistiques",
|
"stats-tab": "Statistiques",
|
||||||
"subtitle": "{{queryCount}} requêtes avec un délai total de requête de {{formatted}}",
|
"subtitle": "{{queryCount}} requêtes avec un délai total de requête de {{formatted}}",
|
||||||
"title": "Inspecter : {{panelTitle}}"
|
"title": "Inspecter\u00a0: {{panelTitle}}"
|
||||||
},
|
},
|
||||||
"inspect-data": {
|
"inspect-data": {
|
||||||
"data-options": "Options de données",
|
"data-options": "Options de données",
|
||||||
@ -51,7 +66,7 @@
|
|||||||
"panel-json-description": "Le modèle enregistré dans le tableau de bord JSON qui configure comment tout fonctionne.",
|
"panel-json-description": "Le modèle enregistré dans le tableau de bord JSON qui configure comment tout fonctionne.",
|
||||||
"panel-json-label": "Panneau JSON",
|
"panel-json-label": "Panneau JSON",
|
||||||
"select-source": "Sélectionner la source",
|
"select-source": "Sélectionner la source",
|
||||||
"unknown": "Objet inconnu : {{show}}"
|
"unknown": "Objet inconnu\u00a0: {{show}}"
|
||||||
},
|
},
|
||||||
"inspect-meta": {
|
"inspect-meta": {
|
||||||
"no-inspector": "Pas d'inspecteur de métadonnées"
|
"no-inspector": "Pas d'inspecteur de métadonnées"
|
||||||
@ -83,9 +98,8 @@
|
|||||||
"datasource-onboarding": {
|
"datasource-onboarding": {
|
||||||
"contact-admin": "Veuillez contacter votre administrateur pour configurer les sources de données.",
|
"contact-admin": "Veuillez contacter votre administrateur pour configurer les sources de données.",
|
||||||
"explanation": "Pour visualiser vos données, vous devrez d’abord les connecter.",
|
"explanation": "Pour visualiser vos données, vous devrez d’abord les connecter.",
|
||||||
"logo": "Logo pour la source de données {{datasourceName}}",
|
|
||||||
"new-dashboard": "Nouveau tableau de bord",
|
"new-dashboard": "Nouveau tableau de bord",
|
||||||
"preferred": "Connectez votre source de données préférée :",
|
"preferred": "Connectez votre source de données préférée\u00a0:",
|
||||||
"sampleData": "Ou établissez un nouveau tableau de bord avec des exemples de données",
|
"sampleData": "Ou établissez un nouveau tableau de bord avec des exemples de données",
|
||||||
"viewAll": "Afficher tout",
|
"viewAll": "Afficher tout",
|
||||||
"welcome": "Bienvenue aux tableaux de bord Grafana !"
|
"welcome": "Bienvenue aux tableaux de bord Grafana !"
|
||||||
@ -105,7 +119,7 @@
|
|||||||
},
|
},
|
||||||
"library-panels": {
|
"library-panels": {
|
||||||
"save": {
|
"save": {
|
||||||
"error": "Erreur lors de l'enregistrement du panneau de bibliothèque : \"{{errorMsg}}\"",
|
"error": "Erreur lors de l'enregistrement du panneau de bibliothèque\u00a0: \"{{errorMsg}}\"",
|
||||||
"success": "Panneau de bibliothèque enregistré"
|
"success": "Panneau de bibliothèque enregistré"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -421,7 +435,7 @@
|
|||||||
"info-text-1": "Un instantané est un moyen instantané de partager publiquement un tableau de bord interactif. Lors de la création, nous supprimons les données sensibles telles que les requêtes (métrique, modèle et annotation) et les liens du panneau, pour ne laisser que les métriques visibles et les noms de séries intégrés dans votre tableau de bord.",
|
"info-text-1": "Un instantané est un moyen instantané de partager publiquement un tableau de bord interactif. Lors de la création, nous supprimons les données sensibles telles que les requêtes (métrique, modèle et annotation) et les liens du panneau, pour ne laisser que les métriques visibles et les noms de séries intégrés dans votre tableau de bord.",
|
||||||
"info-text-2": "N'oubliez pas que votre instantané <1>peut être consulté par une personne</1> qui dispose du lien et qui peut accéder à l'URL. Partagez judicieusement.",
|
"info-text-2": "N'oubliez pas que votre instantané <1>peut être consulté par une personne</1> qui dispose du lien et qui peut accéder à l'URL. Partagez judicieusement.",
|
||||||
"local-button": "Instantané local",
|
"local-button": "Instantané local",
|
||||||
"mistake-message": "Avez-vous commis une erreur ? ",
|
"mistake-message": "Avez-vous commis une erreur\u00a0? ",
|
||||||
"name": "Nom de l'instantané",
|
"name": "Nom de l'instantané",
|
||||||
"timeout": "Délai d’expiration (secondes)",
|
"timeout": "Délai d’expiration (secondes)",
|
||||||
"timeout-description": "Vous devrez peut-être configurer la valeur du délai d'expiration si la collecte des métriques de votre tableau de bord prend beaucoup de temps.",
|
"timeout-description": "Vous devrez peut-être configurer la valeur du délai d'expiration si la collecte des métriques de votre tableau de bord prend beaucoup de temps.",
|
||||||
|
@ -5,6 +5,21 @@
|
|||||||
"success": "Cőpįęđ"
|
"success": "Cőpįęđ"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"command-palette": {
|
||||||
|
"action": {
|
||||||
|
"change-theme": "Cĥäʼnģę ŧĥęmę...",
|
||||||
|
"dark-theme": "Đäřĸ",
|
||||||
|
"light-theme": "Ŀįģĥŧ",
|
||||||
|
"search": "Ŝęäřčĥ"
|
||||||
|
},
|
||||||
|
"section": {
|
||||||
|
"actions": "Åčŧįőʼnş",
|
||||||
|
"dashboard-search-results": "Đäşĥþőäřđş",
|
||||||
|
"pages": "Päģęş",
|
||||||
|
"preferences": "Přęƒęřęʼnčęş",
|
||||||
|
"recent-dashboards": "Ŗęčęʼnŧľy vįęŵęđ đäşĥþőäřđş"
|
||||||
|
}
|
||||||
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"locale": {
|
"locale": {
|
||||||
"default": "Đęƒäūľŧ"
|
"default": "Đęƒäūľŧ"
|
||||||
@ -83,7 +98,6 @@
|
|||||||
"datasource-onboarding": {
|
"datasource-onboarding": {
|
||||||
"contact-admin": "Pľęäşę čőʼnŧäčŧ yőūř äđmįʼnįşŧřäŧőř ŧő čőʼnƒįģūřę đäŧä şőūřčęş.",
|
"contact-admin": "Pľęäşę čőʼnŧäčŧ yőūř äđmįʼnįşŧřäŧőř ŧő čőʼnƒįģūřę đäŧä şőūřčęş.",
|
||||||
"explanation": "Ŧő vįşūäľįžę yőūř đäŧä, yőū'ľľ ʼnęęđ ŧő čőʼnʼnęčŧ įŧ ƒįřşŧ.",
|
"explanation": "Ŧő vįşūäľįžę yőūř đäŧä, yőū'ľľ ʼnęęđ ŧő čőʼnʼnęčŧ įŧ ƒįřşŧ.",
|
||||||
"logo": "Ŀőģő ƒőř {{datasourceName}} đäŧä şőūřčę",
|
|
||||||
"new-dashboard": "Ńęŵ đäşĥþőäřđ",
|
"new-dashboard": "Ńęŵ đäşĥþőäřđ",
|
||||||
"preferred": "Cőʼnʼnęčŧ yőūř přęƒęřřęđ đäŧä şőūřčę:",
|
"preferred": "Cőʼnʼnęčŧ yőūř přęƒęřřęđ đäŧä şőūřčę:",
|
||||||
"sampleData": "Øř şęŧ ūp ä ʼnęŵ đäşĥþőäřđ ŵįŧĥ şämpľę đäŧä",
|
"sampleData": "Øř şęŧ ūp ä ʼnęŵ đäşĥþőäřđ ŵįŧĥ şämpľę đäŧä",
|
||||||
@ -314,7 +328,7 @@
|
|||||||
},
|
},
|
||||||
"support-bundles": {
|
"support-bundles": {
|
||||||
"subtitle": "Đőŵʼnľőäđ şūppőřŧ þūʼnđľęş",
|
"subtitle": "Đőŵʼnľőäđ şūppőřŧ þūʼnđľęş",
|
||||||
"title": "Ŝūppőřŧ ßūʼnđľęş"
|
"title": "Ŝūppőřŧ þūʼnđľęş"
|
||||||
},
|
},
|
||||||
"teams": {
|
"teams": {
|
||||||
"subtitle": "Ğřőūpş őƒ ūşęřş ŧĥäŧ ĥävę čőmmőʼn đäşĥþőäřđ äʼnđ pęřmįşşįőʼn ʼnęęđş",
|
"subtitle": "Ğřőūpş őƒ ūşęřş ŧĥäŧ ĥävę čőmmőʼn đäşĥþőäřđ äʼnđ pęřmįşşįőʼn ʼnęęđş",
|
||||||
|
@ -5,6 +5,21 @@
|
|||||||
"success": ""
|
"success": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"command-palette": {
|
||||||
|
"action": {
|
||||||
|
"change-theme": "",
|
||||||
|
"dark-theme": "",
|
||||||
|
"light-theme": "",
|
||||||
|
"search": ""
|
||||||
|
},
|
||||||
|
"section": {
|
||||||
|
"actions": "",
|
||||||
|
"dashboard-search-results": "",
|
||||||
|
"pages": "",
|
||||||
|
"preferences": "",
|
||||||
|
"recent-dashboards": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"locale": {
|
"locale": {
|
||||||
"default": "默认"
|
"default": "默认"
|
||||||
@ -83,7 +98,6 @@
|
|||||||
"datasource-onboarding": {
|
"datasource-onboarding": {
|
||||||
"contact-admin": "",
|
"contact-admin": "",
|
||||||
"explanation": "",
|
"explanation": "",
|
||||||
"logo": "",
|
|
||||||
"new-dashboard": "",
|
"new-dashboard": "",
|
||||||
"preferred": "",
|
"preferred": "",
|
||||||
"sampleData": "",
|
"sampleData": "",
|
||||||
|
Loading…
Reference in New Issue
Block a user