mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Live: Remove (alpha) ability to configure live pipelines (#65138)
This commit is contained in:
@@ -3469,36 +3469,6 @@ exports[`better eslint`] = {
|
||||
"public/app/features/live/index.ts:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
"public/app/features/live/pages/AddNewRule.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/features/live/pages/PipelineAdminPage.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/features/live/pages/PipelineTable.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/features/live/pages/RuleModal.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "1"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "2"]
|
||||
],
|
||||
"public/app/features/live/pages/RuleTest.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "1"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "2"]
|
||||
],
|
||||
"public/app/features/live/pages/types.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "4"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "5"]
|
||||
],
|
||||
"public/app/features/live/pages/utils.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/features/logs/components/LogRowContextProvider.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "1"],
|
||||
|
||||
@@ -56,7 +56,6 @@ Alpha features might be changed or removed without prior notice.
|
||||
| ---------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
||||
| `alertingBigTransactions` | Use big transactions for alerting database writes |
|
||||
| `dashboardPreviews` | Create and show thumbnails for dashboard search results |
|
||||
| `live-pipeline` | Enable a generic live processing pipeline |
|
||||
| `live-service-web-worker` | This will use a webworker thread to processes events rather than the main thread |
|
||||
| `queryOverLive` | Use Grafana Live WebSocket to execute backend queries |
|
||||
| `publicDashboards` | Enables public access to dashboards |
|
||||
|
||||
@@ -23,7 +23,6 @@ export interface FeatureToggles {
|
||||
disableEnvelopeEncryption?: boolean;
|
||||
database_metrics?: boolean;
|
||||
dashboardPreviews?: boolean;
|
||||
['live-pipeline']?: boolean;
|
||||
['live-service-web-worker']?: boolean;
|
||||
queryOverLive?: boolean;
|
||||
panelTitleSearch?: boolean;
|
||||
|
||||
@@ -591,21 +591,6 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
|
||||
// Some channels may have info
|
||||
liveRoute.Get("/info/*", routing.Wrap(hs.Live.HandleInfoHTTP))
|
||||
|
||||
if hs.Features.IsEnabled(featuremgmt.FlagLivePipeline) {
|
||||
// POST Live data to be processed according to channel rules.
|
||||
liveRoute.Post("/pipeline/push/*", hs.LivePushGateway.HandlePipelinePush)
|
||||
liveRoute.Post("/pipeline-convert-test", routing.Wrap(hs.Live.HandlePipelineConvertTestHTTP), reqOrgAdmin)
|
||||
liveRoute.Get("/pipeline-entities", routing.Wrap(hs.Live.HandlePipelineEntitiesListHTTP), reqOrgAdmin)
|
||||
liveRoute.Get("/channel-rules", routing.Wrap(hs.Live.HandleChannelRulesListHTTP), reqOrgAdmin)
|
||||
liveRoute.Post("/channel-rules", routing.Wrap(hs.Live.HandleChannelRulesPostHTTP), reqOrgAdmin)
|
||||
liveRoute.Put("/channel-rules", routing.Wrap(hs.Live.HandleChannelRulesPutHTTP), reqOrgAdmin)
|
||||
liveRoute.Delete("/channel-rules", routing.Wrap(hs.Live.HandleChannelRulesDeleteHTTP), reqOrgAdmin)
|
||||
liveRoute.Get("/write-configs", routing.Wrap(hs.Live.HandleWriteConfigsListHTTP), reqOrgAdmin)
|
||||
liveRoute.Post("/write-configs", routing.Wrap(hs.Live.HandleWriteConfigsPostHTTP), reqOrgAdmin)
|
||||
liveRoute.Put("/write-configs", routing.Wrap(hs.Live.HandleWriteConfigsPutHTTP), reqOrgAdmin)
|
||||
liveRoute.Delete("/write-configs", routing.Wrap(hs.Live.HandleWriteConfigsDeleteHTTP), reqOrgAdmin)
|
||||
}
|
||||
})
|
||||
|
||||
// short urls
|
||||
|
||||
@@ -39,12 +39,6 @@ var (
|
||||
State: FeatureStateAlpha,
|
||||
Owner: grafanaAppPlatformSquad,
|
||||
},
|
||||
{
|
||||
Name: "live-pipeline",
|
||||
Description: "Enable a generic live processing pipeline",
|
||||
State: FeatureStateAlpha,
|
||||
Owner: grafanaAppPlatformSquad,
|
||||
},
|
||||
{
|
||||
Name: "live-service-web-worker",
|
||||
Description: "This will use a webworker thread to processes events rather than the main thread",
|
||||
|
||||
@@ -4,7 +4,6 @@ trimDefaults,beta,@grafana/grafana-as-code,false,false,false,false
|
||||
disableEnvelopeEncryption,stable,@grafana/grafana-as-code,false,false,false,false
|
||||
database_metrics,stable,@grafana/hosted-grafana-team,false,false,false,false
|
||||
dashboardPreviews,alpha,@grafana/grafana-app-platform-squad,false,false,false,false
|
||||
live-pipeline,alpha,@grafana/grafana-app-platform-squad,false,false,false,false
|
||||
live-service-web-worker,alpha,@grafana/grafana-app-platform-squad,false,false,false,true
|
||||
queryOverLive,alpha,@grafana/grafana-app-platform-squad,false,false,false,true
|
||||
panelTitleSearch,beta,@grafana/grafana-app-platform-squad,false,false,false,false
|
||||
|
||||
|
@@ -27,10 +27,6 @@ const (
|
||||
// Create and show thumbnails for dashboard search results
|
||||
FlagDashboardPreviews = "dashboardPreviews"
|
||||
|
||||
// FlagLivePipeline
|
||||
// Enable a generic live processing pipeline
|
||||
FlagLivePipeline = "live-pipeline"
|
||||
|
||||
// FlagLiveServiceWebWorker
|
||||
// This will use a webworker thread to processes events rather than the main thread
|
||||
FlagLiveServiceWebWorker = "live-service-web-worker"
|
||||
|
||||
@@ -24,7 +24,6 @@ func TestFeatureToggleFiles(t *testing.T) {
|
||||
"httpclientprovider_azure_auth": true,
|
||||
"service-accounts": true,
|
||||
"database_metrics": true,
|
||||
"live-pipeline": true,
|
||||
"live-service-web-worker": true,
|
||||
"k8s": true, // Camel case does not like this one
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -182,53 +181,6 @@ func ProvideService(plugCtxProvider *plugincontext.Provider, cfg *setting.Cfg, r
|
||||
}
|
||||
|
||||
g.ManagedStreamRunner = managedStreamRunner
|
||||
if g.Features.IsEnabled(featuremgmt.FlagLivePipeline) {
|
||||
var builder pipeline.RuleBuilder
|
||||
if os.Getenv("GF_LIVE_DEV_BUILDER") != "" {
|
||||
builder = &pipeline.DevRuleBuilder{
|
||||
Node: node,
|
||||
ManagedStream: g.ManagedStreamRunner,
|
||||
FrameStorage: pipeline.NewFrameStorage(),
|
||||
ChannelHandlerGetter: g,
|
||||
}
|
||||
} else {
|
||||
storage := &pipeline.FileStorage{
|
||||
DataPath: cfg.DataPath,
|
||||
SecretsService: g.SecretsService,
|
||||
}
|
||||
g.pipelineStorage = storage
|
||||
builder = &pipeline.StorageRuleBuilder{
|
||||
Node: node,
|
||||
ManagedStream: g.ManagedStreamRunner,
|
||||
FrameStorage: pipeline.NewFrameStorage(),
|
||||
Storage: storage,
|
||||
ChannelHandlerGetter: g,
|
||||
SecretsService: g.SecretsService,
|
||||
}
|
||||
}
|
||||
channelRuleGetter := pipeline.NewCacheSegmentedTree(builder)
|
||||
|
||||
// Pre-build/validate channel rules for all organizations on start.
|
||||
// This can be unreasonable to have in production scenario with many
|
||||
// organizations.
|
||||
orgQuery := &org.SearchOrgsQuery{}
|
||||
|
||||
result, err := orgService.Search(context.Background(), orgQuery)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't get org list: %w", err)
|
||||
}
|
||||
for _, org := range result {
|
||||
_, _, err := channelRuleGetter.Get(org.ID, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error building channel rules for org %d: %w", org.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
g.Pipeline, err = pipeline.New(channelRuleGetter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
g.contextGetter = liveplugin.NewContextGetter(g.PluginContextProvider, g.DataSourceCache)
|
||||
pipelinedChannelLocalPublisher := liveplugin.NewChannelLocalPublisher(node, g.Pipeline)
|
||||
|
||||
@@ -149,31 +149,6 @@ func (s *ServiceImpl) GetNavTree(c *contextmodel.ReqContext, hasEditPerm bool, p
|
||||
}
|
||||
}
|
||||
|
||||
if s.features.IsEnabled(featuremgmt.FlagLivePipeline) {
|
||||
liveNavLinks := []*navtree.NavLink{}
|
||||
|
||||
liveNavLinks = append(liveNavLinks, &navtree.NavLink{
|
||||
Text: "Status", Id: "live-status", Url: s.cfg.AppSubURL + "/live", Icon: "exchange-alt",
|
||||
})
|
||||
liveNavLinks = append(liveNavLinks, &navtree.NavLink{
|
||||
Text: "Pipeline", Id: "live-pipeline", Url: s.cfg.AppSubURL + "/live/pipeline", Icon: "arrow-to-right",
|
||||
})
|
||||
liveNavLinks = append(liveNavLinks, &navtree.NavLink{
|
||||
Text: "Cloud", Id: "live-cloud", Url: s.cfg.AppSubURL + "/live/cloud", Icon: "cloud-upload",
|
||||
})
|
||||
|
||||
treeRoot.AddSection(&navtree.NavLink{
|
||||
Id: "live",
|
||||
Text: "Live",
|
||||
SubTitle: "Event streaming",
|
||||
Icon: "exchange-alt",
|
||||
Url: s.cfg.AppSubURL + "/live",
|
||||
Children: liveNavLinks,
|
||||
Section: navtree.NavSectionConfig,
|
||||
HideFromTabs: true,
|
||||
})
|
||||
}
|
||||
|
||||
orgAdminNode, err := s.getOrgAdminNode(c)
|
||||
|
||||
if orgAdminNode != nil {
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
)
|
||||
|
||||
var random20HzStreamRegex = regexp.MustCompile(`random-20Hz-stream(-\d+)?`)
|
||||
@@ -34,11 +33,6 @@ func (s *Service) SubscribeStream(ctx context.Context, req *backend.SubscribeStr
|
||||
}
|
||||
}
|
||||
|
||||
if s.features.IsEnabled(featuremgmt.FlagLivePipeline) {
|
||||
// While developing Live pipeline avoid sending initial data.
|
||||
initialData = nil
|
||||
}
|
||||
|
||||
return &backend.SubscribeStreamResponse{
|
||||
Status: backend.SubscribeStreamStatusOK,
|
||||
InitialData: initialData,
|
||||
@@ -121,10 +115,6 @@ func (s *Service) runTestStream(ctx context.Context, path string, conf testStrea
|
||||
}
|
||||
|
||||
mode := data.IncludeDataOnly
|
||||
if s.features.IsEnabled(featuremgmt.FlagLivePipeline) {
|
||||
mode = data.IncludeAll
|
||||
}
|
||||
|
||||
delta := rand.Float64() - 0.5
|
||||
walker += delta
|
||||
|
||||
|
||||
@@ -109,14 +109,6 @@ export function getNavTitle(navId: string | undefined) {
|
||||
return t('nav.storage.title', 'Storage');
|
||||
case 'upgrading':
|
||||
return t('nav.upgrading.title', 'Stats and license');
|
||||
case 'live':
|
||||
return t('nav.live.title', 'Event streaming');
|
||||
case 'live-status':
|
||||
return t('nav.live-status.title', 'Status');
|
||||
case 'live-pipeline':
|
||||
return t('nav.live-pipeline.title', 'Pipeline');
|
||||
case 'live-cloud':
|
||||
return t('nav.live-cloud.title', 'Cloud');
|
||||
case 'monitoring':
|
||||
return t('nav.monitoring.title', 'Monitoring');
|
||||
case 'apps':
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import LdapPage from 'app/features/admin/ldap/LdapPage';
|
||||
import { getAlertingRoutes } from 'app/features/alerting/routes';
|
||||
import { getRoutes as getDataConnectionsRoutes } from 'app/features/connections/routes';
|
||||
import { DATASOURCES_ROUTES } from 'app/features/datasources/constants';
|
||||
import { getLiveRoutes } from 'app/features/live/pages/routes';
|
||||
import { getRoutes as getPluginCatalogRoutes } from 'app/features/plugins/admin/routes';
|
||||
import { getAppPluginRoutes } from 'app/features/plugins/routes';
|
||||
import { getProfileRoutes } from 'app/features/profile/routes';
|
||||
@@ -503,7 +502,6 @@ export function getAppRoutes(): RouteDescriptor[] {
|
||||
...getDynamicDashboardRoutes(),
|
||||
...getPluginCatalogRoutes(),
|
||||
...getSupportBundleRoutes(),
|
||||
...getLiveRoutes(),
|
||||
...getAlertingRoutes(),
|
||||
...getProfileRoutes(),
|
||||
...extraRoutes,
|
||||
|
||||
@@ -243,18 +243,6 @@
|
||||
"subtitle": "Wiederverwendbare Panels, die zu mehreren Dashboards hinzugefügt werden können",
|
||||
"title": "Bibliotheks-Panels"
|
||||
},
|
||||
"live": {
|
||||
"title": "Event-Streaming"
|
||||
},
|
||||
"live-cloud": {
|
||||
"title": "Cloud"
|
||||
},
|
||||
"live-pipeline": {
|
||||
"title": "In Vorbereitung"
|
||||
},
|
||||
"live-status": {
|
||||
"title": "Status"
|
||||
},
|
||||
"manage-dashboards": {
|
||||
"title": "Durchsuchen"
|
||||
},
|
||||
|
||||
@@ -243,18 +243,6 @@
|
||||
"subtitle": "Reusable panels that can be added to multiple dashboards",
|
||||
"title": "Library panels"
|
||||
},
|
||||
"live": {
|
||||
"title": "Event streaming"
|
||||
},
|
||||
"live-cloud": {
|
||||
"title": "Cloud"
|
||||
},
|
||||
"live-pipeline": {
|
||||
"title": "Pipeline"
|
||||
},
|
||||
"live-status": {
|
||||
"title": "Status"
|
||||
},
|
||||
"manage-dashboards": {
|
||||
"title": "Browse"
|
||||
},
|
||||
|
||||
@@ -243,18 +243,6 @@
|
||||
"subtitle": "Paneles reutilizables que se pueden añadir a varios paneles de control",
|
||||
"title": "Paneles de la librería"
|
||||
},
|
||||
"live": {
|
||||
"title": "Transmisión de eventos"
|
||||
},
|
||||
"live-cloud": {
|
||||
"title": "Nube"
|
||||
},
|
||||
"live-pipeline": {
|
||||
"title": "Cartera"
|
||||
},
|
||||
"live-status": {
|
||||
"title": "Estado"
|
||||
},
|
||||
"manage-dashboards": {
|
||||
"title": "Navegar"
|
||||
},
|
||||
|
||||
@@ -243,18 +243,6 @@
|
||||
"subtitle": "Panneaux réutilisables pouvant être ajoutés à plusieurs tableaux de bord",
|
||||
"title": "Panneaux de bibliothèque"
|
||||
},
|
||||
"live": {
|
||||
"title": "Diffusion d'événements"
|
||||
},
|
||||
"live-cloud": {
|
||||
"title": "Cloud"
|
||||
},
|
||||
"live-pipeline": {
|
||||
"title": "Pipeline"
|
||||
},
|
||||
"live-status": {
|
||||
"title": "Statut"
|
||||
},
|
||||
"manage-dashboards": {
|
||||
"title": "Parcourir"
|
||||
},
|
||||
|
||||
@@ -243,18 +243,6 @@
|
||||
"subtitle": "Ŗęūşäþľę päʼnęľş ŧĥäŧ čäʼn þę äđđęđ ŧő mūľŧįpľę đäşĥþőäřđş",
|
||||
"title": "Ŀįþřäřy päʼnęľş"
|
||||
},
|
||||
"live": {
|
||||
"title": "Ēvęʼnŧ şŧřęämįʼnģ"
|
||||
},
|
||||
"live-cloud": {
|
||||
"title": "Cľőūđ"
|
||||
},
|
||||
"live-pipeline": {
|
||||
"title": "Pįpęľįʼnę"
|
||||
},
|
||||
"live-status": {
|
||||
"title": "Ŝŧäŧūş"
|
||||
},
|
||||
"manage-dashboards": {
|
||||
"title": "ßřőŵşę"
|
||||
},
|
||||
|
||||
@@ -243,18 +243,6 @@
|
||||
"subtitle": "可重复使用的面板,可以添加到多个仪表板",
|
||||
"title": "库面板"
|
||||
},
|
||||
"live": {
|
||||
"title": "事件流"
|
||||
},
|
||||
"live-cloud": {
|
||||
"title": "云"
|
||||
},
|
||||
"live-pipeline": {
|
||||
"title": "管道"
|
||||
},
|
||||
"live-status": {
|
||||
"title": "状态"
|
||||
},
|
||||
"manage-dashboards": {
|
||||
"title": "浏览"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user