mirror of https://github.com/grafana/grafana
Live: Remove (alpha) ability to configure live pipelines (#65138)
parent
b2fb7a162a
commit
f96637b5fc
|
@ -1,132 +0,0 @@ |
|||||||
import React, { useState } from 'react'; |
|
||||||
|
|
||||||
import { DataSourceRef, LiveChannelScope, SelectableValue } from '@grafana/data'; |
|
||||||
import { DataSourcePicker, getBackendSrv } from '@grafana/runtime'; |
|
||||||
import { Input, Field, Button, ValuePicker, HorizontalGroup } from '@grafana/ui'; |
|
||||||
import { useAppNotification } from 'app/core/copy/appNotification'; |
|
||||||
|
|
||||||
import { Rule } from './types'; |
|
||||||
|
|
||||||
interface Props { |
|
||||||
onRuleAdded: (rule: Rule) => void; |
|
||||||
} |
|
||||||
|
|
||||||
type PatternType = 'ds' | 'any'; |
|
||||||
|
|
||||||
const patternTypes: Array<SelectableValue<PatternType>> = [ |
|
||||||
{ |
|
||||||
label: 'Data source', |
|
||||||
description: 'Configure a channel scoped to a data source instance', |
|
||||||
value: 'ds', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: 'Any', |
|
||||||
description: 'Enter an arbitray channel pattern', |
|
||||||
value: 'any', |
|
||||||
}, |
|
||||||
]; |
|
||||||
|
|
||||||
export function AddNewRule({ onRuleAdded }: Props) { |
|
||||||
const [patternType, setPatternType] = useState<PatternType>(); |
|
||||||
const [pattern, setPattern] = useState<string>(); |
|
||||||
const [patternPrefix, setPatternPrefix] = useState<string>(''); |
|
||||||
const [datasource, setDatasource] = useState<DataSourceRef>(); |
|
||||||
const notifyApp = useAppNotification(); |
|
||||||
|
|
||||||
const onSubmit = () => { |
|
||||||
if (!pattern) { |
|
||||||
notifyApp.error('Enter path'); |
|
||||||
return; |
|
||||||
} |
|
||||||
if (patternType === 'ds' && !patternPrefix.length) { |
|
||||||
notifyApp.error('Select datasource'); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
getBackendSrv() |
|
||||||
.post(`api/live/channel-rules`, { |
|
||||||
pattern: patternPrefix + pattern, |
|
||||||
settings: { |
|
||||||
converter: { |
|
||||||
type: 'jsonAuto', |
|
||||||
}, |
|
||||||
frameOutputs: [ |
|
||||||
{ |
|
||||||
type: 'managedStream', |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
}) |
|
||||||
.then((v: any) => { |
|
||||||
console.log('ADDED', v); |
|
||||||
setPattern(undefined); |
|
||||||
setPatternType(undefined); |
|
||||||
onRuleAdded(v.rule); |
|
||||||
}) |
|
||||||
.catch((e) => { |
|
||||||
notifyApp.error('Error adding rule', e); |
|
||||||
e.isHandled = true; |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
if (patternType) { |
|
||||||
return ( |
|
||||||
<div> |
|
||||||
<HorizontalGroup> |
|
||||||
{patternType === 'any' && ( |
|
||||||
<Field label="Pattern"> |
|
||||||
<Input |
|
||||||
value={pattern ?? ''} |
|
||||||
onChange={(e) => setPattern(e.currentTarget.value)} |
|
||||||
placeholder="scope/namespace/path" |
|
||||||
/> |
|
||||||
</Field> |
|
||||||
)} |
|
||||||
{patternType === 'ds' && ( |
|
||||||
<> |
|
||||||
<Field label="Data source"> |
|
||||||
<DataSourcePicker |
|
||||||
current={datasource} |
|
||||||
onChange={(ds) => { |
|
||||||
setDatasource(ds); |
|
||||||
setPatternPrefix(`${LiveChannelScope.DataSource}/${ds.uid}/`); |
|
||||||
}} |
|
||||||
/> |
|
||||||
</Field> |
|
||||||
<Field label="Path"> |
|
||||||
<Input value={pattern ?? ''} onChange={(e) => setPattern(e.currentTarget.value)} placeholder="path" /> |
|
||||||
</Field> |
|
||||||
</> |
|
||||||
)} |
|
||||||
|
|
||||||
<Field label=""> |
|
||||||
<Button onClick={onSubmit} variant={pattern?.length ? 'primary' : 'secondary'}> |
|
||||||
Add |
|
||||||
</Button> |
|
||||||
</Field> |
|
||||||
|
|
||||||
<Field label=""> |
|
||||||
<Button variant="secondary" onClick={() => setPatternType(undefined)}> |
|
||||||
Cancel |
|
||||||
</Button> |
|
||||||
</Field> |
|
||||||
</HorizontalGroup> |
|
||||||
</div> |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
return ( |
|
||||||
<div> |
|
||||||
<ValuePicker |
|
||||||
label="Add channel rule" |
|
||||||
variant="secondary" |
|
||||||
size="md" |
|
||||||
icon="plus" |
|
||||||
menuPlacement="auto" |
|
||||||
isFullWidth={false} |
|
||||||
options={patternTypes} |
|
||||||
onChange={(v) => setPatternType(v.value)} |
|
||||||
/> |
|
||||||
</div> |
|
||||||
); |
|
||||||
} |
|
@ -1,51 +0,0 @@ |
|||||||
import { css } from '@emotion/css'; |
|
||||||
import React, { useEffect, useState } from 'react'; |
|
||||||
|
|
||||||
import { getBackendSrv } from '@grafana/runtime'; |
|
||||||
import { Page } from 'app/core/components/Page/Page'; |
|
||||||
import { useNavModel } from 'app/core/hooks/useNavModel'; |
|
||||||
|
|
||||||
import { GrafanaCloudBackend } from './types'; |
|
||||||
|
|
||||||
export default function CloudAdminPage() { |
|
||||||
const navModel = useNavModel('live-cloud'); |
|
||||||
const [cloud, setCloud] = useState<GrafanaCloudBackend[]>([]); |
|
||||||
const [error, setError] = useState<string>(); |
|
||||||
|
|
||||||
useEffect(() => { |
|
||||||
getBackendSrv() |
|
||||||
.get(`api/live/write-configs`) |
|
||||||
.then((data) => { |
|
||||||
setCloud(data.writeConfigs); |
|
||||||
}) |
|
||||||
.catch((e) => { |
|
||||||
if (e.data) { |
|
||||||
setError(JSON.stringify(e.data, null, 2)); |
|
||||||
} |
|
||||||
}); |
|
||||||
}, []); |
|
||||||
|
|
||||||
return ( |
|
||||||
<Page navModel={navModel}> |
|
||||||
<Page.Contents> |
|
||||||
{error && <pre>{error}</pre>} |
|
||||||
{!cloud && <>Loading cloud definitions</>} |
|
||||||
{cloud && |
|
||||||
cloud.map((v) => { |
|
||||||
return ( |
|
||||||
<div key={v.uid}> |
|
||||||
<h2>{v.uid}</h2> |
|
||||||
<pre className={styles.row}>{JSON.stringify(v.settings, null, 2)}</pre> |
|
||||||
</div> |
|
||||||
); |
|
||||||
})} |
|
||||||
</Page.Contents> |
|
||||||
</Page> |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
const styles = { |
|
||||||
row: css` |
|
||||||
cursor: pointer; |
|
||||||
`,
|
|
||||||
}; |
|
@ -1,22 +0,0 @@ |
|||||||
import React from 'react'; |
|
||||||
|
|
||||||
import { Page } from 'app/core/components/Page/Page'; |
|
||||||
import { useNavModel } from 'app/core/hooks/useNavModel'; |
|
||||||
|
|
||||||
export default function FeatureTogglePage() { |
|
||||||
const navModel = useNavModel('live-status'); |
|
||||||
|
|
||||||
return ( |
|
||||||
<Page navModel={navModel}> |
|
||||||
<Page.Contents> |
|
||||||
<h1>Pipeline is not enabled</h1> |
|
||||||
To enable pipelines, enable the feature toggle: |
|
||||||
<pre> |
|
||||||
{`[feature_toggles]
|
|
||||||
enable = live-pipeline |
|
||||||
`}
|
|
||||||
</pre> |
|
||||||
</Page.Contents> |
|
||||||
</Page> |
|
||||||
); |
|
||||||
} |
|
@ -1,14 +0,0 @@ |
|||||||
import React from 'react'; |
|
||||||
|
|
||||||
import { Page } from 'app/core/components/Page/Page'; |
|
||||||
import { useNavModel } from 'app/core/hooks/useNavModel'; |
|
||||||
|
|
||||||
export default function CloudAdminPage() { |
|
||||||
const navModel = useNavModel('live-status'); |
|
||||||
|
|
||||||
return ( |
|
||||||
<Page navModel={navModel}> |
|
||||||
<Page.Contents>Live/Live/Live</Page.Contents> |
|
||||||
</Page> |
|
||||||
); |
|
||||||
} |
|
@ -1,67 +0,0 @@ |
|||||||
import React, { useEffect, useState, ChangeEvent } from 'react'; |
|
||||||
|
|
||||||
import { getBackendSrv } from '@grafana/runtime'; |
|
||||||
import { Input } from '@grafana/ui'; |
|
||||||
import { Page } from 'app/core/components/Page/Page'; |
|
||||||
import { useNavModel } from 'app/core/hooks/useNavModel'; |
|
||||||
|
|
||||||
import { AddNewRule } from './AddNewRule'; |
|
||||||
import { PipelineTable } from './PipelineTable'; |
|
||||||
import { Rule } from './types'; |
|
||||||
|
|
||||||
export default function PipelineAdminPage() { |
|
||||||
const [rules, setRules] = useState<Rule[]>([]); |
|
||||||
const [defaultRules, setDefaultRules] = useState<any[]>([]); |
|
||||||
const [newRule, setNewRule] = useState<Rule>(); |
|
||||||
const navModel = useNavModel('live-pipeline'); |
|
||||||
const [error, setError] = useState<string>(); |
|
||||||
|
|
||||||
const loadRules = () => { |
|
||||||
getBackendSrv() |
|
||||||
.get(`api/live/channel-rules`) |
|
||||||
.then((data) => { |
|
||||||
setRules(data.rules ?? []); |
|
||||||
setDefaultRules(data.rules ?? []); |
|
||||||
}) |
|
||||||
.catch((e) => { |
|
||||||
if (e.data) { |
|
||||||
setError(JSON.stringify(e.data, null, 2)); |
|
||||||
} |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
useEffect(() => { |
|
||||||
loadRules(); |
|
||||||
}, []); |
|
||||||
|
|
||||||
const onSearchQueryChange = (e: ChangeEvent<HTMLInputElement>) => { |
|
||||||
if (e.target.value) { |
|
||||||
setRules(rules.filter((rule) => rule.pattern.toLowerCase().includes(e.target.value.toLowerCase()))); |
|
||||||
} else { |
|
||||||
setRules(defaultRules); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
return ( |
|
||||||
<Page navModel={navModel}> |
|
||||||
<Page.Contents> |
|
||||||
{error && <pre>{error}</pre>} |
|
||||||
<div className="page-action-bar"> |
|
||||||
<div className="gf-form gf-form--grow"> |
|
||||||
<Input placeholder="Search pattern..." onChange={onSearchQueryChange} /> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<PipelineTable rules={rules} onRuleChanged={loadRules} selectRule={newRule} /> |
|
||||||
|
|
||||||
<AddNewRule |
|
||||||
onRuleAdded={(r: Rule) => { |
|
||||||
console.log('GOT', r, 'vs', rules[0]); |
|
||||||
setNewRule(r); |
|
||||||
loadRules(); |
|
||||||
}} |
|
||||||
/> |
|
||||||
</Page.Contents> |
|
||||||
</Page> |
|
||||||
); |
|
||||||
} |
|
@ -1,142 +0,0 @@ |
|||||||
import { css } from '@emotion/css'; |
|
||||||
import React, { useEffect, useState } from 'react'; |
|
||||||
|
|
||||||
import { getBackendSrv } from '@grafana/runtime'; |
|
||||||
import { Tag, IconButton } from '@grafana/ui'; |
|
||||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; |
|
||||||
|
|
||||||
import { RuleModal } from './RuleModal'; |
|
||||||
import { Rule, Output, RuleType } from './types'; |
|
||||||
|
|
||||||
function renderOutputTags(key: string, output?: Output): React.ReactNode { |
|
||||||
if (!output?.type) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
return <Tag key={key} name={output.type} />; |
|
||||||
} |
|
||||||
|
|
||||||
interface Props { |
|
||||||
rules: Rule[]; |
|
||||||
onRuleChanged: () => void; |
|
||||||
selectRule?: Rule; |
|
||||||
} |
|
||||||
|
|
||||||
export const PipelineTable = (props: Props) => { |
|
||||||
const { rules } = props; |
|
||||||
const [isOpen, setOpen] = useState(false); |
|
||||||
const [selectedRule, setSelectedRule] = useState<Rule>(); |
|
||||||
const [clickColumn, setClickColumn] = useState<RuleType>('converter'); |
|
||||||
|
|
||||||
const onRowClick = (rule: Rule, event?: any) => { |
|
||||||
if (!rule) { |
|
||||||
return; |
|
||||||
} |
|
||||||
let column = event?.target?.getAttribute('data-column'); |
|
||||||
if (!column || column === 'pattern') { |
|
||||||
column = 'converter'; |
|
||||||
} |
|
||||||
setClickColumn(column); |
|
||||||
setSelectedRule(rule); |
|
||||||
setOpen(true); |
|
||||||
}; |
|
||||||
|
|
||||||
// Supports selecting a rule from external config (after add rule)
|
|
||||||
useEffect(() => { |
|
||||||
if (props.selectRule) { |
|
||||||
onRowClick(props.selectRule); |
|
||||||
} |
|
||||||
}, [props.selectRule]); |
|
||||||
|
|
||||||
const onRemoveRule = (pattern: string) => { |
|
||||||
getBackendSrv() |
|
||||||
.delete(`api/live/channel-rules`, JSON.stringify({ pattern: pattern })) |
|
||||||
.catch((e) => console.error(e)) |
|
||||||
.finally(() => { |
|
||||||
props.onRuleChanged(); |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
const renderPattern = (pattern: string) => { |
|
||||||
if (pattern.startsWith('ds/')) { |
|
||||||
const idx = pattern.indexOf('/', 4); |
|
||||||
if (idx > 3) { |
|
||||||
const uid = pattern.substring(3, idx); |
|
||||||
const ds = getDatasourceSrv().getInstanceSettings(uid); |
|
||||||
if (ds) { |
|
||||||
return ( |
|
||||||
<div> |
|
||||||
<Tag name={ds.name} colorIndex={1} /> |
|
||||||
<span>{pattern.substring(idx + 1)}</span> |
|
||||||
</div> |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return pattern; |
|
||||||
}; |
|
||||||
|
|
||||||
return ( |
|
||||||
<div> |
|
||||||
<div className="admin-list-table"> |
|
||||||
<table className="filter-table filter-table--hover form-inline"> |
|
||||||
<thead> |
|
||||||
<tr> |
|
||||||
<th>Channel</th> |
|
||||||
<th>Converter</th> |
|
||||||
<th>Processor</th> |
|
||||||
<th>Output</th> |
|
||||||
<th style={{ width: 10 }}> </th> |
|
||||||
</tr> |
|
||||||
</thead> |
|
||||||
<tbody> |
|
||||||
{rules.map((rule) => ( |
|
||||||
<tr key={rule.pattern} onClick={(e) => onRowClick(rule, e)} className={styles.row}> |
|
||||||
<td data-pattern={rule.pattern} data-column="pattern"> |
|
||||||
{renderPattern(rule.pattern)} |
|
||||||
</td> |
|
||||||
<td data-pattern={rule.pattern} data-column="converter"> |
|
||||||
{rule.settings?.converter?.type} |
|
||||||
</td> |
|
||||||
<td data-pattern={rule.pattern} data-column="processor"> |
|
||||||
{rule.settings?.frameProcessors?.map((processor) => ( |
|
||||||
<span key={rule.pattern + processor.type}>{processor.type}</span> |
|
||||||
))} |
|
||||||
</td> |
|
||||||
<td data-pattern={rule.pattern} data-column="output"> |
|
||||||
{rule.settings?.frameOutputs?.map((output) => ( |
|
||||||
<span key={rule.pattern + output.type}>{renderOutputTags('out', output)}</span> |
|
||||||
))} |
|
||||||
</td> |
|
||||||
<td> |
|
||||||
<IconButton |
|
||||||
name="trash-alt" |
|
||||||
onClick={(e) => { |
|
||||||
e.stopPropagation(); |
|
||||||
onRemoveRule(rule.pattern); |
|
||||||
}} |
|
||||||
></IconButton> |
|
||||||
</td> |
|
||||||
</tr> |
|
||||||
))} |
|
||||||
</tbody> |
|
||||||
</table> |
|
||||||
</div> |
|
||||||
{isOpen && selectedRule && ( |
|
||||||
<RuleModal |
|
||||||
rule={selectedRule} |
|
||||||
isOpen={isOpen} |
|
||||||
onClose={() => { |
|
||||||
setOpen(false); |
|
||||||
}} |
|
||||||
clickColumn={clickColumn} |
|
||||||
/> |
|
||||||
)} |
|
||||||
</div> |
|
||||||
); |
|
||||||
}; |
|
||||||
|
|
||||||
const styles = { |
|
||||||
row: css` |
|
||||||
cursor: pointer; |
|
||||||
`,
|
|
||||||
}; |
|
@ -1,128 +0,0 @@ |
|||||||
import { css } from '@emotion/css'; |
|
||||||
import React, { useState, useMemo } from 'react'; |
|
||||||
|
|
||||||
import { getBackendSrv } from '@grafana/runtime'; |
|
||||||
import { Modal, TabContent, TabsBar, Tab, Button } from '@grafana/ui'; |
|
||||||
|
|
||||||
import { RuleSettingsArray } from './RuleSettingsArray'; |
|
||||||
import { RuleSettingsEditor } from './RuleSettingsEditor'; |
|
||||||
import { RuleTest } from './RuleTest'; |
|
||||||
import { Rule, RuleType, PipeLineEntitiesInfo, RuleSetting } from './types'; |
|
||||||
import { getPipeLineEntities } from './utils'; |
|
||||||
|
|
||||||
interface Props { |
|
||||||
rule: Rule; |
|
||||||
isOpen: boolean; |
|
||||||
onClose: () => void; |
|
||||||
clickColumn: RuleType; |
|
||||||
} |
|
||||||
interface TabInfo { |
|
||||||
label: string; |
|
||||||
type?: RuleType; |
|
||||||
isTest?: boolean; |
|
||||||
isConverter?: boolean; |
|
||||||
icon?: string; |
|
||||||
} |
|
||||||
const tabs: TabInfo[] = [ |
|
||||||
{ label: 'Converter', type: 'converter', isConverter: true }, |
|
||||||
{ label: 'Processors', type: 'frameProcessors' }, |
|
||||||
{ label: 'Outputs', type: 'frameOutputs' }, |
|
||||||
{ label: 'Test', isTest: true, icon: 'flask' }, |
|
||||||
]; |
|
||||||
|
|
||||||
export const RuleModal = (props: Props) => { |
|
||||||
const { isOpen, onClose, clickColumn } = props; |
|
||||||
const [rule, setRule] = useState<Rule>(props.rule); |
|
||||||
const [activeTab, setActiveTab] = useState<TabInfo | undefined>(tabs.find((t) => t.type === clickColumn)); |
|
||||||
// to show color of Save button
|
|
||||||
const [hasChange, setChange] = useState<boolean>(false); |
|
||||||
const [ruleSetting, setRuleSetting] = useState<any>(activeTab?.type ? rule?.settings?.[activeTab.type] : undefined); |
|
||||||
const [entitiesInfo, setEntitiesInfo] = useState<PipeLineEntitiesInfo>(); |
|
||||||
|
|
||||||
const onRuleSettingChange = (value: RuleSetting | RuleSetting[]) => { |
|
||||||
setChange(true); |
|
||||||
if (activeTab?.type) { |
|
||||||
setRule({ |
|
||||||
...rule, |
|
||||||
settings: { |
|
||||||
...rule.settings, |
|
||||||
[activeTab?.type]: value, |
|
||||||
}, |
|
||||||
}); |
|
||||||
} |
|
||||||
setRuleSetting(value); |
|
||||||
}; |
|
||||||
|
|
||||||
// load pipeline entities info
|
|
||||||
useMemo(() => { |
|
||||||
getPipeLineEntities().then((data) => { |
|
||||||
setEntitiesInfo(data); |
|
||||||
}); |
|
||||||
}, []); |
|
||||||
|
|
||||||
const onSave = () => { |
|
||||||
getBackendSrv() |
|
||||||
.put(`api/live/channel-rules`, rule) |
|
||||||
.then(() => { |
|
||||||
setChange(false); |
|
||||||
onClose(); |
|
||||||
}) |
|
||||||
.catch((e) => console.error(e)); |
|
||||||
}; |
|
||||||
|
|
||||||
return ( |
|
||||||
<Modal isOpen={isOpen} title={rule.pattern} onDismiss={onClose} closeOnEscape> |
|
||||||
<TabsBar> |
|
||||||
{tabs.map((tab, index) => { |
|
||||||
return ( |
|
||||||
<Tab |
|
||||||
key={index} |
|
||||||
label={tab.label} |
|
||||||
active={tab === activeTab} |
|
||||||
icon={tab.icon as any} |
|
||||||
onChangeTab={() => { |
|
||||||
setActiveTab(tab); |
|
||||||
if (tab.type) { |
|
||||||
// to notify children of the new rule
|
|
||||||
setRuleSetting(rule?.settings?.[tab.type]); |
|
||||||
} |
|
||||||
}} |
|
||||||
/> |
|
||||||
); |
|
||||||
})} |
|
||||||
</TabsBar> |
|
||||||
<TabContent> |
|
||||||
{entitiesInfo && rule && activeTab && ( |
|
||||||
<> |
|
||||||
{activeTab?.isTest && <RuleTest rule={rule} />} |
|
||||||
{activeTab.isConverter && ( |
|
||||||
<RuleSettingsEditor |
|
||||||
onChange={onRuleSettingChange} |
|
||||||
value={ruleSetting} |
|
||||||
ruleType={'converter'} |
|
||||||
entitiesInfo={entitiesInfo} |
|
||||||
/> |
|
||||||
)} |
|
||||||
{!activeTab.isConverter && activeTab.type && ( |
|
||||||
<RuleSettingsArray |
|
||||||
onChange={onRuleSettingChange} |
|
||||||
value={ruleSetting} |
|
||||||
ruleType={activeTab.type} |
|
||||||
entitiesInfo={entitiesInfo} |
|
||||||
/> |
|
||||||
)} |
|
||||||
</> |
|
||||||
)} |
|
||||||
<Button onClick={onSave} className={styles.save} variant={hasChange ? 'primary' : 'secondary'}> |
|
||||||
Save |
|
||||||
</Button> |
|
||||||
</TabContent> |
|
||||||
</Modal> |
|
||||||
); |
|
||||||
}; |
|
||||||
|
|
||||||
const styles = { |
|
||||||
save: css` |
|
||||||
margin-top: 5px; |
|
||||||
`,
|
|
||||||
}; |
|
@ -1,51 +0,0 @@ |
|||||||
import React, { useState } from 'react'; |
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data'; |
|
||||||
import { Select } from '@grafana/ui'; |
|
||||||
|
|
||||||
import { RuleSettingsEditor } from './RuleSettingsEditor'; |
|
||||||
import { RuleType, RuleSetting, PipeLineEntitiesInfo } from './types'; |
|
||||||
|
|
||||||
interface Props { |
|
||||||
ruleType: RuleType; |
|
||||||
onChange: (value: RuleSetting[]) => void; |
|
||||||
value: RuleSetting[]; |
|
||||||
entitiesInfo: PipeLineEntitiesInfo; |
|
||||||
} |
|
||||||
|
|
||||||
export const RuleSettingsArray = ({ onChange, value, ruleType, entitiesInfo }: Props) => { |
|
||||||
const [index, setIndex] = useState<number>(0); |
|
||||||
const arr = value ?? []; |
|
||||||
const onRuleChange = (v: RuleSetting) => { |
|
||||||
if (!value) { |
|
||||||
onChange([v]); |
|
||||||
} else { |
|
||||||
const copy = [...value]; |
|
||||||
copy[index] = v; |
|
||||||
onChange(copy); |
|
||||||
} |
|
||||||
}; |
|
||||||
// create array of value.length + 1
|
|
||||||
let indexArr: Array<SelectableValue<number>> = []; |
|
||||||
for (let i = 0; i <= arr.length; i++) { |
|
||||||
indexArr.push({ |
|
||||||
label: `${ruleType}: ${i}`, |
|
||||||
value: i, |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
return ( |
|
||||||
<> |
|
||||||
<Select |
|
||||||
placeholder="Select an index" |
|
||||||
options={indexArr} |
|
||||||
value={index} |
|
||||||
onChange={(index) => { |
|
||||||
// set index to find the correct setting
|
|
||||||
setIndex(index.value!); |
|
||||||
}} |
|
||||||
></Select> |
|
||||||
<RuleSettingsEditor onChange={onRuleChange} value={arr[index]} ruleType={ruleType} entitiesInfo={entitiesInfo} /> |
|
||||||
</> |
|
||||||
); |
|
||||||
}; |
|
@ -1,48 +0,0 @@ |
|||||||
import React from 'react'; |
|
||||||
|
|
||||||
import { CodeEditor, Select } from '@grafana/ui'; |
|
||||||
|
|
||||||
import { RuleType, RuleSetting, PipeLineEntitiesInfo } from './types'; |
|
||||||
|
|
||||||
interface Props { |
|
||||||
ruleType: RuleType; |
|
||||||
onChange: (value: RuleSetting) => void; |
|
||||||
value: RuleSetting; |
|
||||||
entitiesInfo: PipeLineEntitiesInfo; |
|
||||||
} |
|
||||||
|
|
||||||
export const RuleSettingsEditor = ({ onChange, value, ruleType, entitiesInfo }: Props) => { |
|
||||||
return ( |
|
||||||
<> |
|
||||||
<Select |
|
||||||
key={ruleType} |
|
||||||
options={entitiesInfo[ruleType]} |
|
||||||
placeholder="Select an option" |
|
||||||
value={value?.type ?? ''} |
|
||||||
onChange={(value) => { |
|
||||||
// set the body with example
|
|
||||||
const type = value.value; |
|
||||||
onChange({ |
|
||||||
type, |
|
||||||
[type]: entitiesInfo.getExample(ruleType, type), |
|
||||||
}); |
|
||||||
}} |
|
||||||
/> |
|
||||||
<CodeEditor |
|
||||||
height={'50vh'} |
|
||||||
value={value ? JSON.stringify(value[value.type], null, '\t') : ''} |
|
||||||
showLineNumbers={true} |
|
||||||
readOnly={false} |
|
||||||
language="json" |
|
||||||
showMiniMap={false} |
|
||||||
onBlur={(text: string) => { |
|
||||||
const body = JSON.parse(text); |
|
||||||
onChange({ |
|
||||||
type: value.type, |
|
||||||
[value.type]: body, |
|
||||||
}); |
|
||||||
}} |
|
||||||
/> |
|
||||||
</> |
|
||||||
); |
|
||||||
}; |
|
@ -1,78 +0,0 @@ |
|||||||
import { css } from '@emotion/css'; |
|
||||||
import React, { useState } from 'react'; |
|
||||||
|
|
||||||
import { dataFrameFromJSON, getDisplayProcessor } from '@grafana/data'; |
|
||||||
import { getBackendSrv, config } from '@grafana/runtime'; |
|
||||||
import { Button, CodeEditor, Table, Field } from '@grafana/ui'; |
|
||||||
|
|
||||||
import { ChannelFrame, Rule } from './types'; |
|
||||||
|
|
||||||
interface Props { |
|
||||||
rule: Rule; |
|
||||||
} |
|
||||||
|
|
||||||
export const RuleTest = (props: Props) => { |
|
||||||
const [response, setResponse] = useState<ChannelFrame[]>(); |
|
||||||
const [data, setData] = useState<string>(); |
|
||||||
|
|
||||||
const onBlur = (text: string) => { |
|
||||||
setData(text); |
|
||||||
}; |
|
||||||
|
|
||||||
const onClick = () => { |
|
||||||
getBackendSrv() |
|
||||||
.post(`api/live/pipeline-convert-test`, { |
|
||||||
channelRules: [props.rule], |
|
||||||
channel: props.rule.pattern, |
|
||||||
data: data, |
|
||||||
}) |
|
||||||
.then((data: any) => { |
|
||||||
const t = data.channelFrames as any[]; |
|
||||||
if (t) { |
|
||||||
setResponse( |
|
||||||
t.map((f) => { |
|
||||||
const frame = dataFrameFromJSON(f.frame); |
|
||||||
for (const field of frame.fields) { |
|
||||||
field.display = getDisplayProcessor({ field, theme: config.theme2 }); |
|
||||||
} |
|
||||||
return { channel: f.channel, frame }; |
|
||||||
}) |
|
||||||
); |
|
||||||
} |
|
||||||
}) |
|
||||||
.catch((e) => { |
|
||||||
setResponse(e); |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
return ( |
|
||||||
<div> |
|
||||||
<CodeEditor |
|
||||||
height={100} |
|
||||||
value="" |
|
||||||
showLineNumbers={true} |
|
||||||
readOnly={false} |
|
||||||
language="json" |
|
||||||
showMiniMap={false} |
|
||||||
onBlur={onBlur} |
|
||||||
/> |
|
||||||
|
|
||||||
<Button onClick={onClick} className={styles.margin}> |
|
||||||
Test |
|
||||||
</Button> |
|
||||||
|
|
||||||
{response?.length && |
|
||||||
response.map((r) => ( |
|
||||||
<Field key={r.channel} label={r.channel}> |
|
||||||
<Table data={r.frame} width={700} height={Math.min(10 * r.frame.length + 10, 150)} showTypeIcons></Table> |
|
||||||
</Field> |
|
||||||
))} |
|
||||||
</div> |
|
||||||
); |
|
||||||
}; |
|
||||||
|
|
||||||
const styles = { |
|
||||||
margin: css` |
|
||||||
margin-bottom: 15px; |
|
||||||
`,
|
|
||||||
}; |
|
@ -1,40 +0,0 @@ |
|||||||
import { SafeDynamicImport } from 'app/core/components/DynamicImports/SafeDynamicImport'; |
|
||||||
import { config } from 'app/core/config'; |
|
||||||
import { RouteDescriptor } from 'app/core/navigation/types'; |
|
||||||
import { isGrafanaAdmin } from 'app/features/plugins/admin/permissions'; |
|
||||||
|
|
||||||
const liveRoutes = [ |
|
||||||
{ |
|
||||||
path: '/live', |
|
||||||
component: SafeDynamicImport( |
|
||||||
() => import(/* webpackChunkName: "LiveStatusPage" */ 'app/features/live/pages/LiveStatusPage') |
|
||||||
), |
|
||||||
}, |
|
||||||
{ |
|
||||||
path: '/live/pipeline', |
|
||||||
component: SafeDynamicImport( |
|
||||||
() => import(/* webpackChunkName: "PipelineAdminPage" */ 'app/features/live/pages/PipelineAdminPage') |
|
||||||
), |
|
||||||
}, |
|
||||||
{ |
|
||||||
path: '/live/cloud', |
|
||||||
component: SafeDynamicImport( |
|
||||||
() => import(/* webpackChunkName: "CloudAdminPage" */ 'app/features/live/pages/CloudAdminPage') |
|
||||||
), |
|
||||||
}, |
|
||||||
]; |
|
||||||
|
|
||||||
export function getLiveRoutes(cfg = config): RouteDescriptor[] { |
|
||||||
if (!isGrafanaAdmin()) { |
|
||||||
return []; |
|
||||||
} |
|
||||||
if (cfg.featureToggles['live-pipeline']) { |
|
||||||
return liveRoutes; |
|
||||||
} |
|
||||||
return liveRoutes.map((v) => ({ |
|
||||||
...v, |
|
||||||
component: SafeDynamicImport( |
|
||||||
() => import(/* webpackChunkName: "FeatureTogglePage" */ 'app/features/live/pages/FeatureTogglePage') |
|
||||||
), |
|
||||||
})); |
|
||||||
} |
|
@ -1,61 +0,0 @@ |
|||||||
import { DataFrame, SelectableValue } from '@grafana/data'; |
|
||||||
export interface Converter extends RuleSetting { |
|
||||||
[t: string]: any; |
|
||||||
} |
|
||||||
|
|
||||||
export interface Processor extends RuleSetting { |
|
||||||
[t: string]: any; |
|
||||||
} |
|
||||||
|
|
||||||
export interface Output extends RuleSetting { |
|
||||||
[t: string]: any; |
|
||||||
} |
|
||||||
|
|
||||||
export interface RuleSetting<T = any> { |
|
||||||
type: string; |
|
||||||
[key: string]: any; |
|
||||||
} |
|
||||||
export interface RuleSettings { |
|
||||||
converter?: Converter; |
|
||||||
frameProcessors?: Processor[]; |
|
||||||
frameOutputs?: Output[]; |
|
||||||
} |
|
||||||
|
|
||||||
export interface Rule { |
|
||||||
pattern: string; |
|
||||||
settings: RuleSettings; |
|
||||||
} |
|
||||||
|
|
||||||
export interface Pipeline { |
|
||||||
rules: Rule[]; |
|
||||||
} |
|
||||||
|
|
||||||
export interface GrafanaCloudBackend { |
|
||||||
uid: string; |
|
||||||
settings: any; |
|
||||||
} |
|
||||||
|
|
||||||
export type RuleType = 'converter' | 'frameProcessors' | 'frameOutputs'; |
|
||||||
|
|
||||||
export interface PipelineListOption { |
|
||||||
type: string; |
|
||||||
description: string; |
|
||||||
example?: object; |
|
||||||
} |
|
||||||
export interface EntitiesTypes { |
|
||||||
converters: PipelineListOption[]; |
|
||||||
frameProcessors: PipelineListOption[]; |
|
||||||
frameOutputs: PipelineListOption[]; |
|
||||||
} |
|
||||||
|
|
||||||
export interface PipeLineEntitiesInfo { |
|
||||||
converter: SelectableValue[]; |
|
||||||
frameProcessors: SelectableValue[]; |
|
||||||
frameOutputs: SelectableValue[]; |
|
||||||
getExample: (rule: RuleType, type: string) => object; |
|
||||||
} |
|
||||||
|
|
||||||
export interface ChannelFrame { |
|
||||||
channel: string; |
|
||||||
frame: DataFrame; |
|
||||||
} |
|
@ -1,31 +0,0 @@ |
|||||||
import { getBackendSrv } from '@grafana/runtime'; |
|
||||||
|
|
||||||
import { PipelineListOption, PipeLineEntitiesInfo } from './types'; |
|
||||||
|
|
||||||
export async function getPipeLineEntities(): Promise<PipeLineEntitiesInfo> { |
|
||||||
return await getBackendSrv() |
|
||||||
.get(`api/live/pipeline-entities`) |
|
||||||
.then((data) => { |
|
||||||
return { |
|
||||||
converter: transformLabel(data, 'converters'), |
|
||||||
frameProcessors: transformLabel(data, 'frameProcessors'), |
|
||||||
frameOutputs: transformLabel(data, 'frameOutputs'), |
|
||||||
getExample: (ruleType, type) => { |
|
||||||
return data[`${ruleType}s`]?.filter((option: PipelineListOption) => option.type === type)?.[0]?.['example']; |
|
||||||
}, |
|
||||||
}; |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
export function transformLabel(data: any, key: keyof typeof data) { |
|
||||||
if (Array.isArray(data)) { |
|
||||||
return data.map((d) => ({ |
|
||||||
label: d[key], |
|
||||||
value: d[key], |
|
||||||
})); |
|
||||||
} |
|
||||||
return data[key].map((typeObj: PipelineListOption) => ({ |
|
||||||
label: typeObj.type, |
|
||||||
value: typeObj.type, |
|
||||||
})); |
|
||||||
} |
|
@ -1,132 +0,0 @@ |
|||||||
/* Do not change, this code is generated from Golang structs */ |
|
||||||
|
|
||||||
import { FieldConfig } from '@grafana/data'; |
|
||||||
|
|
||||||
export interface ChannelAuthCheckConfig { |
|
||||||
role?: string; |
|
||||||
} |
|
||||||
export interface ChannelAuthConfig { |
|
||||||
subscribe?: ChannelAuthCheckConfig; |
|
||||||
publish?: ChannelAuthCheckConfig; |
|
||||||
} |
|
||||||
export interface ChangeLogOutputConfig { |
|
||||||
fieldName: string; |
|
||||||
channel: string; |
|
||||||
} |
|
||||||
export interface RemoteWriteOutputConfig { |
|
||||||
uid: string; |
|
||||||
sampleMilliseconds: number; |
|
||||||
} |
|
||||||
export interface ThresholdOutputConfig { |
|
||||||
fieldName: string; |
|
||||||
channel: string; |
|
||||||
} |
|
||||||
export interface NumberCompareFrameConditionConfig { |
|
||||||
fieldName: string; |
|
||||||
op: string; |
|
||||||
value: number; |
|
||||||
} |
|
||||||
export interface MultipleFrameConditionCheckerConfig { |
|
||||||
conditionType: string; |
|
||||||
conditions: FrameConditionCheckerConfig[]; |
|
||||||
} |
|
||||||
export interface FrameConditionCheckerConfig { |
|
||||||
type: Omit<keyof FrameConditionCheckerConfig, 'type'>; |
|
||||||
multiple?: MultipleFrameConditionCheckerConfig; |
|
||||||
numberCompare?: NumberCompareFrameConditionConfig; |
|
||||||
} |
|
||||||
export interface ConditionalOutputConfig { |
|
||||||
condition?: FrameConditionCheckerConfig; |
|
||||||
output?: FrameOutputterConfig; |
|
||||||
} |
|
||||||
export interface RedirectOutputConfig { |
|
||||||
channel: string; |
|
||||||
} |
|
||||||
export interface MultipleOutputterConfig { |
|
||||||
outputs: FrameOutputterConfig[]; |
|
||||||
} |
|
||||||
export interface ManagedStreamOutputConfig {} |
|
||||||
export interface FrameOutputterConfig { |
|
||||||
type: Omit<keyof FrameOutputterConfig, 'type'>; |
|
||||||
managedStream?: ManagedStreamOutputConfig; |
|
||||||
multiple?: MultipleOutputterConfig; |
|
||||||
redirect?: RedirectOutputConfig; |
|
||||||
conditional?: ConditionalOutputConfig; |
|
||||||
threshold?: ThresholdOutputConfig; |
|
||||||
remoteWrite?: RemoteWriteOutputConfig; |
|
||||||
loki?: LokiOutputConfig; |
|
||||||
changeLog?: ChangeLogOutputConfig; |
|
||||||
} |
|
||||||
export interface MultipleFrameProcessorConfig { |
|
||||||
processors: FrameProcessorConfig[]; |
|
||||||
} |
|
||||||
export interface KeepFieldsFrameProcessorConfig { |
|
||||||
fieldNames: string[]; |
|
||||||
} |
|
||||||
export interface DropFieldsFrameProcessorConfig { |
|
||||||
fieldNames: string[]; |
|
||||||
} |
|
||||||
export interface FrameProcessorConfig { |
|
||||||
type: Omit<keyof FrameProcessorConfig, 'type'>; |
|
||||||
dropFields?: DropFieldsFrameProcessorConfig; |
|
||||||
keepFields?: KeepFieldsFrameProcessorConfig; |
|
||||||
multiple?: MultipleFrameProcessorConfig; |
|
||||||
} |
|
||||||
export interface JsonFrameConverterConfig {} |
|
||||||
export interface AutoInfluxConverterConfig { |
|
||||||
frameFormat: string; |
|
||||||
} |
|
||||||
export interface ExactJsonConverterConfig { |
|
||||||
fields: Field[]; |
|
||||||
} |
|
||||||
export interface Label { |
|
||||||
name: string; |
|
||||||
value: string; |
|
||||||
} |
|
||||||
export interface Field { |
|
||||||
name: string; |
|
||||||
type: number; |
|
||||||
value: string; |
|
||||||
labels?: Label[]; |
|
||||||
config?: FieldConfig; |
|
||||||
} |
|
||||||
export interface AutoJsonConverterConfig { |
|
||||||
fieldTips?: { [key: string]: Field }; |
|
||||||
} |
|
||||||
export interface ConverterConfig { |
|
||||||
type: Omit<keyof ConverterConfig, 'type'>; |
|
||||||
jsonAuto?: AutoJsonConverterConfig; |
|
||||||
jsonExact?: ExactJsonConverterConfig; |
|
||||||
influxAuto?: AutoInfluxConverterConfig; |
|
||||||
jsonFrame?: JsonFrameConverterConfig; |
|
||||||
} |
|
||||||
export interface LokiOutputConfig { |
|
||||||
uid: string; |
|
||||||
} |
|
||||||
export interface RedirectDataOutputConfig { |
|
||||||
channel: string; |
|
||||||
} |
|
||||||
export interface DataOutputterConfig { |
|
||||||
type: Omit<keyof DataOutputterConfig, 'type'>; |
|
||||||
redirect?: RedirectDataOutputConfig; |
|
||||||
loki?: LokiOutputConfig; |
|
||||||
} |
|
||||||
export interface MultipleSubscriberConfig { |
|
||||||
subscribers: SubscriberConfig[]; |
|
||||||
} |
|
||||||
export interface SubscriberConfig { |
|
||||||
type: Omit<keyof SubscriberConfig, 'type'>; |
|
||||||
multiple?: MultipleSubscriberConfig; |
|
||||||
} |
|
||||||
export interface ChannelRuleSettings { |
|
||||||
auth?: ChannelAuthConfig; |
|
||||||
subscribers?: SubscriberConfig[]; |
|
||||||
dataOutputs?: DataOutputterConfig[]; |
|
||||||
converter?: ConverterConfig; |
|
||||||
frameProcessors?: FrameProcessorConfig[]; |
|
||||||
frameOutputs?: FrameOutputterConfig[]; |
|
||||||
} |
|
||||||
export interface ChannelRule { |
|
||||||
pattern: string; |
|
||||||
settings: ChannelRuleSettings; |
|
||||||
} |
|
Loading…
Reference in new issue