mirror of
https://github.com/grafana/grafana.git
synced 2024-12-28 01:41:24 -06:00
E2C: Get Cloud Token status (#90525)
* E2C: Get Cloud Token status * remove console.log
This commit is contained in:
parent
fefd3faef4
commit
32232e44d2
@ -534,7 +534,8 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "7"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "8"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "9"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "10"]
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "10"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "11"]
|
||||
],
|
||||
"packages/grafana-runtime/src/services/pluginExtensions/getPluginExtensions.ts:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
|
@ -126,7 +126,7 @@ export interface FetchError<T = any> {
|
||||
traceId?: string;
|
||||
}
|
||||
|
||||
export function isFetchError(e: unknown): e is FetchError {
|
||||
export function isFetchError<T = any>(e: unknown): e is FetchError<T> {
|
||||
return typeof e === 'object' && e !== null && 'status' in e && 'data' in e;
|
||||
}
|
||||
|
||||
|
@ -4,11 +4,18 @@ import { BaseQueryFn, EndpointDefinition } from '@reduxjs/toolkit/dist/query';
|
||||
import { generatedAPI } from './endpoints.gen';
|
||||
|
||||
export const cloudMigrationAPI = generatedAPI.enhanceEndpoints({
|
||||
addTagTypes: ['cloud-migration-session', 'cloud-migration-snapshot'],
|
||||
addTagTypes: ['cloud-migration-token', 'cloud-migration-session', 'cloud-migration-snapshot'],
|
||||
|
||||
endpoints: {
|
||||
// Cloud-side - create token
|
||||
createCloudMigrationToken: suppressErrorsOnQuery,
|
||||
createCloudMigrationToken(endpoint) {
|
||||
suppressErrorsOnQuery(endpoint);
|
||||
endpoint.invalidatesTags = ['cloud-migration-token'];
|
||||
},
|
||||
getCloudMigrationToken(endpoint) {
|
||||
suppressErrorsOnQuery(endpoint);
|
||||
endpoint.providesTags = ['cloud-migration-token'];
|
||||
},
|
||||
|
||||
// List Cloud Configs
|
||||
getSessionList: {
|
||||
|
@ -1,20 +1,15 @@
|
||||
import { Box } from '@grafana/ui';
|
||||
import { t, Trans } from 'app/core/internationalization';
|
||||
|
||||
import { InfoItem } from '../../shared/InfoItem';
|
||||
import { MigrationTokenPane } from '../MigrationTokenPane/MigrationTokenPane';
|
||||
|
||||
export const InfoPane = () => {
|
||||
return (
|
||||
<Box alignItems="flex-start" display="flex" direction="column" gap={2}>
|
||||
<InfoItem title={t('migrate-to-cloud.migrate-to-this-stack.title', 'Let us help you migrate to this stack')}>
|
||||
<Trans i18nKey="migrate-to-cloud.migrate-to-this-stack.body">
|
||||
You can migrate some resources from your self-managed Grafana installation to this cloud stack. To do this
|
||||
securely, you'll need to generate a migration token. Your self-managed instance will use the token to
|
||||
authenticate with this cloud stack.
|
||||
</Trans>
|
||||
</InfoItem>
|
||||
<MigrationTokenPane />
|
||||
</Box>
|
||||
<InfoItem title={t('migrate-to-cloud.migrate-to-this-stack.title', 'Let us help you migrate to this stack')}>
|
||||
<Trans i18nKey="migrate-to-cloud.migrate-to-this-stack.body">
|
||||
You can migrate some resources from your self-managed Grafana installation to this cloud stack. To do this
|
||||
securely, you'll need to generate a migration token. Your self-managed instance will use the token to
|
||||
authenticate with this cloud stack.
|
||||
</Trans>
|
||||
</InfoItem>
|
||||
);
|
||||
};
|
||||
|
@ -1,22 +1,42 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { isFetchError } from '@grafana/runtime';
|
||||
import { Box, Button, Text } from '@grafana/ui';
|
||||
import { t, Trans } from 'app/core/internationalization';
|
||||
|
||||
import { useCreateCloudMigrationTokenMutation } from '../../api';
|
||||
import { useCreateCloudMigrationTokenMutation, useGetCloudMigrationTokenQuery } from '../../api';
|
||||
import { TokenErrorAlert } from '../TokenErrorAlert';
|
||||
|
||||
import { MigrationTokenModal } from './MigrationTokenModal';
|
||||
import { TokenStatus } from './TokenStatus';
|
||||
|
||||
// TODO: candidate to hoist and share
|
||||
function maybeAPIError(err: unknown) {
|
||||
if (!isFetchError<unknown>(err) || typeof err.data !== 'object' || !err.data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = err?.data;
|
||||
const message = 'message' in data && typeof data.message === 'string' ? data.message : null;
|
||||
const messageId = 'messageId' in data && typeof data.messageId === 'string' ? data.messageId : null;
|
||||
const statusCode = 'statusCode' in data && typeof data.statusCode === 'number' ? data.statusCode : null;
|
||||
|
||||
if (!message || !messageId || !statusCode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { message, messageId, statusCode };
|
||||
}
|
||||
|
||||
export const MigrationTokenPane = () => {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const isFetchingStatus = false; // TODO: No API for this yet
|
||||
|
||||
const getTokenQuery = useGetCloudMigrationTokenQuery();
|
||||
const [createTokenMutation, createTokenResponse] = useCreateCloudMigrationTokenMutation();
|
||||
const hasToken = Boolean(createTokenResponse.data?.token);
|
||||
|
||||
const isLoading = isFetchingStatus || createTokenResponse.isLoading; /* || deleteTokenResponse.isLoading */
|
||||
const getTokenQueryError = maybeAPIError(getTokenQuery.error);
|
||||
|
||||
const hasToken = Boolean(createTokenResponse.data?.token) || Boolean(getTokenQuery.data?.id);
|
||||
const isLoading = getTokenQuery.isFetching || createTokenResponse.isLoading;
|
||||
|
||||
const handleGenerateToken = useCallback(async () => {
|
||||
const resp = await createTokenMutation();
|
||||
@ -28,20 +48,22 @@ export const MigrationTokenPane = () => {
|
||||
return (
|
||||
<>
|
||||
<Box display="flex" alignItems="flex-start" direction="column" gap={2}>
|
||||
<Button disabled={isLoading || hasToken} onClick={handleGenerateToken}>
|
||||
{createTokenResponse.isLoading
|
||||
? t('migrate-to-cloud.migration-token.generate-button-loading', 'Generating a migration token...')
|
||||
: t('migrate-to-cloud.migration-token.generate-button', 'Generate a migration token')}
|
||||
</Button>
|
||||
{createTokenResponse?.isError ? (
|
||||
<TokenErrorAlert />
|
||||
) : (
|
||||
<Text color="secondary">
|
||||
<Trans i18nKey="migrate-to-cloud.migration-token.status">
|
||||
Current status: <TokenStatus hasToken={hasToken} isFetching={isLoading} />
|
||||
Current status:{' '}
|
||||
<TokenStatus hasToken={hasToken} isFetching={isLoading} errorMessageId={getTokenQueryError?.messageId} />
|
||||
</Trans>
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<Button disabled={isLoading || hasToken} onClick={handleGenerateToken}>
|
||||
{createTokenResponse.isLoading
|
||||
? t('migrate-to-cloud.migration-token.generate-button-loading', 'Generating a migration token...')
|
||||
: t('migrate-to-cloud.migration-token.generate-button', 'Generate a migration token')}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<MigrationTokenModal
|
||||
|
@ -6,18 +6,31 @@ import { Trans } from 'app/core/internationalization';
|
||||
interface Props {
|
||||
hasToken: boolean;
|
||||
isFetching: boolean;
|
||||
errorMessageId: string | undefined;
|
||||
}
|
||||
|
||||
export const TokenStatus = ({ hasToken, isFetching }: Props) => {
|
||||
export const TokenStatus = ({ hasToken, errorMessageId, isFetching }: Props) => {
|
||||
if (isFetching) {
|
||||
return <Skeleton width={100} />;
|
||||
} else if (hasToken) {
|
||||
return (
|
||||
<Text color="success">
|
||||
<Trans i18nKey="migrate-to-cloud.token-status.active">Token created and active</Trans>
|
||||
</Text>
|
||||
);
|
||||
} else if (errorMessageId === 'cloudmigrations.tokenNotFound') {
|
||||
return <Trans i18nKey="migrate-to-cloud.token-status.no-active">No active token</Trans>;
|
||||
} else if (errorMessageId) {
|
||||
return (
|
||||
<Text color="error">
|
||||
<Trans i18nKey="migrate-to-cloud.token-status.unknown-error">Error retrieving token</Trans>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return hasToken ? (
|
||||
<Text color="success">
|
||||
<Trans i18nKey="migrate-to-cloud.token-status.active">Token created and active</Trans>
|
||||
return (
|
||||
<Text color="warning">
|
||||
<Trans i18nKey="migrate-to-cloud.token-status.unknown">Unknown</Trans>
|
||||
</Text>
|
||||
) : (
|
||||
<Trans i18nKey="migrate-to-cloud.token-status.no-active">No active token</Trans>
|
||||
);
|
||||
};
|
||||
|
@ -1,13 +1,18 @@
|
||||
import { Box } from '@grafana/ui';
|
||||
import { Box, Stack } from '@grafana/ui';
|
||||
|
||||
import { InfoPane } from './EmptyState/InfoPane';
|
||||
import { MigrationStepsPane } from './EmptyState/MigrationStepsPane';
|
||||
import { MigrationTokenPane } from './MigrationTokenPane/MigrationTokenPane';
|
||||
|
||||
export const Page = () => {
|
||||
return (
|
||||
<Box backgroundColor="secondary" display="flex" alignItems="center" direction="column">
|
||||
<Box maxWidth={90} paddingY={6} paddingX={2} gap={6} direction="column" display="flex">
|
||||
<InfoPane />
|
||||
<Stack gap={2} direction="column">
|
||||
<InfoPane />
|
||||
<MigrationTokenPane />
|
||||
</Stack>
|
||||
|
||||
<MigrationStepsPane />
|
||||
</Box>
|
||||
</Box>
|
||||
|
@ -1040,7 +1040,7 @@
|
||||
"modal-field-description": "Copy the token now as you will not be able to see it again. Losing a token requires creating a new one.",
|
||||
"modal-field-label": "Token",
|
||||
"modal-title": "Migration token created",
|
||||
"status": "Current status: <1></1>"
|
||||
"status": "Current status: <2></2>"
|
||||
},
|
||||
"pdc": {
|
||||
"body": "Exposing your data sources to the internet can raise security concerns. Private data source connect (PDC) allows Grafana Cloud to access your existing data sources over a secure network tunnel.",
|
||||
@ -1088,7 +1088,9 @@
|
||||
},
|
||||
"token-status": {
|
||||
"active": "Token created and active",
|
||||
"no-active": "No active token"
|
||||
"no-active": "No active token",
|
||||
"unknown": "Unknown",
|
||||
"unknown-error": "Error retrieving token"
|
||||
},
|
||||
"what-is-cloud": {
|
||||
"body": "Grafana cloud is a fully managed cloud-hosted observability platform ideal for cloud native environments. It's everything you love about Grafana without the overhead of maintaining, upgrading, and supporting an installation.",
|
||||
|
@ -1040,7 +1040,7 @@
|
||||
"modal-field-description": "Cőpy ŧĥę ŧőĸęʼn ʼnőŵ äş yőū ŵįľľ ʼnőŧ þę äþľę ŧő şęę įŧ äģäįʼn. Ŀőşįʼnģ ä ŧőĸęʼn řęqūįřęş čřęäŧįʼnģ ä ʼnęŵ őʼnę.",
|
||||
"modal-field-label": "Ŧőĸęʼn",
|
||||
"modal-title": "Mįģřäŧįőʼn ŧőĸęʼn čřęäŧęđ",
|
||||
"status": "Cūřřęʼnŧ şŧäŧūş: <1></1>"
|
||||
"status": "Cūřřęʼnŧ şŧäŧūş: <2></2>"
|
||||
},
|
||||
"pdc": {
|
||||
"body": "Ēχpőşįʼnģ yőūř đäŧä şőūřčęş ŧő ŧĥę įʼnŧęřʼnęŧ čäʼn řäįşę şęčūřįŧy čőʼnčęřʼnş. Přįväŧę đäŧä şőūřčę čőʼnʼnęčŧ (PĐC) äľľőŵş Ğřäƒäʼnä Cľőūđ ŧő äččęşş yőūř ęχįşŧįʼnģ đäŧä şőūřčęş ővęř ä şęčūřę ʼnęŧŵőřĸ ŧūʼnʼnęľ.",
|
||||
@ -1088,7 +1088,9 @@
|
||||
},
|
||||
"token-status": {
|
||||
"active": "Ŧőĸęʼn čřęäŧęđ äʼnđ äčŧįvę",
|
||||
"no-active": "Ńő äčŧįvę ŧőĸęʼn"
|
||||
"no-active": "Ńő äčŧįvę ŧőĸęʼn",
|
||||
"unknown": "Ůʼnĸʼnőŵʼn",
|
||||
"unknown-error": "Ēřřőř řęŧřįęvįʼnģ ŧőĸęʼn"
|
||||
},
|
||||
"what-is-cloud": {
|
||||
"body": "Ğřäƒäʼnä čľőūđ įş ä ƒūľľy mäʼnäģęđ čľőūđ-ĥőşŧęđ őþşęřväþįľįŧy pľäŧƒőřm įđęäľ ƒőř čľőūđ ʼnäŧįvę ęʼnvįřőʼnmęʼnŧş. Ĩŧ'ş ęvęřyŧĥįʼnģ yőū ľővę äþőūŧ Ğřäƒäʼnä ŵįŧĥőūŧ ŧĥę ővęřĥęäđ őƒ mäįʼnŧäįʼnįʼnģ, ūpģřäđįʼnģ, äʼnđ şūppőřŧįʼnģ äʼn įʼnşŧäľľäŧįőʼn.",
|
||||
|
Loading…
Reference in New Issue
Block a user