Skip to content

Commit

Permalink
Upgrade dependencies for Grafana 7.0 (breaking); Upgrade TypeScript t…
Browse files Browse the repository at this point in the history
…o 3.8 to circumvent a regression in eslint
  • Loading branch information
Gowee committed May 21, 2020
1 parent 7575d52 commit 6104c24
Show file tree
Hide file tree
Showing 7 changed files with 985 additions and 616 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ An alternative way is custom API or custom function as long as the target API ha
1. Install & Configure Telegraf and InfluxDB properly.
2. See [Telegraf's wiki](https://github.com/influxdata/telegraf/wiki/Traceroute) to configure MTR data collection as an input.
3. Explore database via the `influx` CLi tool, so that to make sure data is collected as expected. See [the query section](#preview-via-the-cli-tool-of-influxdb).
4. Install the Traceroute Map Panel plugin to Grafana 6.7.x.
4. Install the Traceroute Map Panel plugin to Grafana. [v0.1.0](https://github.com/Gowee/traceroute-map-panel/releases/tag/v0.1.0) is meant for Grafana 6.7.x. v0.2+ is meant for Grafana 7.0+.
1. Download [the latest tarball](https://github.com/Gowee/traceroute-map-panel/releases/latest).
2. Uncompress & put the tarball content into Grafana plugin directory (usually `/var/lib/grafana/plugins`).
5. Create a new panel in Grafana:
Expand Down
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@
"email": "whygowe@gmail.com"
},
"devDependencies": {
"@grafana/data": "latest",
"@grafana/toolkit": "latest",
"@grafana/ui": "latest"
"@grafana/data": "^7.0.0",
"@grafana/toolkit": "^7.0.0",
"@grafana/ui": "^7.0.0"
},
"resolutions": {
"@babel/preset-env": "7.9.0",
"is-promise": "2.2.2"
"is-promise": "2.2.2",
"typescript": "3.8"
},
"engines": {
"node": ">=12 <13"
Expand All @@ -44,4 +45,4 @@
"react-leaflet-markercluster": "^2.0.0",
"ts-transformer-keys": "^0.4.1"
}
}
}
135 changes: 84 additions & 51 deletions src/TracerouteMapEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React, { PureComponent, ChangeEvent } from 'react';
import { Forms, Slider } from '@grafana/ui';
import { Slider, Field, Button, TextArea, Select, Input, Switch } from '@grafana/ui';
import { PanelEditorProps, SelectableValue } from '@grafana/data';

import { TracerouteMapOptions } from './types';
import { GeoIPProviderKind, GeoIPProvider, IPInfo, CustomAPI, IP2Geo, CustomFunction } from './geoip';
import { CodeSnippets, timeout } from './utils';
// import { Switch } from '@grafana/ui/components/Forms/Legacy/Switch/Switch';

interface Props extends PanelEditorProps<TracerouteMapOptions> {}

Expand All @@ -28,7 +29,10 @@ export class TracerouteMapEditor extends PureComponent<PanelEditorProps<Tracerou
}

handleGeoIPProviderSelected = (option: SelectableValue<GeoIPProviderKind>) => {
this.setState({ geoIPProvider: this.props.options.geoIPProviders[option.value ?? 'ipsb'], test: { pending: false } });
this.setState({
geoIPProvider: this.props.options.geoIPProviders[option.value ?? 'ipsb'],
test: { pending: false },
});
};

