import React, { FC, useCallback, useEffect, useState } from 'react'; import { Legend, LoadingPlaceholder } from '@grafana/ui'; import { useLocation } from 'react-router-dom'; // import logo from '../../img/logo.svg'; import PluginState from '../../plugin_state'; import ConfigurationForm from './parts/ConfigurationForm'; import RemoveCurrentConfigurationButton from './parts/RemoveCurrentConfigurationButton'; import StatusMessageBlock from './parts/StatusMessageBlock'; import { DataExporterPluginConfigPageProps } from 'types'; const PLUGIN_CONFIGURED_QUERY_PARAM = 'pluginConfigured'; const PLUGIN_CONFIGURED_QUERY_PARAM_TRUTHY_VALUE = 'true'; const DEFAULT_API_URL = 'http://localhost:8080'; /** * 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 = (pluginEnabled: boolean): void => { if (!pluginEnabled) { window.location.href = `${window.location.href}?${PLUGIN_CONFIGURED_QUERY_PARAM}=${PLUGIN_CONFIGURED_QUERY_PARAM_TRUTHY_VALUE}`; } }; /** * 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 = ({ plugin: { meta: { jsonData, enabled: pluginIsEnabled }, }, }) => { const { search } = useLocation(); const queryParams = new URLSearchParams(search); const pluginConfiguredQueryParam = queryParams.get(PLUGIN_CONFIGURED_QUERY_PARAM); const pluginConfiguredRedirect = pluginConfiguredQueryParam === PLUGIN_CONFIGURED_QUERY_PARAM_TRUTHY_VALUE; const [checkingIfPluginIsConnected, setCheckingIfPluginIsConnected] = useState(!pluginConfiguredRedirect); const [pluginConnectionCheckError, setPluginConnectionCheckError] = useState(null); const [pluginIsConnected, setPluginIsConnected] = useState(pluginConfiguredRedirect); const [resettingPlugin, setResettingPlugin] = useState(false); const [pluginResetError, setPluginResetError] = useState(null); const pluginMetaDataExporterApiUrl = jsonData?.dataExporterApiUrl; const dataExporterApiUrl = pluginMetaDataExporterApiUrl || DEFAULT_API_URL; const resetQueryParams = useCallback(() => removePluginConfiguredQueryParams(pluginIsEnabled), [pluginIsEnabled]); useEffect(resetQueryParams, [resetQueryParams]); useEffect(() => { const configurePluginAndSyncData = async () => { /** * If the plugin has never been configured, DataExporterApiUrl will be undefined in the plugin's jsonData * In that case, check to see if DataExporter_API_URL has been supplied as an env var. * Supplying the env var basically allows to skip the configuration form * (check webpack.config.js to see how this is set) */ if (!pluginMetaDataExporterApiUrl) { /** * DataExporterApiUrl is not yet saved in the grafana plugin settings, but has been supplied as an env var * lets auto-trigger a self-hosted plugin install w/ the DataExporterApiUrl passed in as an env var */ const errorMsg = await PluginState.installPlugin(dataExporterApiUrl); if (errorMsg) { setPluginConnectionCheckError(errorMsg); setCheckingIfPluginIsConnected(false); return; } } /** * If the DataExporterApiUrl is not set in the plugin settings, and not supplied via an env var * there's no reason to check if the plugin is connected, we know it can't be */ if (dataExporterApiUrl) { const pluginConnectionResponse = await PluginState.checkIfPluginIsConnected(dataExporterApiUrl); if (typeof pluginConnectionResponse === 'string') { setPluginConnectionCheckError(pluginConnectionResponse); } } setCheckingIfPluginIsConnected(false); }; /** * don't check the plugin status (or trigger a data sync) if the user was just redirected after a successful * plugin setup */ if (!pluginConfiguredRedirect) { configurePluginAndSyncData(); } }, [pluginMetaDataExporterApiUrl, dataExporterApiUrl, pluginConfiguredRedirect]); 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 onSuccessfulSetup = useCallback(async () => { reloadPageWithPluginConfiguredQueryParams(false); }, []); const RemoveConfigButton = useCallback( () => , [resettingPlugin, triggerPluginReset] ); let content: React.ReactNode; if (checkingIfPluginIsConnected) { content = ; } else if (pluginConnectionCheckError || pluginResetError) { content = ( <> ); } else if (!pluginIsConnected) { content = ( ); } else { // plugin is fully connected and synced content = ; } return ( <> Configure DataExporter {pluginIsConnected ? ( <>

Plugin is connected! Continue to DataExporter by clicking the{' '} {/* DataExporter Logo icon over there 👈 */}

) : (

This page will help you configure the DataExporter plugin 👋

)} {content} ); };