mirror of
https://github.com/grafana/grafana.git
synced 2025-02-20 11:48:34 -06:00
Canvas: use grafana datasource to list resource files (#39204)
This commit is contained in:
parent
e31b8f4d33
commit
cde133fe05
@ -27,7 +27,7 @@ export abstract class FunctionalVector<T = any> implements Vector<T>, Iterable<T
|
||||
return vectorator(this).map(transform);
|
||||
}
|
||||
|
||||
filter<V>(predicate: (item: T) => V) {
|
||||
filter(predicate: (item: T) => boolean): T[] {
|
||||
return vectorator(this).filter(predicate);
|
||||
}
|
||||
|
||||
@ -65,7 +65,8 @@ export function vectorator<T>(vector: Vector<T>) {
|
||||
return result;
|
||||
},
|
||||
|
||||
filter<V>(predicate: (item: T) => V) {
|
||||
/** Add a predicate where you return true if it should *keep* the value */
|
||||
filter(predicate: (item: T) => boolean): T[] {
|
||||
const result: T[] = [];
|
||||
for (const val of this) {
|
||||
if (predicate(val)) {
|
||||
|
@ -12,11 +12,12 @@ import {
|
||||
stylesFactory,
|
||||
} from '@grafana/ui';
|
||||
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { ResourceCards } from './ResourceCards';
|
||||
import SVG from 'react-inlinesvg';
|
||||
import { css } from '@emotion/css';
|
||||
import { getPublicOrAbsoluteUrl } from '../resource';
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
import { FileElement, GrafanaDatasource } from 'app/plugins/datasource/grafana/datasource';
|
||||
|
||||
interface Props {
|
||||
value?: string; //img/icons/unicons/0-plus.svg
|
||||
@ -24,6 +25,13 @@ interface Props {
|
||||
mediaType: 'icon' | 'image';
|
||||
}
|
||||
|
||||
interface ResourceItem {
|
||||
label: string;
|
||||
value: string;
|
||||
search: string;
|
||||
imgUrl: string;
|
||||
}
|
||||
|
||||
export function ResourcePicker(props: Props) {
|
||||
const { value, onChange, mediaType } = props;
|
||||
const folders = (mediaType === 'icon' ? ['img/icons/unicons', 'img/icons/iot'] : ['img/bg']).map((v) => ({
|
||||
@ -36,43 +44,52 @@ export function ResourcePicker(props: Props) {
|
||||
{ label: 'Select', active: true },
|
||||
// { label: 'Upload', active: false },
|
||||
]);
|
||||
const [directoryIndex, setDirectoryIndex] = useState<SelectableValue[]>([]);
|
||||
const [defaultList, setDefaultList] = useState<SelectableValue[]>([]);
|
||||
const [directoryIndex, setDirectoryIndex] = useState<ResourceItem[]>([]);
|
||||
const [filteredIndex, setFilteredIndex] = useState<ResourceItem[]>([]);
|
||||
const theme = useTheme2();
|
||||
const styles = getStyles(theme);
|
||||
|
||||
useEffect(() => {
|
||||
// we don't want to load everything before picking a folder
|
||||
if (currentFolder) {
|
||||
getBackendSrv()
|
||||
.get(`public/${currentFolder?.value}/index.json`)
|
||||
.then((data) => {
|
||||
const cards = data.files.map((v: string) => ({
|
||||
value: v,
|
||||
label: v,
|
||||
imgUrl: `public/${currentFolder?.value}/${v}`,
|
||||
}));
|
||||
setDirectoryIndex(cards);
|
||||
setDefaultList(cards);
|
||||
})
|
||||
.catch((e) => console.error(e));
|
||||
} else {
|
||||
return;
|
||||
const folder = currentFolder?.value;
|
||||
if (folder) {
|
||||
const filter =
|
||||
mediaType === 'icon'
|
||||
? (item: FileElement) => item.name.endsWith('.svg')
|
||||
: (item: FileElement) => item.name.endsWith('.png') || item.name.endsWith('.gif');
|
||||
|
||||
getDatasourceSrv()
|
||||
.get('-- Grafana --')
|
||||
.then((ds) => {
|
||||
(ds as GrafanaDatasource).listFiles(folder).subscribe({
|
||||
next: (frame) => {
|
||||
const cards: ResourceItem[] = [];
|
||||
frame.forEach((item) => {
|
||||
if (filter(item)) {
|
||||
const idx = item.name.lastIndexOf('.');
|
||||
cards.push({
|
||||
value: item.name,
|
||||
label: item.name,
|
||||
search: (idx ? item.name.substr(0, idx) : item.name).toLowerCase(),
|
||||
imgUrl: `public/${folder}/${item.name}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
setDirectoryIndex(cards);
|
||||
setFilteredIndex(cards);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [currentFolder]);
|
||||
}, [mediaType, currentFolder]);
|
||||
|
||||
const onChangeSearch = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.value) {
|
||||
const filtered = directoryIndex.filter((card) =>
|
||||
card.value
|
||||
// exclude file type (.svg) in the search
|
||||
.substr(0, card.value.length - 4)
|
||||
.toLocaleLowerCase()
|
||||
.includes(e.target.value.toLocaleLowerCase())
|
||||
);
|
||||
setDirectoryIndex(filtered);
|
||||
let query = e.currentTarget.value;
|
||||
if (query) {
|
||||
query = query.toLowerCase();
|
||||
setFilteredIndex(directoryIndex.filter((card) => card.search.includes(query)));
|
||||
} else {
|
||||
setDirectoryIndex(defaultList);
|
||||
setFilteredIndex(directoryIndex);
|
||||
}
|
||||
};
|
||||
const imgSrc = getPublicOrAbsoluteUrl(value!);
|
||||
@ -106,9 +123,9 @@ export function ResourcePicker(props: Props) {
|
||||
<div className={styles.tabContent}>
|
||||
<Select options={folders} onChange={setCurrentFolder} value={currentFolder} />
|
||||
<Input placeholder="Search" onChange={onChangeSearch} />
|
||||
{directoryIndex ? (
|
||||
{filteredIndex ? (
|
||||
<div className={styles.cardsWrapper}>
|
||||
<ResourceCards cards={directoryIndex} onChange={onChange} currentFolder={currentFolder} />
|
||||
<ResourceCards cards={filteredIndex} onChange={onChange} currentFolder={currentFolder} />
|
||||
</div>
|
||||
) : (
|
||||
<Spinner />
|
||||
|
@ -3,6 +3,7 @@ import { DataSourceWithBackend, getBackendSrv, getGrafanaLiveSrv, getTemplateSrv
|
||||
import {
|
||||
AnnotationQuery,
|
||||
AnnotationQueryRequest,
|
||||
DataFrameView,
|
||||
DataQueryRequest,
|
||||
DataQueryResponse,
|
||||
DataSourceInstanceSettings,
|
||||
@ -17,6 +18,7 @@ import { GrafanaAnnotationQuery, GrafanaAnnotationType, GrafanaQuery, GrafanaQue
|
||||
import AnnotationQueryEditor from './components/AnnotationQueryEditor';
|
||||
import { getDashboardSrv } from '../../../features/dashboard/services/DashboardSrv';
|
||||
import { isString } from 'lodash';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
let counter = 100;
|
||||
|
||||
@ -124,6 +126,23 @@ export class GrafanaDatasource extends DataSourceWithBackend<GrafanaQuery> {
|
||||
return of(); // nothing
|
||||
}
|
||||
|
||||
listFiles(path: string): Observable<DataFrameView<FileElement>> {
|
||||
return this.query({
|
||||
targets: [
|
||||
{
|
||||
refId: 'A',
|
||||
queryType: GrafanaQueryType.List,
|
||||
path,
|
||||
},
|
||||
],
|
||||
} as any).pipe(
|
||||
map((v) => {
|
||||
const frame = v.data[0] ?? toDataFrame({});
|
||||
return new DataFrameView<FileElement>(frame);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
metricFindQuery(options: any) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
@ -183,3 +202,8 @@ export class GrafanaDatasource extends DataSourceWithBackend<GrafanaQuery> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
export interface FileElement {
|
||||
name: string;
|
||||
['media-type']: string;
|
||||
}
|
||||
|
@ -1,4 +0,0 @@
|
||||
{
|
||||
"name": "bg",
|
||||
"files": ["p0.png", "p1.png", "p2.png", "p3.png", "p4.png", "p5.png", "p6.png"]
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
{
|
||||
"name": "line",
|
||||
"files": ["faucet.svg", "pump.svg"]
|
||||
}
|
Loading…
Reference in New Issue
Block a user