Live: remove admin pages, add alpha panel (#28101)
This commit is contained in:
@@ -152,6 +152,13 @@ export interface LiveChannelAddress {
|
||||
path: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the address has a scope, namespace, and path
|
||||
*/
|
||||
export function isValidLiveChannelAddress(addr?: LiveChannelAddress): addr is LiveChannelAddress {
|
||||
return !!(addr?.path && addr.namespace && addr.scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
|
||||
@@ -56,7 +56,6 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
r.Get("/admin/orgs", reqGrafanaAdmin, hs.Index)
|
||||
r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, hs.Index)
|
||||
r.Get("/admin/stats", reqGrafanaAdmin, hs.Index)
|
||||
r.Get("/admin/live", reqGrafanaAdmin, hs.Index)
|
||||
r.Get("/admin/ldap", reqGrafanaAdmin, hs.Index)
|
||||
|
||||
r.Get("/styleguide", reqSignedIn, hs.Index)
|
||||
|
||||
@@ -287,12 +287,6 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool) ([]*dto
|
||||
{Text: "Stats", Id: "server-stats", Url: setting.AppSubUrl + "/admin/stats", Icon: "graph-bar"},
|
||||
}
|
||||
|
||||
if hs.Live != nil {
|
||||
adminNavLinks = append(adminNavLinks, &dtos.NavLink{
|
||||
Text: "Live", Id: "live", Url: setting.AppSubUrl + "/admin/live", Icon: "water",
|
||||
})
|
||||
}
|
||||
|
||||
if setting.LDAPEnabled {
|
||||
adminNavLinks = append(adminNavLinks, &dtos.NavLink{
|
||||
Text: "LDAP", Id: "ldap", Url: setting.AppSubUrl + "/admin/ldap", Icon: "book",
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { hot } from 'react-hot-loader';
|
||||
import { connect } from 'react-redux';
|
||||
import { css } from 'emotion';
|
||||
import { StoreState } from 'app/types';
|
||||
import { getNavModel } from 'app/core/selectors/navModel';
|
||||
import Page from 'app/core/components/Page/Page';
|
||||
import {
|
||||
NavModel,
|
||||
SelectableValue,
|
||||
FeatureState,
|
||||
LiveChannelScope,
|
||||
LiveChannelConfig,
|
||||
LiveChannelSupport,
|
||||
} from '@grafana/data';
|
||||
import { LivePanel } from './LivePanel';
|
||||
import { Select, FeatureInfoBox, Container } from '@grafana/ui';
|
||||
import { getGrafanaLiveCentrifugeSrv } from '../live/live';
|
||||
|
||||
interface Props {
|
||||
navModel: NavModel;
|
||||
}
|
||||
|
||||
const scopes: Array<SelectableValue<LiveChannelScope>> = [
|
||||
{ label: 'Grafana', value: LiveChannelScope.Grafana, description: 'Core grafana live features' },
|
||||
{ label: 'Data Sources', value: LiveChannelScope.DataSource, description: 'Data sources with live support' },
|
||||
{ label: 'Plugins', value: LiveChannelScope.Plugin, description: 'Plugins with live support' },
|
||||
];
|
||||
|
||||
interface State {
|
||||
scope: LiveChannelScope;
|
||||
namespace?: string;
|
||||
path?: string;
|
||||
|
||||
namespaces: Array<SelectableValue<string>>;
|
||||
paths: Array<SelectableValue<string>>;
|
||||
support?: LiveChannelSupport;
|
||||
config?: LiveChannelConfig;
|
||||
}
|
||||
|
||||
export class LiveAdmin extends PureComponent<Props, State> {
|
||||
state: State = {
|
||||
scope: LiveChannelScope.Grafana,
|
||||
namespace: 'testdata',
|
||||
path: 'random-2s-stream',
|
||||
namespaces: [],
|
||||
paths: [],
|
||||
};
|
||||
// onTextChanged: ((event: FormEvent<HTMLInputElement>) => void) | undefined;
|
||||
// onPublish: ((event: MouseEvent<HTMLButtonElement, MouseEvent>) => void) | undefined;
|
||||
|
||||
async componentDidMount() {
|
||||
const { scope, namespace, path } = this.state;
|
||||
const srv = getGrafanaLiveCentrifugeSrv();
|
||||
const namespaces = await srv.scopes[scope].listNamespaces();
|
||||
const support = namespace ? await srv.scopes[scope].getChannelSupport(namespace) : undefined;
|
||||
const paths = support ? await support.getSupportedPaths() : undefined;
|
||||
const config = support && path ? await support.getChannelConfig(path) : undefined;
|
||||
|
||||
this.setState({
|
||||
namespaces,
|
||||
support,
|
||||
paths: paths
|
||||
? paths.map(p => ({
|
||||
label: p.path,
|
||||
value: p.path,
|
||||
description: p.description,
|
||||
}))
|
||||
: [],
|
||||
config,
|
||||
});
|
||||
}
|
||||
|
||||
onScopeChanged = async (v: SelectableValue<LiveChannelScope>) => {
|
||||
if (v.value) {
|
||||
const srv = getGrafanaLiveCentrifugeSrv();
|
||||
|
||||
this.setState({
|
||||
scope: v.value,
|
||||
namespace: undefined,
|
||||
path: undefined,
|
||||
namespaces: await srv.scopes[v.value!].listNamespaces(),
|
||||
paths: [],
|
||||
support: undefined,
|
||||
config: undefined,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onNamespaceChanged = async (v: SelectableValue<string>) => {
|
||||
if (v.value) {
|
||||
const namespace = v.value;
|
||||
const srv = getGrafanaLiveCentrifugeSrv();
|
||||
const support = await srv.scopes[this.state.scope].getChannelSupport(namespace);
|
||||
|
||||
this.setState({
|
||||
namespace: v.value,
|
||||
paths: support!.getSupportedPaths().map(p => ({
|
||||
label: p.path,
|
||||
value: p.path,
|
||||
description: p.description,
|
||||
})),
|
||||
path: undefined,
|
||||
config: undefined,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onPathChanged = async (v: SelectableValue<string>) => {
|
||||
if (v.value) {
|
||||
const path = v.value;
|
||||
const srv = getGrafanaLiveCentrifugeSrv();
|
||||
const support = await srv.scopes[this.state.scope].getChannelSupport(this.state.namespace!);
|
||||
if (!support) {
|
||||
this.setState({
|
||||
namespace: undefined,
|
||||
paths: [],
|
||||
config: undefined,
|
||||
support,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
path,
|
||||
support,
|
||||
config: support.getChannelConfig(path),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { navModel } = this.props;
|
||||
const { scope, namespace, namespaces, path, paths, config } = this.state;
|
||||
|
||||
return (
|
||||
<Page navModel={navModel}>
|
||||
<Page.Contents>
|
||||
<Container grow={1}>
|
||||
<FeatureInfoBox
|
||||
title="Grafana Live"
|
||||
featureState={FeatureState.alpha}
|
||||
// url={getDocsLink(DocsId.Transformations)}
|
||||
>
|
||||
<p>
|
||||
This supports real-time event streams in grafana core. This feature is under heavy development. Expect
|
||||
the intefaces and structures to change as this becomes more production ready.
|
||||
</p>
|
||||
</FeatureInfoBox>
|
||||
<br />
|
||||
<br />
|
||||
</Container>
|
||||
|
||||
<div
|
||||
className={css`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
> div {
|
||||
margin-right: 8px;
|
||||
min-width: 150px;
|
||||
}
|
||||
`}
|
||||
>
|
||||
<div>
|
||||
<h5>Scope</h5>
|
||||
<Select options={scopes} value={scopes.find(s => s.value === scope)} onChange={this.onScopeChanged} />
|
||||
</div>
|
||||
<div>
|
||||
<h5>Namespace</h5>
|
||||
<Select
|
||||
options={namespaces}
|
||||
value={namespaces.find(s => s.value === namespace) || namespace || ''}
|
||||
onChange={this.onNamespaceChanged}
|
||||
allowCustomValue={true}
|
||||
backspaceRemovesValue={true}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h5>Path</h5>
|
||||
<Select
|
||||
options={paths}
|
||||
value={paths.find(s => s.value === path) || path || ''}
|
||||
onChange={this.onPathChanged}
|
||||
allowCustomValue={true}
|
||||
backspaceRemovesValue={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<br />
|
||||
{scope && namespace && path && <LivePanel scope={scope} namespace={namespace} path={path} config={config} />}
|
||||
</Page.Contents>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: StoreState) => ({
|
||||
navModel: getNavModel(state.navIndex, 'live'),
|
||||
});
|
||||
|
||||
export default hot(module)(connect(mapStateToProps)(LiveAdmin));
|
||||
@@ -1,150 +0,0 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Unsubscribable, PartialObserver } from 'rxjs';
|
||||
import { getGrafanaLiveSrv } from '@grafana/runtime';
|
||||
import {
|
||||
AppEvents,
|
||||
isLiveChannelStatusEvent,
|
||||
LiveChannel,
|
||||
LiveChannelConfig,
|
||||
LiveChannelConnectionState,
|
||||
LiveChannelEvent,
|
||||
LiveChannelEventType,
|
||||
LiveChannelScope,
|
||||
LiveChannelStatusEvent,
|
||||
} from '@grafana/data';
|
||||
import { Input, Button } from '@grafana/ui';
|
||||
import { appEvents } from 'app/core/core';
|
||||
|
||||
interface Props {
|
||||
scope: LiveChannelScope;
|
||||
namespace: string;
|
||||
path: string;
|
||||
config?: LiveChannelConfig;
|
||||
}
|
||||
|
||||
interface State {
|
||||
channel?: LiveChannel;
|
||||
status: LiveChannelStatusEvent;
|
||||
count: number;
|
||||
lastTime: number;
|
||||
lastBody: string;
|
||||
text: string; // for publish!
|
||||
}
|
||||
|
||||
export class LivePanel extends PureComponent<Props, State> {
|
||||
state: State = {
|
||||
status: {
|
||||
type: LiveChannelEventType.Status,
|
||||
id: '?',
|
||||
state: LiveChannelConnectionState.Pending,
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
count: 0,
|
||||
lastTime: 0,
|
||||
lastBody: '',
|
||||
text: '',
|
||||
};
|
||||
subscription?: Unsubscribable;
|
||||
|
||||
streamObserver: PartialObserver<LiveChannelEvent> = {
|
||||
next: (event: LiveChannelEvent) => {
|
||||
if (isLiveChannelStatusEvent(event)) {
|
||||
this.setState({ status: event });
|
||||
} else {
|
||||
this.setState({
|
||||
count: this.state.count + 1,
|
||||
lastTime: Date.now(),
|
||||
lastBody: JSON.stringify(event),
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
startSubscription = () => {
|
||||
const { scope, namespace, path } = this.props;
|
||||
const channel = getGrafanaLiveSrv().getChannel({ scope, namespace, path });
|
||||
if (this.state.channel === channel) {
|
||||
return; // no change!
|
||||
}
|
||||
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
|
||||
this.subscription = channel.getStream().subscribe(this.streamObserver);
|
||||
this.setState({ channel });
|
||||
};
|
||||
|
||||
componentDidMount = () => {
|
||||
this.startSubscription();
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(oldProps: Props) {
|
||||
if (oldProps.config !== this.props.config) {
|
||||
this.startSubscription();
|
||||
}
|
||||
}
|
||||
|
||||
onTextChanged = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ text: event.target.value });
|
||||
};
|
||||
|
||||
onPublish = () => {
|
||||
const { text, channel } = this.state;
|
||||
if (text && channel) {
|
||||
const msg = {
|
||||
line: text,
|
||||
};
|
||||
|
||||
channel.publish!(msg)
|
||||
.then(v => {
|
||||
console.log('PUBLISHED', text, v);
|
||||
})
|
||||
.catch(err => {
|
||||
appEvents.emit(AppEvents.alertError, ['Publish error', `${err}`]);
|
||||
});
|
||||
}
|
||||
this.setState({ text: '' });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { lastBody, lastTime, count, status, text } = this.state;
|
||||
const { config } = this.props;
|
||||
const showPublish = config && config.canPublish && config.canPublish();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h5>Status: {config ? '' : '(no config)'}</h5>
|
||||
<pre>{JSON.stringify(status)}</pre>
|
||||
|
||||
<h5>Count: {count}</h5>
|
||||
{lastTime > 0 && (
|
||||
<>
|
||||
<h5>Last: {lastTime}</h5>
|
||||
{lastBody && (
|
||||
<div>
|
||||
<pre>{lastBody}</pre>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{showPublish && (
|
||||
<div>
|
||||
<h3>Write to channel</h3>
|
||||
<Input value={text} onChange={this.onTextChanged} />
|
||||
<Button onClick={this.onPublish} variant={text ? 'primary' : 'secondary'}>
|
||||
Publish
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,7 @@ import * as pieChartPanel from 'app/plugins/panel/piechart/module';
|
||||
import * as barGaugePanel from 'app/plugins/panel/bargauge/module';
|
||||
import * as logsPanel from 'app/plugins/panel/logs/module';
|
||||
import * as newsPanel from 'app/plugins/panel/news/module';
|
||||
import * as livePanel from 'app/plugins/panel/live/module';
|
||||
import * as homeLinksPanel from 'app/plugins/panel/homelinks/module';
|
||||
import * as welcomeBanner from 'app/plugins/panel/welcome/module';
|
||||
|
||||
@@ -90,6 +91,7 @@ const builtInPlugins: any = {
|
||||
'app/plugins/panel/table/module': tablePanel,
|
||||
'app/plugins/panel/table-old/module': oldTablePanel,
|
||||
'app/plugins/panel/news/module': newsPanel,
|
||||
'app/plugins/panel/live/module': livePanel,
|
||||
'app/plugins/panel/singlestat/module': singlestatPanel,
|
||||
'app/plugins/panel/stat/module': singlestatPanel2,
|
||||
'app/plugins/panel/gettingstarted/module': gettingStartedPanel,
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { css } from 'emotion';
|
||||
import { Select, FeatureInfoBox, Label, stylesFactory } from '@grafana/ui';
|
||||
import {
|
||||
LiveChannelScope,
|
||||
LiveChannelAddress,
|
||||
LiveChannelSupport,
|
||||
LiveChannelConfig,
|
||||
SelectableValue,
|
||||
StandardEditorProps,
|
||||
FeatureState,
|
||||
GrafanaTheme,
|
||||
} from '@grafana/data';
|
||||
|
||||
import { LivePanelOptions } from './types';
|
||||
import { getGrafanaLiveCentrifugeSrv } from 'app/features/live/live';
|
||||
import { config } from 'app/core/config';
|
||||
|
||||
type Props = StandardEditorProps<LiveChannelAddress, any, LivePanelOptions>;
|
||||
|
||||
const scopes: Array<SelectableValue<LiveChannelScope>> = [
|
||||
{ label: 'Grafana', value: LiveChannelScope.Grafana, description: 'Core grafana live features' },
|
||||
{ label: 'Data Sources', value: LiveChannelScope.DataSource, description: 'Data sources with live support' },
|
||||
{ label: 'Plugins', value: LiveChannelScope.Plugin, description: 'Plugins with live support' },
|
||||
];
|
||||
|
||||
interface State {
|
||||
namespaces: Array<SelectableValue<string>>;
|
||||
paths: Array<SelectableValue<string>>;
|
||||
support?: LiveChannelSupport;
|
||||
config?: LiveChannelConfig;
|
||||
}
|
||||
|
||||
export class LiveChannelEditor extends PureComponent<Props, State> {
|
||||
state: State = {
|
||||
namespaces: [],
|
||||
paths: [],
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
this.updateSelectOptions();
|
||||
}
|
||||
|
||||
async componentDidUpdate(oldProps: Props) {
|
||||
if (this.props.value !== oldProps.value) {
|
||||
this.updateSelectOptions();
|
||||
}
|
||||
}
|
||||
|
||||
async updateSelectOptions() {
|
||||
const { scope, namespace, path } = this.props.value;
|
||||
const srv = getGrafanaLiveCentrifugeSrv();
|
||||
const namespaces = await srv.scopes[scope].listNamespaces();
|
||||
const support = namespace ? await srv.scopes[scope].getChannelSupport(namespace) : undefined;
|
||||
const paths = support ? await support.getSupportedPaths() : undefined;
|
||||
const config = support && path ? await support.getChannelConfig(path) : undefined;
|
||||
|
||||
this.setState({
|
||||
namespaces,
|
||||
support,
|
||||
paths: paths
|
||||
? paths.map(p => ({
|
||||
label: p.path,
|
||||
value: p.path,
|
||||
description: p.description,
|
||||
}))
|
||||
: [],
|
||||
config,
|
||||
});
|
||||
}
|
||||
|
||||
onScopeChanged = (v: SelectableValue<LiveChannelScope>) => {
|
||||
if (v.value) {
|
||||
this.props.onChange({ scope: v.value } as LiveChannelAddress);
|
||||
}
|
||||
};
|
||||
|
||||
onNamespaceChanged = (v: SelectableValue<string>) => {
|
||||
const update = {
|
||||
scope: this.props.value?.scope,
|
||||
} as LiveChannelAddress;
|
||||
if (v.value) {
|
||||
update.namespace = v.value;
|
||||
}
|
||||
this.props.onChange(update);
|
||||
};
|
||||
|
||||
onPathChanged = (v: SelectableValue<string>) => {
|
||||
const { value, onChange } = this.props;
|
||||
const update = {
|
||||
scope: value.scope,
|
||||
namespace: value.namespace,
|
||||
} as LiveChannelAddress;
|
||||
if (v.value) {
|
||||
update.path = v.value;
|
||||
}
|
||||
onChange(update);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { namespaces, paths } = this.state;
|
||||
const { scope, namespace, path } = this.props.value;
|
||||
const style = getStyles(config.theme);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FeatureInfoBox title="Grafana Live" featureState={FeatureState.alpha}>
|
||||
<p>
|
||||
This supports real-time event streams in grafana core. This feature is under heavy development. Expect the
|
||||
intefaces and structures to change as this becomes more production ready.
|
||||
</p>
|
||||
</FeatureInfoBox>
|
||||
|
||||
<div>
|
||||
<div className={style.dropWrap}>
|
||||
<Label>Scope</Label>
|
||||
<Select options={scopes} value={scopes.find(s => s.value === scope)} onChange={this.onScopeChanged} />
|
||||
</div>
|
||||
|
||||
{scope && (
|
||||
<div className={style.dropWrap}>
|
||||
<Label>Namespace</Label>
|
||||
<Select
|
||||
options={namespaces}
|
||||
value={namespaces.find(s => s.value === namespace) || namespace || ''}
|
||||
onChange={this.onNamespaceChanged}
|
||||
allowCustomValue={true}
|
||||
backspaceRemovesValue={true}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{scope && namespace && (
|
||||
<div className={style.dropWrap}>
|
||||
<Label>Path</Label>
|
||||
<Select
|
||||
options={paths}
|
||||
value={paths.find(s => s.value === path) || path || ''}
|
||||
onChange={this.onPathChanged}
|
||||
allowCustomValue={true}
|
||||
backspaceRemovesValue={true}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme) => ({
|
||||
dropWrap: css`
|
||||
margin-bottom: ${theme.spacing.sm};
|
||||
`,
|
||||
}));
|
||||
Executable
+156
@@ -0,0 +1,156 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Unsubscribable, PartialObserver } from 'rxjs';
|
||||
import { CustomScrollbar, FeatureInfoBox, Label } from '@grafana/ui';
|
||||
import {
|
||||
PanelProps,
|
||||
LiveChannelStatusEvent,
|
||||
isValidLiveChannelAddress,
|
||||
LiveChannel,
|
||||
LiveChannelEvent,
|
||||
isLiveChannelStatusEvent,
|
||||
isLiveChannelMessageEvent,
|
||||
} from '@grafana/data';
|
||||
import { LivePanelOptions } from './types';
|
||||
import { getGrafanaLiveSrv } from '@grafana/runtime';
|
||||
|
||||
interface Props extends PanelProps<LivePanelOptions> {}
|
||||
|
||||
interface State {
|
||||
error?: any;
|
||||
channel?: LiveChannel;
|
||||
status?: LiveChannelStatusEvent;
|
||||
message?: any;
|
||||
}
|
||||
|
||||
export class LivePanel extends PureComponent<Props, State> {
|
||||
private readonly isValid: boolean;
|
||||
subscription?: Unsubscribable;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.isValid = !!getGrafanaLiveSrv();
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
this.loadChannel();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props): void {
|
||||
if (this.props.options?.channel !== prevProps.options?.channel) {
|
||||
this.loadChannel();
|
||||
}
|
||||
}
|
||||
|
||||
streamObserver: PartialObserver<LiveChannelEvent> = {
|
||||
next: (event: LiveChannelEvent) => {
|
||||
if (isLiveChannelStatusEvent(event)) {
|
||||
this.setState({ status: event });
|
||||
} else if (isLiveChannelMessageEvent(event)) {
|
||||
this.setState({ message: event.message });
|
||||
} else {
|
||||
console.log('ignore', event);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
unsubscribe = () => {
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
this.subscription = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
async loadChannel() {
|
||||
const addr = this.props.options?.channel;
|
||||
if (!isValidLiveChannelAddress(addr)) {
|
||||
console.log('INVALID', addr);
|
||||
this.unsubscribe();
|
||||
this.setState({
|
||||
channel: undefined,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const channel = getGrafanaLiveSrv().getChannel(addr);
|
||||
const changed = channel.id !== this.state.channel?.id;
|
||||
console.log('LOAD', addr, changed, channel);
|
||||
if (changed) {
|
||||
this.unsubscribe();
|
||||
|
||||
// Subscribe to new events
|
||||
try {
|
||||
this.subscription = channel.getStream().subscribe(this.streamObserver);
|
||||
this.setState({ channel, error: undefined });
|
||||
} catch (err) {
|
||||
this.setState({ channel: undefined, error: err });
|
||||
}
|
||||
} else {
|
||||
console.log('Same channel', channel);
|
||||
}
|
||||
}
|
||||
|
||||
renderNotEnabled() {
|
||||
const preformatted = `[feature_toggles]
|
||||
enable = live`;
|
||||
return (
|
||||
<FeatureInfoBox
|
||||
title="Grafana Live"
|
||||
style={{
|
||||
height: this.props.height,
|
||||
}}
|
||||
>
|
||||
<p>Grafana live requires a feature flag to run</p>
|
||||
|
||||
<b>custom.ini:</b>
|
||||
<pre>{preformatted}</pre>
|
||||
</FeatureInfoBox>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.isValid) {
|
||||
return this.renderNotEnabled();
|
||||
}
|
||||
const { channel, status, message, error } = this.state;
|
||||
if (!channel) {
|
||||
return (
|
||||
<FeatureInfoBox
|
||||
title="Grafana Live"
|
||||
style={{
|
||||
height: this.props.height,
|
||||
}}
|
||||
>
|
||||
<p>Use the panel editor to pick a channel</p>
|
||||
</FeatureInfoBox>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div>
|
||||
<h2>ERROR</h2>
|
||||
<div>{JSON.stringify(error)}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CustomScrollbar autoHeightMin="100%" autoHeightMax="100%">
|
||||
<Label>Status</Label>
|
||||
<pre>{JSON.stringify(status)}</pre>
|
||||
|
||||
<br />
|
||||
<Label>Message</Label>
|
||||
<pre>{JSON.stringify(message)}</pre>
|
||||
</CustomScrollbar>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" x="0px" y="0px" viewBox="0 0 100 125" style="enable-background:new 0 0 100 100;" xml:space="preserve">
|
||||
<defs><style>.cls-1{fill:#84aff1;}.cls-2{fill:url(#linear-gradient);}.cls-3{fill:#3865ab;}</style><linearGradient id="linear-gradient" y1="40.18" x2="82.99" y2="40.18" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f2cc0c"/><stop offset="1" stop-color="#ff9830"/></linearGradient></defs>
|
||||
<g>
|
||||
<path class="cls-2" d="M8.8,31.6c-2.2,0-3.9,1.7-3.9,3.9c0,2.2,1.7,3.9,3.9,3.9c26.1,0,47.3,21.2,47.3,47.3c0,2.2,1.7,3.9,3.9,3.9 c2.2,0,3.9-1.7,3.9-3.9C63.9,56.3,39.2,31.6,8.8,31.6z"/>
|
||||
<path class="cls-2" d="M8.8,47.9c-2.2,0-3.9,1.7-3.9,3.9c0,2.2,1.7,3.9,3.9,3.9c17.1,0,31.1,13.9,31.1,31c0,2.2,1.7,3.9,3.9,3.9 c2.2,0,3.9-1.7,3.9-3.9C47.7,65.3,30.2,47.9,8.8,47.9z"/>
|
||||
<path class="cls-2" d="M8.8,64.1c-2.2,0-3.9,1.7-3.9,3.9c0,2.2,1.7,3.9,3.9,3.9c8.2,0,14.8,6.6,14.8,14.8c0,2.2,1.7,3.9,3.9,3.9 s3.9-1.7,3.9-3.9C31.4,74.2,21.2,64.1,8.8,64.1z"/>
|
||||
<path class="cls-3" d="M8.8,80.4c-3.5,0-6.3,2.8-6.3,6.3c0,3.5,2.8,6.3,6.3,6.3c3.5,0,6.3-2.8,6.3-6.3C15.1,83.2,12.3,80.4,8.8,80.4z"/>
|
||||
<path class="cls-2" d="M93.6,7H8.9C6.7,7,5,8.7,5,10.9v8.6c0,2.2,1.7,3.9,3.9,3.9s3.9-1.7,3.9-3.9v-4.7h76.9v67.9H76c-2.2,0-3.9,1.7-3.9,3.9 c0,2.2,1.7,3.9,3.9,3.9h17.6c2.2,0,3.9-1.7,3.9-3.9V10.9C97.5,8.7,95.8,7,93.6,7z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
Executable
+14
@@ -0,0 +1,14 @@
|
||||
import { PanelPlugin } from '@grafana/data';
|
||||
import { LiveChannelEditor } from './LiveChannelEditor';
|
||||
import { LivePanel } from './LivePanel';
|
||||
import { LivePanelOptions } from './types';
|
||||
|
||||
export const plugin = new PanelPlugin<LivePanelOptions>(LivePanel).setPanelOptions(builder => {
|
||||
builder.addCustomEditor({
|
||||
id: 'channel',
|
||||
path: 'channel',
|
||||
name: 'Channel',
|
||||
editor: LiveChannelEditor,
|
||||
defaultValue: {},
|
||||
});
|
||||
});
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"type": "panel",
|
||||
"name": "Live",
|
||||
"id": "live",
|
||||
|
||||
"skipDataQuery": true,
|
||||
|
||||
"state": "alpha",
|
||||
|
||||
"info": {
|
||||
"author": {
|
||||
"name": "Grafana Labs",
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
"logos": {
|
||||
"small": "img/live.svg",
|
||||
"large": "img/live.svg"
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
import { LiveChannelAddress } from '@grafana/data';
|
||||
|
||||
export interface LivePanelOptions {
|
||||
channel?: LiveChannelAddress;
|
||||
}
|
||||
@@ -29,16 +29,16 @@ export class NewsPanel extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.loadFeed();
|
||||
this.loadChannel();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props): void {
|
||||
if (this.props.options.feedUrl !== prevProps.options.feedUrl) {
|
||||
this.loadFeed();
|
||||
this.loadChannel();
|
||||
}
|
||||
}
|
||||
|
||||
async loadFeed() {
|
||||
async loadChannel() {
|
||||
const { options } = this.props;
|
||||
try {
|
||||
const url = options.feedUrl
|
||||
|
||||
@@ -403,13 +403,6 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
||||
SafeDynamicImport(import(/* webpackChunkName: "ServerStats" */ 'app/features/admin/ServerStats')),
|
||||
},
|
||||
})
|
||||
.when('/admin/live', {
|
||||
template: '<react-container />',
|
||||
reloadOnSearch: false,
|
||||
resolve: {
|
||||
component: () => SafeDynamicImport(import(/* webpackChunkName: "LiveAdmin" */ 'app/features/admin/LiveAdmin')),
|
||||
},
|
||||
})
|
||||
.when('/admin/ldap', {
|
||||
template: '<react-container />',
|
||||
reloadOnSearch: false,
|
||||
|
||||
Reference in New Issue
Block a user