|
|
|
import { PanelOptions, TableRowConfig } from '../types';
|
|
|
|
|
|
|
|
import { makeRequest } from 'services/network_service';
|
|
|
|
|
|
|
|
import { Table, Button, HorizontalGroup, LoadingPlaceholder } from '@grafana/ui';
|
|
|
|
import {
|
|
|
|
PanelProps,
|
|
|
|
toDataFrame,
|
|
|
|
FieldType,
|
|
|
|
applyFieldOverrides,
|
|
|
|
createTheme,
|
|
|
|
DataFrame,
|
|
|
|
DataLinkClickEvent,
|
|
|
|
} from '@grafana/data';
|
|
|
|
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
import * as _ from 'lodash';
|
|
|
|
|
|
|
|
interface Props extends PanelProps<PanelOptions> {}
|
|
|
|
|
|
|
|
export function Panel({ options, data, width, height, timeRange, onChangeTimeRange }: Props) {
|
|
|
|
console.log('panel', data);
|
|
|
|
|
|
|
|
const [tasks, setTasks] = useState<TableRowConfig[] | null>(null);
|
|
|
|
const [dataFrame, setDataFrame] = useState<DataFrame | null>(null);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if(tasks === null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const dataFrame = getDataFrameForTable(tasks, setTasks);
|
|
|
|
setDataFrame(dataFrame)
|
|
|
|
}, [tasks]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
// TODO: move this function to Network Service
|
|
|
|
async function getTasks(): Promise<TableRowConfig[]> {
|
|
|
|
return makeRequest<TableRowConfig[]>('/tasks', {});
|
|
|
|
}
|
|
|
|
|
|
|
|
getTasks().then(tasks => setTasks(tasks)).catch(err => console.error(err));
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
function onAddTaskClick(): void {
|
|
|
|
if(tasks === null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const configs = [...tasks, createConfigItem()];
|
|
|
|
setTasks(configs);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
{dataFrame === null ?
|
|
|
|
// TODO: if datasource responds with error, display the error
|
|
|
|
<LoadingPlaceholder text="Loading..."></LoadingPlaceholder> :
|
|
|
|
<div>
|
|
|
|
<Table width={width} height={height - 40} data={dataFrame} />
|
|
|
|
<HorizontalGroup justify="flex-end">
|
|
|
|
<Button
|
|
|
|
variant="primary"
|
|
|
|
aria-label="Rich history button"
|
|
|
|
icon="plus"
|
|
|
|
style={{ marginTop: '8px' }}
|
|
|
|
onClick={onAddTaskClick}
|
|
|
|
>
|
|
|
|
Add Task
|
|
|
|
</Button>
|
|
|
|
</HorizontalGroup>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function getDataFrameForTable(configs: TableRowConfig[], setTasks: any): DataFrame {
|
|
|
|
const dataFrame = toDataFrame({
|
|
|
|
name: 'A',
|
|
|
|
fields: [
|
|
|
|
{
|
|
|
|
name: 'Time',
|
|
|
|
type: FieldType.number,
|
|
|
|
values: _.map(configs, (config) => convertTimestampToDate(config.timestamp)),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'User',
|
|
|
|
type: FieldType.string,
|
|
|
|
values: _.map(configs, (config) => config.user),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'Datasource',
|
|
|
|
type: FieldType.string,
|
|
|
|
values: _.map(configs, (config) => config.datasource),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'Exported Rows',
|
|
|
|
type: FieldType.number,
|
|
|
|
values: _.map(configs, (config) => config.rowsCount),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'Progress',
|
|
|
|
type: FieldType.string,
|
|
|
|
values: _.map(configs, (config) => `${config.progress}%`),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'Status',
|
|
|
|
type: FieldType.string,
|
|
|
|
values: _.map(configs, (config) => config.status),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'Download CSV',
|
|
|
|
type: FieldType.string,
|
|
|
|
values: _.map(configs, (config) => `data:image/png;base64,${DOWNLOAD_ICON_BASE_64}`),
|
|
|
|
config: {
|
|
|
|
custom: {
|
|
|
|
filterable: false,
|
|
|
|
displayMode: 'image',
|
|
|
|
},
|
|
|
|
links: [
|
|
|
|
{
|
|
|
|
targetBlank: false,
|
|
|
|
title: 'CSV link',
|
|
|
|
url: 'https://chartwerk.io/',
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'Delete task',
|
|
|
|
type: FieldType.string,
|
|
|
|
values: _.map(configs, (config) => `data:image/png;base64,${CLOSE_ICON_BASE_64}`),
|
|
|
|
config: {
|
|
|
|
custom: {
|
|
|
|
filterable: false,
|
|
|
|
displayMode: 'image',
|
|
|
|
},
|
|
|
|
links: [
|
|
|
|
{
|
|
|
|
targetBlank: false,
|
|
|
|
title: 'Delete',
|
|
|
|
url: '#',
|
|
|
|
onClick: (event: DataLinkClickEvent) => onDeleteClick(event, configs, setTasks),
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
],
|
|
|
|
});
|
|
|
|
|
|
|
|
const dataFrames = applyFieldOverrides({
|
|
|
|
data: [dataFrame],
|
|
|
|
fieldConfig: {
|
|
|
|
overrides: [],
|
|
|
|
defaults: {},
|
|
|
|
},
|
|
|
|
theme: createTheme(),
|
|
|
|
replaceVariables: (value: string) => value,
|
|
|
|
});
|
|
|
|
return dataFrames[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
function onDeleteClick(e: DataLinkClickEvent, tasks: TableRowConfig[], setTasks: any): void {
|
|
|
|
const rowIndex = e.origin.rowIndex;
|
|
|
|
const filteredTasks = _.filter(tasks, (task, idx) => idx !== rowIndex);
|
|
|
|
setTasks(filteredTasks);
|
|
|
|
}
|
|
|
|
|
|
|
|
function createConfigItem(): TableRowConfig {
|
|
|
|
return {
|
|
|
|
timestamp: Date.now(),
|
|
|
|
user: 'admin',
|
|
|
|
datasource: 'Prometheus',
|
|
|
|
rowsCount: 3214,
|
|
|
|
progress: 100,
|
|
|
|
status: 'finished',
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function convertTimestampToDate(timestamp: number): string {
|
|
|
|
const options: Intl.DateTimeFormatOptions = {
|
|
|
|
year: 'numeric',
|
|
|
|
month: 'short',
|
|
|
|
day: 'numeric',
|
|
|
|
hour: 'numeric',
|
|
|
|
minute: 'numeric',
|
|
|
|
second: 'numeric',
|
|
|
|
};
|
|
|
|
return new Date(timestamp).toLocaleString('en-GB', options);
|
|
|
|
}
|
|
|
|
|
|
|
|
const CLOSE_ICON_BASE_64 =
|
|
|
|
'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAACJSURBVHgB7ZPRCcAgDESv0lWc0zhC53OIjmAVUpCSamL76YEoyfFIiAGWfldK6SwnKHyhep9xJ3iPcqgH5RyxV1VlBWYJypXVHMEiCaqBbRhAy3W3B76j954wq6ZSVZsOY+WXt6i9l2ymGTlUq0VpOcIqaQC96Zth01DN1zBBefVI4SNp9Za+6wLcH6DKFrfpxgAAAABJRU5ErkJggg==';
|
|
|
|
const DOWNLOAD_ICON_BASE_64 =
|
|
|
|
'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAADlSURBVHgB3ZPNDYJAEIVnN96lBA7A2RIsATuQiiwBSrAD7MAjCZBACXqFwPomMRtEwMVwgZeQhfn5mNnMEG1CaZoeiqKwTWJ3JkFCiEtVVU+8+r9iJRlKSrk3iqOFtWJglmXHf3yDwCRJbBxxnudh34cRYluMMbKGcgWNCIlnjEuIJ1JK2WzDWeKb7YHjONEsYBf6kTABY+mWuYX+3Xiex9UFU7D3FllfwLqueQvi/h8ZCtBprDLY703T6A0yWj2ArmSoxedQV4jSSz5xj4pmCi0/NKfrwNz5bdtaNENciOu6N1qNXhzZXHMb9Q+nAAAAAElFTkSuQmCC';
|