handleGeoIPProviderChange(provider: GeoIPProvider) {
Expand Down Expand Up @@ -87,60 +91,76 @@ export class TracerouteMapEditor extends PureComponent<PanelEditorProps<Tracerou
<div className="section gf-form-group">
<h5 className="section-header">General</h5>
<div style={{ width: 300 }}>
<Forms.Field label="Wrap longitude to [0°, 360°)" description="So that it won't lay within [-180°, 0°)">
<Forms.Switch
<Field label="Wrap longitude to [0°, 360°)" description="So that it won't lay within [-180°, 0°)">
<Switch
label="switch label"
checked={options.longitude360}
onChange={event => this.handleLongitude360Switched(event?.currentTarget.checked ?? false)}
/>
</Forms.Field>
<Forms.Field label="Cluster Radius" description="Merge close points within a radius into one circle">
<Slider min={5} max={50} value={[this.props.options.mapClusterRadius]} onChange={value => this.handleMapClusterRadius(value[0])} />
</Forms.Field>
<Forms.Field label="Note">
</Field>
<Field label="Cluster Radius" description="Merge close points within a radius into one circle">
<Slider
min={5}
max={50}
value={[this.props.options.mapClusterRadius]}
onChange={value => this.handleMapClusterRadius(value[0])}
/>
</Field>
<Field label="Note">
<span>Some options won't take effect until the panel/page is refreshed.</span>
</Forms.Field>
</Field>
</div>
</div>
<div className="section gf-form-group">
<h5 className="section-header">GeoIP</h5>
<div style={{ width: 400 }}>
<Forms.Field label="Provider">
<Forms.Select options={geoIPOptions} value={this.state.geoIPProvider.kind} onChange={this.handleGeoIPProviderSelected} />
</Forms.Field>
<Field label="Provider">
<Select
options={geoIPOptions}
value={this.state.geoIPProvider.kind}
onChange={this.handleGeoIPProviderSelected}
/>
</Field>
{(() => {
switch (this.state.geoIPProvider.kind) {
case 'ipinfo':
return <IPInfoConfig onChange={this.handleGeoIPProviderChange} config={this.state.geoIPProvider} />;
case 'ipsb':
return <IPSBConfig />;
case 'custom-api':
return <CustomAPIConfig onChange={this.handleGeoIPProviderChange} config={this.state.geoIPProvider} />;
return (
<CustomAPIConfig onChange={this.handleGeoIPProviderChange} config={this.state.geoIPProvider} />
);
case 'custom-function':
return <CustomFunctionConfig onChange={this.handleGeoIPProviderChange} config={this.state.geoIPProvider} />;
return (
<CustomFunctionConfig onChange={this.handleGeoIPProviderChange} config={this.state.geoIPProvider} />
);
}
})()}
</div>
<Forms.Field>
<Field>
<>
<Forms.Button icon={this.state.test.pending ? 'fa fa-spinner fa-spin' : undefined} onClick={this.handleTestAndSave}>
<Button onClick={this.handleTestAndSave}>
{/* @grafana/ui IconName types prevents using of fa-spin */}
{this.state.test.pending ? <i className="fa fa-spinner fa-spin icon-right-space" /> : <></>}
Test and Save
</Forms.Button>
<span style={{ marginLeft: '0.5em', marginRight: '0.5em' }}></span>
<Forms.Button variant="secondary" onClick={this.handleClearGeoIPCache}>
</Button>
<span className="hspace"></span>
<Button variant="secondary" onClick={this.handleClearGeoIPCache}>
Clear Cache
</Forms.Button>
</Button>
</>
</Forms.Field>
</Field>

{this.state.test.title ? (
<Forms.Field label="">
<Field label="">
<>
<span style={{ fontWeight: 'bold' }}>{this.state.test.title}</span>
<pre>
<code>{this.state.test.output}</code>
</pre>
</>
</Forms.Field>
</Field>
) : (
<></>
)}
Expand All @@ -152,56 +172,64 @@ export class TracerouteMapEditor extends PureComponent<PanelEditorProps<Tracerou

const geoIPOptions: Array<SelectableValue<GeoIPProviderKind>> = [
{ label: 'IPInfo.io', value: 'ipinfo', description: 'API provided by IPInfo.io.' },
{ label: 'IP.sb (MaxMind GeoLite2)', value: 'ipsb', description: "IP.sb free API, backed by MaxMind's GeoLite2 database." },
{
label: 'IP.sb (MaxMind GeoLite2)',
value: 'ipsb',
description: "IP.sb free API, backed by MaxMind's GeoLite2 database.",
},
{ label: 'Custom API', value: 'custom-api', description: 'Custom API defined by a URL' },
{ label: 'Custom function', value: 'custom-function', description: 'Custom JavaScript function.' },
];

const IPSBConfig: React.FC = () => {
return (
<Forms.Field label="Note">
<Field label="Note">
<span>
<a href="https://ip.sb/api/">IP.sb</a> provides with free IP-to-GeoLocation API without registration. Their data comes from{' '}
<a href="https://www.maxmind.com/">MaxMind</a>'s GeoLite2 database (<a href="https://github.com/fcambus/telize">telize</a>), which is
inaccurate sometimes.
<a href="https://ip.sb/api/">IP.sb</a> provides with free IP-to-GeoLocation API without registration. Their data
comes from <a href="https://www.maxmind.com/">MaxMind</a>'s GeoLite2 database (
<a href="https://github.com/fcambus/telize">telize</a>), which is inaccurate sometimes.
</span>
</Forms.Field>
</Field>
);
};

