diff --git a/public/app/features/browse-dashboards/components/RecentlyViewedDashboards.tsx b/public/app/features/browse-dashboards/components/RecentlyViewedDashboards.tsx
index 29a4dce6557..ebfbd1d0899 100644
--- a/public/app/features/browse-dashboards/components/RecentlyViewedDashboards.tsx
+++ b/public/app/features/browse-dashboards/components/RecentlyViewedDashboards.tsx
@@ -4,7 +4,9 @@ import { useAsync } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data';
import { t, Trans } from '@grafana/i18n';
import { evaluateBooleanFlag } from '@grafana/runtime/internal';
-import { CollapsableSection, Link, Spinner, Text, useStyles2 } from '@grafana/ui';
+import { CollapsableSection, Grid, Spinner, Text, useStyles2 } from '@grafana/ui';
+import { useDashboardLocationInfo } from 'app/features/search/hooks/useDashboardLocationInfo';
+import { DashListItem } from 'app/plugins/panel/dashlist/DashListItem';
import { getRecentlyViewedDashboards } from './utils';
@@ -19,6 +21,7 @@ export function RecentlyViewedDashboards() {
}
return getRecentlyViewedDashboards(MAX_RECENT);
}, []);
+ const { foldersByUid } = useDashboardLocationInfo(recentDashboards.length > 0);
if (!evaluateBooleanFlag('recentlyViewedDashboards', false)) {
return null;
@@ -43,35 +46,53 @@ export function RecentlyViewedDashboards() {
{t('browse-dashboards.recently-viewed.empty', 'Nothing viewed yet')}
)}
- {/* TODO: implement actual card content */}
{!loading && recentDashboards.length > 0 && (
- <>
- {recentDashboards.map((dash) => (
-
- {dash.name}
-
- ))}
- >
+
+
+ {recentDashboards.map((dash) => (
+ -
+
+
+ ))}
+
+
)}
);
}
const getStyles = (theme: GrafanaTheme2) => {
- const accent = theme.visualization.getColorByName('purple'); // or your own hex
-
return {
title: css({
- background: `linear-gradient(90deg, ${accent} 0%, #e478eaff 100%)`,
- WebkitTextFillColor: 'transparent',
- backgroundClip: 'text',
- color: 'transparent',
'& button svg': {
- color: accent,
+ color: theme.colors.primary.text,
+ },
+ h3: {
+ background: `linear-gradient(90deg, ${theme.colors.primary.text} 0%, ${theme.colors.secondary.text} 100%)`,
+ WebkitTextFillColor: 'transparent',
+ backgroundClip: 'text',
+ color: 'transparent',
},
}),
content: css({
paddingTop: theme.spacing(0),
}),
+ list: css({
+ listStyle: 'none',
+ margin: 0,
+ padding: 0,
+ display: 'grid',
+ gap: theme.spacing(2),
+ }),
+ listItem: css({
+ margin: 0,
+ }),
};
};
diff --git a/public/app/features/search/hooks/useDashboardLocationInfo.tsx b/public/app/features/search/hooks/useDashboardLocationInfo.tsx
new file mode 100644
index 00000000000..10c4f80c62e
--- /dev/null
+++ b/public/app/features/search/hooks/useDashboardLocationInfo.tsx
@@ -0,0 +1,29 @@
+import { useAsync } from 'react-use';
+
+import { getGrafanaSearcher } from 'app/features/search/service/searcher';
+import { LocationInfo } from 'app/features/search/service/types';
+
+/**
+ *
+ * @description Hook to fetch dashboard location info (folders).
+ * @returns An object containing a mapping of folder UIDs to LocationInfo, loading state, and error state.
+ */
+export function useDashboardLocationInfo(enabled: boolean) {
+ const searcher = getGrafanaSearcher();
+ const {
+ value: foldersByUid,
+ loading,
+ error,
+ } = useAsync(async (): Promise> => {
+ if (!enabled) {
+ return {};
+ }
+ return searcher.getLocationInfo();
+ }, [enabled, searcher]);
+
+ return {
+ foldersByUid: foldersByUid ?? {},
+ loading,
+ error,
+ };
+}
diff --git a/public/app/plugins/panel/dashlist/DashList.tsx b/public/app/plugins/panel/dashlist/DashList.tsx
index dab66730379..ff62a9ffd0a 100644
--- a/public/app/plugins/panel/dashlist/DashList.tsx
+++ b/public/app/plugins/panel/dashlist/DashList.tsx
@@ -4,18 +4,18 @@ import { useThrottle } from 'react-use';
import { InterpolateFunction, PanelProps, textUtil } from '@grafana/data';
import { t } from '@grafana/i18n';
-import { useStyles2, ScrollContainer, Box, Text, EmptyState, Link } from '@grafana/ui';
+import { ScrollContainer, Box, Text, EmptyState } from '@grafana/ui';
import { getConfig } from 'app/core/config';
import impressionSrv from 'app/core/services/impression_srv';
+import { useDashboardLocationInfo } from 'app/features/search/hooks/useDashboardLocationInfo';
import { getGrafanaSearcher } from 'app/features/search/service/searcher';
-import { DashboardQueryResult, LocationInfo, QueryResponse, SearchQuery } from 'app/features/search/service/types';
-import { StarToolbarButton } from 'app/features/stars/StarToolbarButton';
+import { DashboardQueryResult, QueryResponse, SearchQuery } from 'app/features/search/service/types';
+import { DashListItem } from './DashListItem';
import { Options } from './panelcfg.gen';
-import { getStyles } from './styles';
import { useDashListUrlParams } from './utils';
-type Dashboard = DashboardQueryResult & {
+export type Dashboard = DashboardQueryResult & {
isSearchResult?: boolean;
isRecent?: boolean;
isStarred?: boolean;
@@ -107,15 +107,10 @@ async function fetchDashboards(options: Options, replaceVars: InterpolateFunctio
return dashMap;
}
-async function fetchDashboardFolders() {
- return getGrafanaSearcher().getLocationInfo();
-}
-
const collator = new Intl.Collator();
export function DashList(props: PanelProps) {
const [dashboards, setDashboards] = useState(new Map());
- const [foldersTitleMap, setFoldersTitleMap] = useState>({});
const throttledRenderCount = useThrottle(props.renderCounter, 5000);
@@ -125,13 +120,7 @@ export function DashList(props: PanelProps) {
});
}, [props.options, props.replaceVariables, throttledRenderCount]);
- useEffect(() => {
- if (props.options.showFolderNames && dashboards.size > 0) {
- fetchDashboardFolders().then((locationInfo) => {
- setFoldersTitleMap(locationInfo);
- });
- }
- }, [props.options.showFolderNames, dashboards]);
+ const { foldersByUid } = useDashboardLocationInfo(props.options.showFolderNames && dashboards.size > 0);
const [starredDashboards, recentDashboards, searchedDashboards] = useMemo(() => {
const dashboardList = [...dashboards.values()];
@@ -185,7 +174,6 @@ export function DashList(props: PanelProps) {
setDashboards(updatedDashboards);
};
- const css = useStyles2(getStyles);
const urlParams = useDashListUrlParams(props);
const renderList = (dashboards: Dashboard[]) => (
@@ -194,26 +182,17 @@ export function DashList(props: PanelProps) {
let url = dash.url + urlParams;
url = getConfig().disableSanitizeHtml ? url : textUtil.sanitizeUrl(url);
- const locationInfo = showFolderNames && dash.location ? foldersTitleMap[dash.location] : undefined;
+ const locationInfo = showFolderNames && dash.location ? foldersByUid[dash.location] : undefined;
return (
-
-
- {dash.name}
- {showFolderNames && locationInfo && (
-
- {locationInfo?.name}
-
- )}
-
-
-
+
);
})}
diff --git a/public/app/plugins/panel/dashlist/DashListItem.tsx b/public/app/plugins/panel/dashlist/DashListItem.tsx
new file mode 100644
index 00000000000..9ef0ca64c3d
--- /dev/null
+++ b/public/app/plugins/panel/dashlist/DashListItem.tsx
@@ -0,0 +1,64 @@
+import { Box, Card, Icon, Link, Stack, Text, useStyles2 } from '@grafana/ui';
+import { LocationInfo } from 'app/features/search/service/types';
+import { StarToolbarButton } from 'app/features/stars/StarToolbarButton';
+
+import { Dashboard } from './DashList';
+import { getStyles } from './styles';
+
+interface Props {
+ dashboard: Dashboard;
+ url: string;
+ showFolderNames: boolean;
+ locationInfo?: LocationInfo;
+ layoutMode: 'list' | 'card';
+ onStarChange?: (id: string, isStarred: boolean) => void;
+}
+export function DashListItem({ dashboard, url, showFolderNames, locationInfo, layoutMode, onStarChange }: Props) {
+ const css = useStyles2(getStyles);
+
+ return (
+ <>
+ {layoutMode === 'list' ? (
+
+
+ {dashboard.name}
+ {showFolderNames && locationInfo && (
+
+ {locationInfo?.name}
+
+ )}
+
+
+
+ ) : (
+
+
+ {dashboard.name}
+
+
+
+ {showFolderNames && locationInfo && (
+
+
+
+ {locationInfo?.name}
+
+
+ )}
+
+ )}
+ >
+ );
+}
diff --git a/public/app/plugins/panel/dashlist/styles.ts b/public/app/plugins/panel/dashlist/styles.ts
index 56becdd0684..5e725daaf2d 100644
--- a/public/app/plugins/panel/dashlist/styles.ts
+++ b/public/app/plugins/panel/dashlist/styles.ts
@@ -1,8 +1,13 @@
import { css } from '@emotion/css';
-import { GrafanaTheme2 } from '@grafana/data';
+import { GrafanaTheme2, colorManipulator } from '@grafana/data';
export const getStyles = (theme: GrafanaTheme2) => {
+ const gradient = `linear-gradient(
+ 90deg,
+ ${colorManipulator.alpha(theme.colors.primary.text, 0.1)} 0%,
+ ${colorManipulator.alpha(theme.colors.secondary.text, 0.1)} 100%
+ )`;
return {
dashlistLink: css({
display: 'flex',
@@ -19,5 +24,22 @@ export const getStyles = (theme: GrafanaTheme2) => {
},
},
}),
+ dashlistCard: css({
+ display: 'flex',
+ flexDirection: 'column',
+ '&:hover a': {
+ color: theme.colors.text.link,
+ textDecoration: 'underline',
+ },
+ height: '100%',
+
+ '&:hover': {
+ backgroundImage: gradient,
+ color: theme.colors.text.primary,
+ },
+ }),
+ dashlistCardIcon: css({
+ marginRight: theme.spacing(0.5),
+ }),
};
};