mirror of
https://github.com/grafana/grafana.git
synced 2024-11-25 10:20:29 -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() {
|
||||
return config.featureToggles.commandPalette && !config.isPublicDashboardView;
|
||||
const isLoginPage = locationService.getLocation().pathname === '/login';
|
||||
return config.featureToggles.commandPalette && !config.isPublicDashboardView && !isLoginPage;
|
||||
}
|
||||
|
||||
searchBarEnabled() {
|
||||
|
@ -9,21 +9,18 @@ import {
|
||||
KBarResults,
|
||||
KBarSearch,
|
||||
useMatches,
|
||||
Action,
|
||||
VisualState,
|
||||
useRegisterActions,
|
||||
useKBar,
|
||||
} from 'kbar';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { reportInteraction, locationService } from '@grafana/runtime';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
import { useSelector } from 'app/types';
|
||||
|
||||
import { ResultItem } from './ResultItem';
|
||||
import getDashboardNavActions from './actions/dashboard.nav.actions';
|
||||
import getGlobalActions from './actions/global.static.actions';
|
||||
import useActions from './actions/useActions';
|
||||
|
||||
/**
|
||||
* Wrap all the components from KBar here.
|
||||
@ -32,18 +29,14 @@ import getGlobalActions from './actions/global.static.actions';
|
||||
|
||||
export const CommandPalette = () => {
|
||||
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) => {
|
||||
return {
|
||||
navBarTree: state.navBarTree,
|
||||
};
|
||||
});
|
||||
const { query, showing, searchQuery } = useKBar((state) => ({
|
||||
showing: state.visualState === VisualState.showing,
|
||||
searchQuery: state.searchQuery,
|
||||
}));
|
||||
|
||||
const actions = useActions(searchQuery, showing);
|
||||
useRegisterActions(actions, [actions]);
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const { overlayProps } = useOverlay(
|
||||
@ -52,26 +45,10 @@ export const CommandPalette = () => {
|
||||
);
|
||||
const { dialogProps } = useDialog({}, ref);
|
||||
|
||||
// Report interaction when opened
|
||||
useEffect(() => {
|
||||
if (isNotLogin) {
|
||||
const staticActionsResp = getGlobalActions(navBarTree);
|
||||
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]);
|
||||
showing && reportInteraction('command_palette_opened');
|
||||
}, [showing]);
|
||||
|
||||
return actions.length > 0 ? (
|
||||
<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 React from 'react';
|
||||
|
||||
import { isIconName, locationUtil, NavModelItem } from '@grafana/data';
|
||||
import { locationUtil, NavModelItem } from '@grafana/data';
|
||||
import { locationService } from '@grafana/runtime';
|
||||
import { Icon } from '@grafana/ui';
|
||||
import { t } from 'app/core/internationalization';
|
||||
import { changeTheme } from 'app/core/services/theme';
|
||||
|
||||
const SECTION_PAGES = 'Pages';
|
||||
const SECTION_ACTIONS = 'Actions';
|
||||
const SECTION_PREFERENCES = 'Preferences';
|
||||
import { CommandPaletteAction } from '../types';
|
||||
import { DEFAULT_PRIORITY, PREFERENCES_PRIORITY } from '../values';
|
||||
|
||||
export interface NavBarActions {
|
||||
url: string;
|
||||
actions: Action[];
|
||||
}
|
||||
// We reuse this, but translations cannot be in module scope (t must be called after i18n has set up,)
|
||||
const getPagesSectionTranslation = () => t('command-palette.section.pages', 'Pages');
|
||||
|
||||
// TODO: Clean this once ID is mandatory on nav items
|
||||
function idForNavItem(navItem: NavModelItem) {
|
||||
return 'navModel.' + navItem.id ?? navItem.url ?? navItem.text ?? navItem.subTitle;
|
||||
}
|
||||
|
||||
function navTreeToActions(navTree: NavModelItem[], parent?: NavModelItem): Action[] {
|
||||
const navActions: Action[] = [];
|
||||
function navTreeToActions(navTree: NavModelItem[], parent?: NavModelItem): CommandPaletteAction[] {
|
||||
const navActions: CommandPaletteAction[] = [];
|
||||
|
||||
for (const navItem of navTree) {
|
||||
const { url, text, isCreateAction, children } = navItem;
|
||||
@ -31,15 +25,15 @@ function navTreeToActions(navTree: NavModelItem[], parent?: NavModelItem): Actio
|
||||
continue;
|
||||
}
|
||||
|
||||
const action: Action = {
|
||||
const section = isCreateAction ? t('command-palette.section.actions', 'Actions') : getPagesSectionTranslation();
|
||||
|
||||
const action = {
|
||||
id: idForNavItem(navItem),
|
||||
name: text, // TODO: translate
|
||||
section: isCreateAction ? SECTION_ACTIONS : SECTION_PAGES,
|
||||
section: section,
|
||||
perform: url ? () => locationService.push(locationUtil.stripBaseFromUrl(url)) : undefined,
|
||||
parent: parent && idForNavItem(parent),
|
||||
|
||||
// Only show icons for top level items
|
||||
icon: !parent && iconForNavItem(navItem),
|
||||
priority: DEFAULT_PRIORITY,
|
||||
};
|
||||
|
||||
navActions.push(action);
|
||||
@ -53,46 +47,40 @@ function navTreeToActions(navTree: NavModelItem[], parent?: NavModelItem): Actio
|
||||
return navActions;
|
||||
}
|
||||
|
||||
export default (navBarTree: NavModelItem[]) => {
|
||||
const globalActions: Action[] = [
|
||||
{
|
||||
// TODO: Figure out what section, if any, to put this in
|
||||
id: 'go/dashboard',
|
||||
name: 'Dashboards...',
|
||||
keywords: 'navigate',
|
||||
priority: Priority.NORMAL,
|
||||
},
|
||||
export default (navBarTree: NavModelItem[]): CommandPaletteAction[] => {
|
||||
const globalActions: CommandPaletteAction[] = [
|
||||
{
|
||||
id: 'go/search',
|
||||
name: 'Search',
|
||||
name: t('command-palette.action.search', 'Search'),
|
||||
keywords: 'navigate',
|
||||
icon: <Icon name="search" size="md" />,
|
||||
perform: () => locationService.push('?search=open'),
|
||||
section: SECTION_PAGES,
|
||||
section: t('command-palette.section.pages', 'Pages'),
|
||||
shortcut: ['s', 'o'],
|
||||
priority: DEFAULT_PRIORITY,
|
||||
},
|
||||
{
|
||||
id: 'preferences/theme',
|
||||
name: 'Change theme...',
|
||||
name: t('command-palette.action.change-theme', 'Change theme...'),
|
||||
keywords: 'interface color dark light',
|
||||
section: SECTION_PREFERENCES,
|
||||
section: t('command-palette.section.preferences', 'Preferences'),
|
||||
shortcut: ['c', 't'],
|
||||
priority: PREFERENCES_PRIORITY,
|
||||
},
|
||||
{
|
||||
id: 'preferences/dark-theme',
|
||||
name: 'Dark',
|
||||
name: t('command-palette.action.dark-theme', 'Dark'),
|
||||
keywords: 'dark theme',
|
||||
section: '',
|
||||
perform: () => changeTheme('dark'),
|
||||
parent: 'preferences/theme',
|
||||
priority: PREFERENCES_PRIORITY,
|
||||
},
|
||||
{
|
||||
id: 'preferences/light-theme',
|
||||
name: 'Light',
|
||||
name: t('command-palette.action.light-theme', 'Light'),
|
||||
keywords: 'light theme',
|
||||
section: '',
|
||||
perform: () => changeTheme('light'),
|
||||
parent: 'preferences/theme',
|
||||
priority: PREFERENCES_PRIORITY,
|
||||
},
|
||||
];
|
||||
|
||||
@ -100,13 +88,3 @@ export default (navBarTree: NavModelItem[]) => {
|
||||
|
||||
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"
|
||||
}
|
||||
},
|
||||
"command-palette": {
|
||||
"action": {
|
||||
"change-theme": "",
|
||||
"dark-theme": "",
|
||||
"light-theme": "",
|
||||
"search": ""
|
||||
},
|
||||
"section": {
|
||||
"actions": "",
|
||||
"dashboard-search-results": "",
|
||||
"pages": "",
|
||||
"preferences": "",
|
||||
"recent-dashboards": ""
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"locale": {
|
||||
"default": "Standard"
|
||||
@ -83,7 +98,6 @@
|
||||
"datasource-onboarding": {
|
||||
"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.",
|
||||
"logo": "Logo für die Datenquelle {{datasourceName}}",
|
||||
"new-dashboard": "Neues Dashboard",
|
||||
"preferred": "Verbinden Sie Ihre bevorzugte Datenquelle:",
|
||||
"sampleData": "Oder erstellen Sie ein neues Dashboard mit Beispieldaten",
|
||||
|
@ -5,6 +5,21 @@
|
||||
"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": {
|
||||
"locale": {
|
||||
"default": "Default"
|
||||
@ -83,7 +98,6 @@
|
||||
"datasource-onboarding": {
|
||||
"contact-admin": "Please contact your administrator to configure data sources.",
|
||||
"explanation": "To visualize your data, you'll need to connect it first.",
|
||||
"logo": "Logo for {{datasourceName}} data source",
|
||||
"new-dashboard": "New dashboard",
|
||||
"preferred": "Connect your preferred data source:",
|
||||
"sampleData": "Or set up a new dashboard with sample data",
|
||||
@ -314,7 +328,7 @@
|
||||
},
|
||||
"support-bundles": {
|
||||
"subtitle": "Download support bundles",
|
||||
"title": "Support Bundles"
|
||||
"title": "Support bundles"
|
||||
},
|
||||
"teams": {
|
||||
"subtitle": "Groups of users that have common dashboard and permission needs",
|
||||
|
@ -5,6 +5,21 @@
|
||||
"success": "Copiado"
|
||||
}
|
||||
},
|
||||
"command-palette": {
|
||||
"action": {
|
||||
"change-theme": "",
|
||||
"dark-theme": "",
|
||||
"light-theme": "",
|
||||
"search": ""
|
||||
},
|
||||
"section": {
|
||||
"actions": "",
|
||||
"dashboard-search-results": "",
|
||||
"pages": "",
|
||||
"preferences": "",
|
||||
"recent-dashboards": ""
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"locale": {
|
||||
"default": "Por defecto"
|
||||
@ -83,7 +98,6 @@
|
||||
"datasource-onboarding": {
|
||||
"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.",
|
||||
"logo": "Logo para la fuente de datos {{datasourceName}}",
|
||||
"new-dashboard": "Nuevo panel de control",
|
||||
"preferred": "Conecte su fuente de datos preferida:",
|
||||
"sampleData": "O configure un nuevo panel de control con datos de muestra",
|
||||
|
@ -5,6 +5,21 @@
|
||||
"success": "Copié"
|
||||
}
|
||||
},
|
||||
"command-palette": {
|
||||
"action": {
|
||||
"change-theme": "",
|
||||
"dark-theme": "",
|
||||
"light-theme": "",
|
||||
"search": ""
|
||||
},
|
||||
"section": {
|
||||
"actions": "",
|
||||
"dashboard-search-results": "",
|
||||
"pages": "",
|
||||
"preferences": "",
|
||||
"recent-dashboards": ""
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"locale": {
|
||||
"default": "Par défaut"
|
||||
@ -21,7 +36,7 @@
|
||||
"query-tab": "Requête",
|
||||
"stats-tab": "Statistiques",
|
||||
"subtitle": "{{queryCount}} requêtes avec un délai total de requête de {{formatted}}",
|
||||
"title": "Inspecter : {{panelTitle}}"
|
||||
"title": "Inspecter\u00a0: {{panelTitle}}"
|
||||
},
|
||||
"inspect-data": {
|
||||
"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-label": "Panneau JSON",
|
||||
"select-source": "Sélectionner la source",
|
||||
"unknown": "Objet inconnu : {{show}}"
|
||||
"unknown": "Objet inconnu\u00a0: {{show}}"
|
||||
},
|
||||
"inspect-meta": {
|
||||
"no-inspector": "Pas d'inspecteur de métadonnées"
|
||||
@ -83,9 +98,8 @@
|
||||
"datasource-onboarding": {
|
||||
"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.",
|
||||
"logo": "Logo pour la source de données {{datasourceName}}",
|
||||
"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",
|
||||
"viewAll": "Afficher tout",
|
||||
"welcome": "Bienvenue aux tableaux de bord Grafana !"
|
||||
@ -105,7 +119,7 @@
|
||||
},
|
||||
"library-panels": {
|
||||
"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é"
|
||||
}
|
||||
},
|
||||
@ -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-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",
|
||||
"mistake-message": "Avez-vous commis une erreur ? ",
|
||||
"mistake-message": "Avez-vous commis une erreur\u00a0? ",
|
||||
"name": "Nom de l'instantané",
|
||||
"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.",
|
||||
|
@ -5,6 +5,21 @@
|
||||
"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": {
|
||||
"locale": {
|
||||
"default": "Đęƒäūľŧ"
|
||||
@ -83,7 +98,6 @@
|
||||
"datasource-onboarding": {
|
||||
"contact-admin": "Pľęäşę čőʼnŧäčŧ yőūř äđmįʼnįşŧřäŧőř ŧő čőʼnƒįģūřę đäŧä şőūřčęş.",
|
||||
"explanation": "Ŧő vįşūäľįžę yőūř đäŧä, yőū'ľľ ʼnęęđ ŧő čőʼnʼnęčŧ įŧ ƒįřşŧ.",
|
||||
"logo": "Ŀőģő ƒőř {{datasourceName}} đäŧä şőūřčę",
|
||||
"new-dashboard": "Ńęŵ đäşĥþőäřđ",
|
||||
"preferred": "Cőʼnʼnęčŧ yőūř přęƒęřřęđ đäŧä şőūřčę:",
|
||||
"sampleData": "Øř şęŧ ūp ä ʼnęŵ đäşĥþőäřđ ŵįŧĥ şämpľę đäŧä",
|
||||
@ -314,7 +328,7 @@
|
||||
},
|
||||
"support-bundles": {
|
||||
"subtitle": "Đőŵʼnľőäđ şūppőřŧ þūʼnđľęş",
|
||||
"title": "Ŝūppőřŧ ßūʼnđľęş"
|
||||
"title": "Ŝūppőřŧ þūʼnđľęş"
|
||||
},
|
||||
"teams": {
|
||||
"subtitle": "Ğřőūpş őƒ ūşęřş ŧĥäŧ ĥävę čőmmőʼn đäşĥþőäřđ äʼnđ pęřmįşşįőʼn ʼnęęđş",
|
||||
|
@ -5,6 +5,21 @@
|
||||
"success": ""
|
||||
}
|
||||
},
|
||||
"command-palette": {
|
||||
"action": {
|
||||
"change-theme": "",
|
||||
"dark-theme": "",
|
||||
"light-theme": "",
|
||||
"search": ""
|
||||
},
|
||||
"section": {
|
||||
"actions": "",
|
||||
"dashboard-search-results": "",
|
||||
"pages": "",
|
||||
"preferences": "",
|
||||
"recent-dashboards": ""
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"locale": {
|
||||
"default": "默认"
|
||||
@ -83,7 +98,6 @@
|
||||
"datasource-onboarding": {
|
||||
"contact-admin": "",
|
||||
"explanation": "",
|
||||
"logo": "",
|
||||
"new-dashboard": "",
|
||||
"preferred": "",
|
||||
"sampleData": "",
|
||||
|
Loading…
Reference in New Issue
Block a user