2022-10-20 15:53:10 +02:00
|
|
|
import { css } from '@emotion/css';
|
|
|
|
|
import React, { useMemo, useState } from 'react';
|
|
|
|
|
|
2024-01-10 09:57:17 +00:00
|
|
|
import { GrafanaTheme2, PluginType } from '@grafana/data';
|
2024-03-27 12:15:12 +00:00
|
|
|
import { useStyles2, LoadingPlaceholder, EmptyState } from '@grafana/ui';
|
2023-01-18 15:34:23 +01:00
|
|
|
import { contextSrv } from 'app/core/core';
|
2024-01-10 09:57:17 +00:00
|
|
|
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
2023-06-30 11:27:19 +01:00
|
|
|
import { t } from 'app/core/internationalization';
|
2023-05-05 09:38:18 +02:00
|
|
|
import { useGetAll } from 'app/features/plugins/admin/state/hooks';
|
2023-01-18 15:34:23 +01:00
|
|
|
import { AccessControlAction } from 'app/types';
|
2022-10-20 15:53:10 +02:00
|
|
|
|
2022-11-10 11:14:23 +01:00
|
|
|
import { ROUTES } from '../../constants';
|
|
|
|
|
|
2023-01-18 15:34:23 +01:00
|
|
|
import { CardGrid, type CardGridItem } from './CardGrid';
|
2022-10-20 15:53:10 +02:00
|
|
|
import { CategoryHeader } from './CategoryHeader';
|
2023-01-18 15:34:23 +01:00
|
|
|
import { NoAccessModal } from './NoAccessModal';
|
2022-10-20 15:53:10 +02:00
|
|
|
import { Search } from './Search';
|
|
|
|
|
|
2024-01-10 09:57:17 +00:00
|
|
|
const getStyles = (theme: GrafanaTheme2) => ({
|
|
|
|
|
spacer: css({
|
|
|
|
|
height: theme.spacing(2),
|
|
|
|
|
}),
|
|
|
|
|
modal: css({
|
|
|
|
|
width: '500px',
|
|
|
|
|
}),
|
|
|
|
|
modalContent: css({
|
|
|
|
|
overflow: 'visible',
|
|
|
|
|
}),
|
2022-10-20 15:53:10 +02:00
|
|
|
});
|
|
|
|
|
|
2023-05-02 10:51:59 +02:00
|
|
|
export function AddNewConnection() {
|
2024-01-10 09:57:17 +00:00
|
|
|
const [queryParams, setQueryParams] = useQueryParams();
|
|
|
|
|
const searchTerm = queryParams.search ? String(queryParams.search) : '';
|
2023-01-18 15:34:23 +01:00
|
|
|
const [isNoAccessModalOpen, setIsNoAccessModalOpen] = useState(false);
|
|
|
|
|
const [focusedItem, setFocusedItem] = useState<CardGridItem | null>(null);
|
2022-10-20 15:53:10 +02:00
|
|
|
const styles = useStyles2(getStyles);
|
2023-01-18 15:34:23 +01:00
|
|
|
const canCreateDataSources = contextSrv.hasPermission(AccessControlAction.DataSourcesCreate);
|
2022-10-20 15:53:10 +02:00
|
|
|
|
|
|
|
|
const handleSearchChange = (e: React.FormEvent<HTMLInputElement>) => {
|
2024-01-10 09:57:17 +00:00
|
|
|
setQueryParams({
|
|
|
|
|
search: e.currentTarget.value.toLowerCase(),
|
|
|
|
|
});
|
2022-10-20 15:53:10 +02:00
|
|
|
};
|
|
|
|
|
|
2023-09-27 15:04:23 +02:00
|
|
|
const { error, plugins, isLoading } = useGetAll({
|
2023-05-05 09:38:18 +02:00
|
|
|
keyword: searchTerm,
|
|
|
|
|
type: PluginType.datasource,
|
2023-01-04 10:45:50 +01:00
|
|
|
});
|
2022-10-20 15:53:10 +02:00
|
|
|
|
|
|
|
|
const cardGridItems = useMemo(
|
|
|
|
|
() =>
|
|
|
|
|
plugins.map((plugin) => ({
|
|
|
|
|
id: plugin.id,
|
|
|
|
|
name: plugin.name,
|
2023-01-18 15:34:23 +01:00
|
|
|
description: plugin.description,
|
2022-10-20 15:53:10 +02:00
|
|
|
logo: plugin.info.logos.small,
|
2022-11-10 11:14:23 +01:00
|
|
|
url: ROUTES.DataSourcesDetails.replace(':id', plugin.id),
|
2023-07-11 10:12:09 +02:00
|
|
|
angularDetected: plugin.angularDetected,
|
2022-10-20 15:53:10 +02:00
|
|
|
})),
|
|
|
|
|
[plugins]
|
|
|
|
|
);
|
2023-01-18 15:34:23 +01:00
|
|
|
|
|
|
|
|
const onClickCardGridItem = (e: React.MouseEvent<HTMLElement>, item: CardGridItem) => {
|
|
|
|
|
if (!canCreateDataSources) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
|
|
|
|
|
openModal(item);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const openModal = (item: CardGridItem) => {
|
|
|
|
|
setIsNoAccessModalOpen(true);
|
|
|
|
|
setFocusedItem(item);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const closeModal = () => {
|
|
|
|
|
setIsNoAccessModalOpen(false);
|
|
|
|
|
setFocusedItem(null);
|
|
|
|
|
};
|
|
|
|
|
|
2022-10-20 15:53:10 +02:00
|
|
|
const showNoResults = useMemo(() => !isLoading && !error && plugins.length < 1, [isLoading, error, plugins]);
|
2023-06-30 11:27:19 +01:00
|
|
|
const categoryHeaderLabel = t('connections.connect-data.category-header-label', 'Data sources');
|
2022-10-20 15:53:10 +02:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
2023-01-18 15:34:23 +01:00
|
|
|
{focusedItem && <NoAccessModal item={focusedItem} isOpen={isNoAccessModalOpen} onDismiss={closeModal} />}
|
2024-01-10 09:57:17 +00:00
|
|
|
<Search onChange={handleSearchChange} value={searchTerm} />
|
2022-10-20 15:53:10 +02:00
|
|
|
{/* We need this extra spacing when there are no filters */}
|
|
|
|
|
<div className={styles.spacer} />
|
2023-06-30 11:27:19 +01:00
|
|
|
<CategoryHeader iconName="database" label={categoryHeaderLabel} />
|
2022-10-20 15:53:10 +02:00
|
|
|
{isLoading ? (
|
|
|
|
|
<LoadingPlaceholder text="Loading..." />
|
|
|
|
|
) : !!error ? (
|
|
|
|
|
<p>Error: {error.message}</p>
|
|
|
|
|
) : (
|
2023-01-18 15:34:23 +01:00
|
|
|
<CardGrid items={cardGridItems} onClickItem={onClickCardGridItem} />
|
2022-10-20 15:53:10 +02:00
|
|
|
)}
|
2024-03-27 12:15:12 +00:00
|
|
|
{showNoResults && (
|
|
|
|
|
<EmptyState
|
|
|
|
|
variant="not-found"
|
|
|
|
|
message={t('connections.connect-data.empty-message', 'No results matching your query were found')}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
2022-10-20 15:53:10 +02:00
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|