|
|
|
import PluginState, { PluginConnectedStatusResponse } from '../../plugin_state';
|
|
|
|
|
|
|
|
import ConfigurationForm from './parts/ConfigurationForm';
|
|
|
|
import RemoveCurrentConfigurationButton from './parts/RemoveCurrentConfigurationButton';
|
|
|
|
import StatusMessageBlock from './parts/StatusMessageBlock';
|
|
|
|
import { DataExporterPluginConfigPageProps } from '../../types';
|
|
|
|
|
|
|
|
import { Legend, LoadingPlaceholder } from '@grafana/ui';
|
|
|
|
import { useLocation } from 'react-router-dom';
|
|
|
|
|
|
|
|
import React, { FC, useCallback, useEffect, useState } from 'react';
|
|
|
|
|
|
|
|
const PLUGIN_CONFIGURED_QUERY_PARAM = 'pluginConfigured';
|
|
|
|
const PLUGIN_CONFIGURED_QUERY_PARAM_TRUTHY_VALUE = 'true';
|
|
|
|
|
|
|
|
const PLUGIN_CONFIGURED_VERSION_QUERY_PARAM = 'pluginConfiguredVersion';
|
|
|
|
|
|
|
|
const DEFAULT_API_URL = 'http://localhost:8000';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* When everything is successfully configured, reload the page, and pass along a few query parameters
|
|
|
|
* so that we avoid an infinite configuration-check/data-sync loop
|
|
|
|
*
|
|
|
|
* Don't refresh the page if the plugin is already enabled..
|
|
|
|
*/
|
|
|
|
export const reloadPageWithPluginConfiguredQueryParams = (
|
|
|
|
{ version }: PluginConnectedStatusResponse,
|
|
|
|
pluginEnabled: boolean
|
|
|
|
): void => {
|
|
|
|
if (!pluginEnabled) {
|
|
|
|
window.location.href = `${window.location.href}?${PLUGIN_CONFIGURED_QUERY_PARAM}=${PLUGIN_CONFIGURED_QUERY_PARAM_TRUTHY_VALUE}&${PLUGIN_CONFIGURED_VERSION_QUERY_PARAM}=${version}`;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* remove the query params used to track state for a page reload after successful configuration, without triggering
|
|
|
|
* a page reload
|
|
|
|
* https://stackoverflow.com/a/19279428
|
|
|
|
*/
|
|
|
|
export const removePluginConfiguredQueryParams = (pluginIsEnabled?: boolean): void => {
|
|
|
|
if (window.history.pushState && pluginIsEnabled) {
|
|
|
|
const newurl = `${window.location.protocol}//${window.location.host}${window.location.pathname}`;
|
|
|
|
window.history.pushState({ path: newurl }, '', newurl);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
export const PluginConfigPage: FC<DataExporterPluginConfigPageProps> = ({
|
|
|
|
plugin: {
|
|
|
|
meta: { jsonData, enabled: pluginIsEnabled },
|
|
|
|
},
|
|
|
|
}) => {
|
|
|
|
const { search } = useLocation();
|
|
|
|
const queryParams = new URLSearchParams(search);
|
|
|
|
const pluginConfiguredQueryParam = queryParams.get(PLUGIN_CONFIGURED_QUERY_PARAM);
|
|
|
|
const pluginConfiguredVersionQueryParam = queryParams.get(PLUGIN_CONFIGURED_VERSION_QUERY_PARAM);
|
|
|
|
|
|
|
|
const pluginConfiguredRedirect = pluginConfiguredQueryParam === PLUGIN_CONFIGURED_QUERY_PARAM_TRUTHY_VALUE;
|
|
|
|
|
|
|
|
const [checkingIfPluginIsConnected, setCheckingIfPluginIsConnected] = useState<boolean>(!pluginConfiguredRedirect);
|
|
|
|
const [pluginConnectionCheckError, setPluginConnectionCheckError] = useState<string | null>(null);
|
|
|
|
|
|
|
|
const [pluginIsConnected, setPluginIsConnected] = useState<PluginConnectedStatusResponse | null>(
|
|
|
|
pluginConfiguredRedirect ? { version: pluginConfiguredVersionQueryParam as string } : null
|
|
|
|
);
|
|
|
|
|
|
|
|
const [resettingPlugin, setResettingPlugin] = useState<boolean>(false);
|
|
|
|
const [pluginResetError, setPluginResetError] = useState<string | null>(null);
|
|
|
|
|
|
|
|
const pluginMetaDataExporterApiUrl = jsonData?.dataExporterApiUrl;
|
|
|
|
|
|
|
|
const resetQueryParams = useCallback(() => removePluginConfiguredQueryParams(pluginIsEnabled), [pluginIsEnabled]);
|
|
|
|
|
|
|
|
const checkConnection = useCallback(
|
|
|
|
async (grafanaDataExporterUrl?: string) => {
|
|
|
|
setCheckingIfPluginIsConnected(true);
|
|
|
|
setPluginConnectionCheckError(null);
|
|
|
|
|
|
|
|
const backendUrl = grafanaDataExporterUrl || pluginMetaDataExporterApiUrl;
|
|
|
|
|
|
|
|
if (!backendUrl) {
|
|
|
|
setCheckingIfPluginIsConnected(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const pluginConnectionResponse = await PluginState.checkIfPluginIsConnected(backendUrl);
|
|
|
|
|
|
|
|
if (typeof pluginConnectionResponse === 'string') {
|
|
|
|
setPluginConnectionCheckError(pluginConnectionResponse);
|
|
|
|
} else {
|
|
|
|
setPluginIsConnected(pluginConnectionResponse);
|
|
|
|
reloadPageWithPluginConfiguredQueryParams(pluginConnectionResponse, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
setCheckingIfPluginIsConnected(false);
|
|
|
|
},
|
|
|
|
[pluginMetaDataExporterApiUrl]
|
|
|
|
);
|
|
|
|
|
|
|
|
useEffect(resetQueryParams, [resetQueryParams]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
/**
|
|
|
|
* don't check the plugin status if the user was just redirected after a successful
|
|
|
|
* plugin setup
|
|
|
|
*/
|
|
|
|
if (!pluginConfiguredRedirect) {
|
|
|
|
checkConnection();
|
|
|
|
}
|
|
|
|
}, [pluginMetaDataExporterApiUrl, pluginConfiguredRedirect, checkConnection]);
|
|
|
|
|
|
|
|
const resetState = useCallback(() => {
|
|
|
|
setPluginResetError(null);
|
|
|
|
setPluginConnectionCheckError(null);
|
|
|
|
setPluginIsConnected(null);
|
|
|
|
resetQueryParams();
|
|
|
|
}, [resetQueryParams]);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* NOTE: there is a possible edge case when resetting the plugin, that would lead to an error message being shown
|
|
|
|
* (which could be fixed by just reloading the page)
|
|
|
|
* This would happen if the user removes the plugin configuration, leaves the page, then comes back to the plugin
|
|
|
|
* configuration.
|
|
|
|
*
|
|
|
|
* This is because the props being passed into this component wouldn't reflect the actual plugin
|
|
|
|
* provisioning state. The props would still have DataExporterApiUrl set in the plugin jsonData, so when we make the API
|
|
|
|
* call to check the plugin state w/ DataExporter API the plugin-proxy would return a 502 Bad Gateway because the actual
|
|
|
|
* provisioned plugin doesn't know about the DataExporterApiUrl.
|
|
|
|
*
|
|
|
|
* This could be fixed by instead of passing in the plugin provisioning information as props always fetching it
|
|
|
|
* when this component renders (via a useEffect). We probably don't need to worry about this because it should happen
|
|
|
|
* very rarely, if ever
|
|
|
|
*/
|
|
|
|
const triggerPluginReset = useCallback(async () => {
|
|
|
|
setResettingPlugin(true);
|
|
|
|
resetState();
|
|
|
|
|
|
|
|
try {
|
|
|
|
await PluginState.resetPlugin();
|
|
|
|
} catch (e) {
|
|
|
|
// this should rarely, if ever happen, but we should handle the case nevertheless
|
|
|
|
setPluginResetError('There was an error resetting your plugin, try again.');
|
|
|
|
}
|
|
|
|
|
|
|
|
setResettingPlugin(false);
|
|
|
|
}, [resetState]);
|
|
|
|
|
|
|
|
const RemoveConfigButton = useCallback(
|
|
|
|
() => <RemoveCurrentConfigurationButton disabled={resettingPlugin} onClick={triggerPluginReset} />,
|
|
|
|
[resettingPlugin, triggerPluginReset]
|
|
|
|
);
|
|
|
|
|
|
|
|
let content: React.ReactNode;
|
|
|
|
|
|
|
|
if (checkingIfPluginIsConnected) {
|
|
|
|
content = <LoadingPlaceholder text="Validating your plugin connection..." />;
|
|
|
|
} else if (pluginConnectionCheckError || pluginResetError) {
|
|
|
|
content = (
|
|
|
|
<>
|
|
|
|
<StatusMessageBlock text={(pluginConnectionCheckError || pluginResetError) as string} />
|
|
|
|
<RemoveConfigButton />
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
} else if (!pluginIsConnected) {
|
|
|
|
content = <ConfigurationForm onSuccessfulSetup={checkConnection} defaultDataExporterApiUrl={DEFAULT_API_URL} />;
|
|
|
|
} else {
|
|
|
|
// plugin is fully connected and synced
|
|
|
|
content = <RemoveConfigButton />;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<Legend>Configure DataExporter</Legend>
|
|
|
|
{pluginIsConnected ? (
|
|
|
|
<>
|
|
|
|
<p>Plugin is connected! You can now go to a dashboard and add the DataExporter panel there.</p>
|
|
|
|
<StatusMessageBlock text={`Connected to DataExporter`} />
|
|
|
|
</>
|
|
|
|
) : (
|
|
|
|
<p>This page will help you configure the DataExporter plugin 👋</p>
|
|
|
|
)}
|
|
|
|
{content}
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|