const IPInfoConfig: React.FC<{ config: IPInfo; onChange: (config: IPInfo) => void }> = ({ config, onChange }) => {
return (
<>
<Forms.Field label="Access Token" description="optional">
<Forms.Input
<Field label="Access Token" description="optional">
<Input
type="text"
value={config.token}
placeholder="Usually in the form 0a1b2c3d4e5f6e7d"
onChange={(event: ChangeEvent<HTMLInputElement>) => onChange({ ...config, token: event.target.value })}
/>
</Forms.Field>
<Forms.Field label="Note">
</Field>
<Field label="Note">
<span>
<a href="https://IPInfo.io">IPInfo.io</a> is generally more accurate compared to MaxMind's GeoLite2 database. The API access token is
optional, but requests without token is rate-limited. After registration, their free plan provides with 50k lookups per month.
<a href="https://IPInfo.io">IPInfo.io</a> is generally more accurate compared to MaxMind's GeoLite2 database.
The API access token is optional, but requests without token is rate-limited. After registration, their free
plan provides with 50k lookups per month.
</span>
</Forms.Field>
</Field>
</>
);
};

const CustomAPIConfig: React.FC<{ config: CustomAPI; onChange: (config: CustomAPI) => void }> = ({ config, onChange }) => {
const CustomAPIConfig: React.FC<{ config: CustomAPI; onChange: (config: CustomAPI) => void }> = ({
config,
onChange,
}) => {
return (
<>
<Forms.Field label="API URL">
<Forms.Input
<Field label="API URL">
<Input
type="text"
value={config.url}
placeholder="e.g. https://example.org/geoip/{IP}"
onChange={(event: ChangeEvent<HTMLInputElement>) => onChange({ ...config, url: event.target.value })}
/>
</Forms.Field>
<Forms.Field label="Note">
</Field>
<Field label="Note">
<>
<p>
<code>{'{IP}'}</code> in the URL will replaced to the actual IP address.
Expand All @@ -211,30 +239,35 @@ const CustomAPIConfig: React.FC<{ config: CustomAPI; onChange: (config: CustomAP
<pre>
<code>{CodeSnippets.ipgeoInterface}</code>
</pre>
with <code>Content-Type: application/json</code> and proper <code>Access-Control-Allow-Origin</code> HTTP header set.
with <code>Content-Type: application/json</code> and proper <code>Access-Control-Allow-Origin</code> HTTP
header set.
</p>
<p>
<strong>Example</strong>: <a href="https://github.com/Gowee/traceroute-map-panel/blob/master/ipip-cfworker.js">ipip-cfworker.js</a>
<strong>Example</strong>:{' '}
<a href="https://github.com/Gowee/traceroute-map-panel/blob/master/ipip-cfworker.js">ipip-cfworker.js</a>
</p>
</>
</Forms.Field>
</Field>
</>
);
};

const CustomFunctionConfig: React.FC<{ config: CustomFunction; onChange: (config: CustomFunction) => void }> = ({ config, onChange }) => {
const CustomFunctionConfig: React.FC<{ config: CustomFunction; onChange: (config: CustomFunction) => void }> = ({
config,
onChange,
}) => {
return (
<>
<Forms.Field label="Code">
<Forms.TextArea
<Field label="Code">
<TextArea
value={config.code}
rows={15}
placeholder={CodeSnippets.ip2geoFunction}
style={{ fontFamily: 'monospace' }}
onChange={(event: ChangeEvent<HTMLTextAreaElement>) => onChange({ ...config, code: event.target.value })}
/>
</Forms.Field>
<Forms.Field label="Note">
</Field>
<Field label="Note">
<p>
The JavaScript function is expected to match the signature:
<pre>
Expand All @@ -245,7 +278,7 @@ const CustomFunctionConfig: React.FC<{ config: CustomFunction; onChange: (config
<code>{CodeSnippets.ipgeoInterface}</code>
</pre>
</p>
</Forms.Field>
</Field>
</>
);
};
Expand Down
15 changes: 10 additions & 5 deletions src/TracerouteMapPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ export class TracerouteMapPanel extends Component<Props, State> {
}
}

async processData(series: DataFrame[]): Promise<{ data: Map<string, PathPoint[]>; mapBounds: Map<string, LatLngTuple[]> }> {
async processData(
series: DataFrame[]
): Promise<{ data: Map<string, PathPoint[]>; mapBounds: Map<string, LatLngTuple[]> }> {
if (series.length !== 1 || series[0].fields.length !== 7) {
throw new Error('No query data or not formatted as table.');
}
Expand Down Expand Up @@ -232,7 +234,7 @@ export class TracerouteMapPanel extends Component<Props, State> {
{this.state.hostListExpanded ? (
<>
<span className="host-list-toggler host-list-collapse" onClick={() => this.toggleHostList()}>
<Icon name="compress"></Icon>
<i className="fa fa-compress" />
</span>
<ul className="host-list">
{Array.from(data.entries()).map(([key, points]) => {
Expand All @@ -242,7 +244,7 @@ export class TracerouteMapPanel extends Component<Props, State> {
<li className="host-item" onClick={() => this.toggleHostItem(key)}>
<span className="host-label">{host}</span>
<span className="host-arrow" style={{ color: this.state.hiddenHosts.has(key) ? 'grey' : color }}>
<Icon name="arrow-right"></Icon>
<Icon name="arrow-right" />
</span>
<span className="dest-label">{dest}</span>
</li>
Expand All @@ -252,7 +254,7 @@ export class TracerouteMapPanel extends Component<Props, State> {
</>
) : (
<span className="host-list-toggler host-list-expand" onClick={() => this.toggleHostList()}>
<Icon name="expand" />
<i className="fa fa-expand" />
</span>
)}
</Control>
Expand Down Expand Up @@ -330,7 +332,10 @@ const TraceRouteMarkers: React.FC<{
</Popup>
</Marker>
))}
<Polyline positions={points.map(point => wrapCoord_([point.lat, point.lon]) as LatLngTuple)} color={color}></Polyline>
<Polyline
positions={points.map(point => wrapCoord_([point.lat, point.lon]) as LatLngTuple)}
color={color}
></Polyline>
</div>
) : (
<></>
Expand Down
4 changes: 3 additions & 1 deletion src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ import { TracerouteMapOptions, defaults } from './types';
import { TracerouteMapPanel } from './TracerouteMapPanel';
import { TracerouteMapEditor } from './TracerouteMapEditor';

export const plugin = new PanelPlugin<TracerouteMapOptions>(TracerouteMapPanel).setDefaults(defaults).setEditor(TracerouteMapEditor);
export const plugin = new PanelPlugin<TracerouteMapOptions>(TracerouteMapPanel)
.setDefaults(defaults)
.setEditor(TracerouteMapEditor);
11 changes: 10 additions & 1 deletion src/panel.css
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
display: inline-block;
}

.map-indicator > i:first-child {
.map-indicator>i:first-child {
margin-right: 0.5em;
}

Expand All @@ -92,3 +92,12 @@
color: black;
background: rgba(240, 248, 255, 0.618);
}

.icon-right-space {
margin-right: 8px;
}

.hspace {
margin-left: '0.5em';
margin-right: '0.5em';
}

0 comments on commit 6104c24

Please sign in to comment.