mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
E2C: Empty and Loading snapshot states (#90043)
* E2C: Empty and Loading snapshot states * fix responsive
This commit is contained in:
parent
e9ebb6eaa4
commit
1b3597d795
27
public/app/features/migrate-to-cloud/onprem/CTAInfo.tsx
Normal file
27
public/app/features/migrate-to-cloud/onprem/CTAInfo.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { Stack, Box, Text } from '@grafana/ui';
|
||||
|
||||
interface CTAInfoProps {
|
||||
title: NonNullable<ReactNode>;
|
||||
accessory?: ReactNode;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function CTAInfo(props: CTAInfoProps) {
|
||||
const { title, accessory, children } = props;
|
||||
|
||||
return (
|
||||
<Box maxWidth={44} display="flex" direction="row" gap={1} alignItems="flex-start">
|
||||
{accessory && <Box>{accessory}</Box>}
|
||||
|
||||
<Stack gap={2} direction="column" alignItems="flex-start">
|
||||
<Text element="h3" variant="h5">
|
||||
{title}
|
||||
</Text>
|
||||
|
||||
{children}
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -1,19 +1,19 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { Stack, Text } from '@grafana/ui';
|
||||
import { Box, Text } from '@grafana/ui';
|
||||
|
||||
interface MigrationInfoProps {
|
||||
title: NonNullable<ReactNode>;
|
||||
value: NonNullable<ReactNode>;
|
||||
children: NonNullable<ReactNode>;
|
||||
}
|
||||
|
||||
export function MigrationInfo({ title, value }: MigrationInfoProps) {
|
||||
export function MigrationInfo({ title, children }: MigrationInfoProps) {
|
||||
return (
|
||||
<Stack direction="column">
|
||||
<Box minWidth={{ xs: 0, xxl: 16 }} display="flex" direction="column">
|
||||
<Text variant="bodySmall" color="secondary">
|
||||
{title}
|
||||
</Text>
|
||||
<Text variant="h4">{value}</Text>
|
||||
</Stack>
|
||||
<Text variant="h4">{children}</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
110
public/app/features/migrate-to-cloud/onprem/MigrationSummary.tsx
Normal file
110
public/app/features/migrate-to-cloud/onprem/MigrationSummary.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
import { Box, Button, Space, Stack, Text } from '@grafana/ui';
|
||||
import { Trans, t } from 'app/core/internationalization';
|
||||
|
||||
import { GetSessionApiResponse, GetSnapshotResponseDto } from '../api';
|
||||
|
||||
import { MigrationInfo } from './MigrationInfo';
|
||||
|
||||
interface MigrationSummaryProps {
|
||||
snapshot: GetSnapshotResponseDto | undefined;
|
||||
session: GetSessionApiResponse;
|
||||
isBusy: boolean;
|
||||
|
||||
disconnectIsLoading: boolean;
|
||||
onDisconnect: () => void;
|
||||
|
||||
showBuildSnapshot: boolean;
|
||||
buildSnapshotIsLoading: boolean;
|
||||
onBuildSnapshot: () => void;
|
||||
|
||||
showUploadSnapshot: boolean;
|
||||
uploadSnapshotIsLoading: boolean;
|
||||
onUploadSnapshot: () => void;
|
||||
}
|
||||
|
||||
export function MigrationSummary(props: MigrationSummaryProps) {
|
||||
const {
|
||||
session,
|
||||
snapshot,
|
||||
isBusy,
|
||||
disconnectIsLoading,
|
||||
onDisconnect,
|
||||
showBuildSnapshot,
|
||||
buildSnapshotIsLoading,
|
||||
onBuildSnapshot,
|
||||
|
||||
showUploadSnapshot,
|
||||
uploadSnapshotIsLoading,
|
||||
onUploadSnapshot,
|
||||
} = props;
|
||||
|
||||
const totalCount = 0;
|
||||
const errorCount = 0;
|
||||
const successCount = 0;
|
||||
|
||||
return (
|
||||
<Box
|
||||
borderColor="weak"
|
||||
borderStyle="solid"
|
||||
padding={2}
|
||||
display="flex"
|
||||
gap={4}
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Stack gap={4} wrap="wrap">
|
||||
<MigrationInfo title={t('migrate-to-cloud.summary.snapshot-date', 'Snapshot timestamp')}>
|
||||
{snapshot?.created ? (
|
||||
snapshot?.created
|
||||
) : (
|
||||
<Text color="secondary">
|
||||
<Trans i18nKey="migrate-to-cloud.summary.snapshot-not-created">Not yet created</Trans>
|
||||
</Text>
|
||||
)}
|
||||
</MigrationInfo>
|
||||
|
||||
<MigrationInfo title={t('migrate-to-cloud.summary.total-resource-count', 'Total resources')}>
|
||||
{totalCount}
|
||||
</MigrationInfo>
|
||||
|
||||
<MigrationInfo title={t('migrate-to-cloud.summary.errored-resource-count', 'Errors')}>
|
||||
{errorCount}
|
||||
</MigrationInfo>
|
||||
|
||||
<MigrationInfo title={t('migrate-to-cloud.summary.successful-resource-count', 'Successfully migrated')}>
|
||||
{successCount}
|
||||
</MigrationInfo>
|
||||
|
||||
<MigrationInfo title={t('migrate-to-cloud.summary.target-stack-title', 'Uploading to')}>
|
||||
{session.slug}
|
||||
<Space h={1} layout="inline" />
|
||||
<Button
|
||||
disabled={isBusy}
|
||||
onClick={onDisconnect}
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
icon={disconnectIsLoading ? 'spinner' : undefined}
|
||||
>
|
||||
<Trans i18nKey="migrate-to-cloud.summary.disconnect">Disconnect</Trans>
|
||||
</Button>
|
||||
</MigrationInfo>
|
||||
</Stack>
|
||||
|
||||
{showBuildSnapshot && (
|
||||
<Button disabled={isBusy} onClick={onBuildSnapshot} icon={buildSnapshotIsLoading ? 'spinner' : undefined}>
|
||||
<Trans i18nKey="migrate-to-cloud.summary.start-migration">Build snapshot</Trans>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{showUploadSnapshot && (
|
||||
<Button
|
||||
disabled={isBusy || uploadSnapshotIsLoading}
|
||||
onClick={onUploadSnapshot}
|
||||
icon={uploadSnapshotIsLoading ? 'spinner' : undefined}
|
||||
>
|
||||
<Trans i18nKey="migrate-to-cloud.summary.upload-migration">Upload snapshot</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
import { skipToken } from '@reduxjs/toolkit/query/react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { Alert, Box, Button, Stack } from '@grafana/ui';
|
||||
import { Alert, Box, Stack } from '@grafana/ui';
|
||||
import { Trans, t } from 'app/core/internationalization';
|
||||
|
||||
import {
|
||||
SnapshotDto,
|
||||
useCancelSnapshotMutation,
|
||||
useCreateSnapshotMutation,
|
||||
useDeleteSessionMutation,
|
||||
useGetSessionListQuery,
|
||||
@ -16,22 +17,25 @@ import {
|
||||
|
||||
import { DisconnectModal } from './DisconnectModal';
|
||||
import { EmptyState } from './EmptyState/EmptyState';
|
||||
import { MigrationInfo } from './MigrationInfo';
|
||||
import { MigrationSummary } from './MigrationSummary';
|
||||
import { ResourcesTable } from './ResourcesTable';
|
||||
import { BuildSnapshotCTA, CreatingSnapshotCTA } from './SnapshotCTAs';
|
||||
|
||||
/**
|
||||
* Here's how migrations work:
|
||||
*
|
||||
* A single on-prem instance can be configured to be migrated to multiple cloud instances.
|
||||
* - GetMigrationList returns this the list of migration targets for the on prem instance
|
||||
* - If GetMigrationList returns an empty list, then the empty state with a prompt to enter a token should be shown
|
||||
* A single on-prem instance can be configured to be migrated to multiple cloud instances. We call these 'sessions'.
|
||||
* - GetSessionList returns this the list of migration targets for the on prem instance
|
||||
* - If GetMigrationList returns an empty list, then an empty state to prompt for token should be shown
|
||||
* - The UI (at the moment) only shows the most recently created migration target (the last one returned from the API)
|
||||
* and doesn't allow for others to be created
|
||||
*
|
||||
* A single on-prem migration 'target' (CloudMigrationResponse) can have multiple migration runs (CloudMigrationRun)
|
||||
* - To list the migration resources:
|
||||
* 1. call GetCloudMigratiopnRunList to list all runs
|
||||
* 2. call GetCloudMigrationRun with the ID from first step to list the result of that migration
|
||||
* A single on-prem migration 'target' (CloudMigrationSession) can have multiple snapshots.
|
||||
* A snapshot represents a copy of all migratable resources at a fixed point in time.
|
||||
* A snapshots are created asynchronously in the background, so GetSnapshot must be polled to get the current status.
|
||||
*
|
||||
* After a snapshot has been created, it will be PENDING_UPLOAD. UploadSnapshot is then called which asynchronously
|
||||
* uploads and migrates the snapshot to the cloud instance.
|
||||
*/
|
||||
|
||||
function useGetLatestSession() {
|
||||
@ -52,6 +56,10 @@ const SHOULD_POLL_STATUSES: Array<SnapshotDto['status']> = [
|
||||
'PROCESSING',
|
||||
];
|
||||
|
||||
const SNAPSHOT_BUILDING_STATUSES: Array<SnapshotDto['status']> = ['INITIALIZING', 'CREATING'];
|
||||
|
||||
const SNAPSHOT_UPLOADING_STATUSES: Array<SnapshotDto['status']> = ['UPLOADING', 'PENDING_PROCESSING', 'PROCESSING'];
|
||||
|
||||
const STATUS_POLL_INTERVAL = 5 * 1000;
|
||||
|
||||
function useGetLatestSnapshot(sessionUid?: string) {
|
||||
@ -91,23 +99,27 @@ export const Page = () => {
|
||||
const snapshot = useGetLatestSnapshot(session.data?.uid);
|
||||
const [performCreateSnapshot, createSnapshotResult] = useCreateSnapshotMutation();
|
||||
const [performUploadSnapshot, uploadSnapshotResult] = useUploadSnapshotMutation();
|
||||
const [performCancelSnapshot, cancelSnapshotResult] = useCancelSnapshotMutation();
|
||||
const [performDisconnect, disconnectResult] = useDeleteSessionMutation();
|
||||
|
||||
const sessionUid = session.data?.uid;
|
||||
const snapshotUid = snapshot.data?.uid;
|
||||
const migrationMeta = session.data;
|
||||
const isInitialLoading = session.isLoading;
|
||||
const status = snapshot.data?.status;
|
||||
|
||||
// isBusy is not a loading state, but indicates that the system is doing *something*
|
||||
// and all buttons should be disabled
|
||||
const isBusy =
|
||||
createSnapshotResult.isLoading ||
|
||||
uploadSnapshotResult.isLoading ||
|
||||
cancelSnapshotResult.isLoading ||
|
||||
session.isLoading ||
|
||||
snapshot.isLoading ||
|
||||
disconnectResult.isLoading;
|
||||
|
||||
const resources = snapshot.data?.results;
|
||||
const showBuildSnapshot = !snapshot.isLoading && !snapshot.data;
|
||||
const showBuildingSnapshot = SNAPSHOT_BUILDING_STATUSES.includes(status);
|
||||
const showUploadSnapshot = status === 'PENDING_UPLOAD' || SNAPSHOT_UPLOADING_STATUSES.includes(status);
|
||||
|
||||
const handleDisconnect = useCallback(async () => {
|
||||
if (sessionUid) {
|
||||
@ -127,16 +139,23 @@ export const Page = () => {
|
||||
}
|
||||
}, [performUploadSnapshot, sessionUid, snapshotUid]);
|
||||
|
||||
const handleCancelSnapshot = useCallback(() => {
|
||||
if (sessionUid && snapshotUid) {
|
||||
performCancelSnapshot({ uid: sessionUid, snapshotUid: snapshotUid });
|
||||
}
|
||||
}, [performCancelSnapshot, sessionUid, snapshotUid]);
|
||||
|
||||
if (isInitialLoading) {
|
||||
// TODO: better loading state
|
||||
return <div>Loading...</div>;
|
||||
} else if (!migrationMeta) {
|
||||
} else if (!session.data) {
|
||||
return <EmptyState />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack direction="column" gap={4}>
|
||||
{/* TODO: show errors from all mutation's in a... modal? */}
|
||||
{createSnapshotResult.isError && (
|
||||
<Alert
|
||||
severity="error"
|
||||
@ -162,55 +181,45 @@ export const Page = () => {
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{migrationMeta.slug && (
|
||||
<Box
|
||||
borderColor="weak"
|
||||
borderStyle="solid"
|
||||
padding={2}
|
||||
display="flex"
|
||||
gap={4}
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<MigrationInfo
|
||||
title={t('migrate-to-cloud.summary.target-stack-title', 'Uploading to')}
|
||||
value={
|
||||
<>
|
||||
{migrationMeta.slug}{' '}
|
||||
<Button
|
||||
disabled={isBusy}
|
||||
onClick={() => setDisconnectModalOpen(true)}
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
icon={disconnectResult.isLoading ? 'spinner' : undefined}
|
||||
>
|
||||
<Trans i18nKey="migrate-to-cloud.summary.disconnect">Disconnect</Trans>
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
{session.data && (
|
||||
<MigrationSummary
|
||||
session={session.data}
|
||||
snapshot={snapshot.data}
|
||||
isBusy={isBusy}
|
||||
disconnectIsLoading={disconnectResult.isLoading}
|
||||
onDisconnect={handleDisconnect}
|
||||
showBuildSnapshot={showBuildSnapshot}
|
||||
buildSnapshotIsLoading={createSnapshotResult.isLoading}
|
||||
onBuildSnapshot={handleCreateSnapshot}
|
||||
showUploadSnapshot={showUploadSnapshot}
|
||||
uploadSnapshotIsLoading={uploadSnapshotResult.isLoading || SNAPSHOT_UPLOADING_STATUSES.includes(status)}
|
||||
onUploadSnapshot={handleUploadSnapshot}
|
||||
/>
|
||||
)}
|
||||
|
||||
<MigrationInfo title="Status" value={snapshot?.data?.status ?? 'no snapshot yet'} />
|
||||
{(showBuildSnapshot || showBuildingSnapshot) && (
|
||||
<Box display="flex" justifyContent="center" paddingY={10}>
|
||||
{showBuildSnapshot && (
|
||||
<BuildSnapshotCTA
|
||||
disabled={isBusy}
|
||||
isLoading={createSnapshotResult.isLoading}
|
||||
onClick={handleCreateSnapshot}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Button
|
||||
disabled={isBusy || Boolean(snapshot.data)}
|
||||
onClick={handleCreateSnapshot}
|
||||
icon={createSnapshotResult.isLoading ? 'spinner' : undefined}
|
||||
>
|
||||
<Trans i18nKey="migrate-to-cloud.summary.start-migration">Build snapshot</Trans>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
disabled={isBusy || !(snapshot.data?.status === 'PENDING_UPLOAD')}
|
||||
onClick={handleUploadSnapshot}
|
||||
icon={createSnapshotResult.isLoading ? 'spinner' : undefined}
|
||||
>
|
||||
<Trans i18nKey="migrate-to-cloud.summary.upload-migration">Upload & migrate snapshot</Trans>
|
||||
</Button>
|
||||
{showBuildingSnapshot && (
|
||||
<CreatingSnapshotCTA
|
||||
disabled={isBusy}
|
||||
isLoading={cancelSnapshotResult.isLoading}
|
||||
onClick={handleCancelSnapshot}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{resources && <ResourcesTable resources={resources} />}
|
||||
{snapshot.data?.results && snapshot.data.results.length > 0 && (
|
||||
<ResourcesTable resources={snapshot.data.results} />
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
<DisconnectModal
|
||||
|
67
public/app/features/migrate-to-cloud/onprem/SnapshotCTAs.tsx
Normal file
67
public/app/features/migrate-to-cloud/onprem/SnapshotCTAs.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
import { Button, Icon, Spinner, Text } from '@grafana/ui';
|
||||
import { Trans, t } from 'app/core/internationalization';
|
||||
|
||||
import { CTAInfo } from './CTAInfo';
|
||||
|
||||
interface SnapshotCTAProps {
|
||||
disabled: boolean;
|
||||
isLoading: boolean;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export function BuildSnapshotCTA(props: SnapshotCTAProps) {
|
||||
const { disabled, isLoading, onClick } = props;
|
||||
|
||||
return (
|
||||
<CTAInfo
|
||||
title={t('migrate-to-cloud.build-snapshot.title', 'No snapshot exists')}
|
||||
accessory={<Icon name="cog" size="lg" />}
|
||||
>
|
||||
<Text element="p" variant="body" color="secondary">
|
||||
<Trans i18nKey="migrate-to-cloud.build-snapshot.description">
|
||||
This tool can migrate some resources from this installation to your cloud stack. To get started, you'll
|
||||
need to create a snapshot of this installation. Creating a snapshot typically takes less than two minutes. The
|
||||
snapshot is stored alongside this Grafana installation.
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<Text element="p" variant="body" color="secondary">
|
||||
<Trans i18nKey="migrate-to-cloud.build-snapshot.when-complete">
|
||||
Once the snapshot is complete, you will be able to upload it to your cloud stack.
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<Button disabled={disabled} onClick={onClick} icon={isLoading ? 'spinner' : undefined}>
|
||||
<Trans i18nKey="migrate-to-cloud.summary.start-migration">Build snapshot</Trans>
|
||||
</Button>
|
||||
</CTAInfo>
|
||||
);
|
||||
}
|
||||
|
||||
export function CreatingSnapshotCTA(props: SnapshotCTAProps) {
|
||||
const { disabled, isLoading, onClick } = props;
|
||||
|
||||
return (
|
||||
<CTAInfo
|
||||
title={t('migrate-to-cloud.building-snapshot.title', 'Building installation snapshot')}
|
||||
accessory={<Spinner inline />}
|
||||
>
|
||||
<Text element="p" variant="body" color="secondary">
|
||||
<Trans i18nKey="migrate-to-cloud.building-snapshot.description">
|
||||
We're creating a point-in-time snapshot of the current state of this installation. Once the snapshot is
|
||||
complete. you'll be able to upload it to Grafana Cloud.
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<Text element="p" variant="body" color="secondary">
|
||||
<Trans i18nKey="migrate-to-cloud.building-snapshot.description-eta">
|
||||
Creating a snapshot typically takes less than two minutes.
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<Button disabled={disabled} onClick={onClick} icon={isLoading ? 'spinner' : undefined} variant="secondary">
|
||||
<Trans i18nKey="migrate-to-cloud.summary.cancel-snapshot">Cancel snapshot</Trans>
|
||||
</Button>
|
||||
</CTAInfo>
|
||||
);
|
||||
}
|
@ -901,6 +901,16 @@
|
||||
}
|
||||
},
|
||||
"migrate-to-cloud": {
|
||||
"build-snapshot": {
|
||||
"description": "This tool can migrate some resources from this installation to your cloud stack. To get started, you'll need to create a snapshot of this installation. Creating a snapshot typically takes less than two minutes. The snapshot is stored alongside this Grafana installation.",
|
||||
"title": "No snapshot exists",
|
||||
"when-complete": "Once the snapshot is complete, you will be able to upload it to your cloud stack."
|
||||
},
|
||||
"building-snapshot": {
|
||||
"description": "We're creating a point-in-time snapshot of the current state of this installation. Once the snapshot is complete. you'll be able to upload it to Grafana Cloud.",
|
||||
"description-eta": "Creating a snapshot typically takes less than two minutes.",
|
||||
"title": "Building installation snapshot"
|
||||
},
|
||||
"can-i-move": {
|
||||
"body": "Once you connect this installation to a cloud stack, you'll be able to upload data sources and dashboards.",
|
||||
"link-title": "Learn about migrating other settings",
|
||||
@ -1003,14 +1013,20 @@
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"summary": {
|
||||
"cancel-snapshot": "Cancel snapshot",
|
||||
"disconnect": "Disconnect",
|
||||
"disconnect-error-description": "See the Grafana server logs for more details",
|
||||
"disconnect-error-title": "There was an error disconnecting",
|
||||
"errored-resource-count": "Errors",
|
||||
"run-migration-error-description": "See the Grafana server logs for more details",
|
||||
"run-migration-error-title": "There was an error migrating your resources",
|
||||
"snapshot-date": "Snapshot timestamp",
|
||||
"snapshot-not-created": "Not yet created",
|
||||
"start-migration": "Build snapshot",
|
||||
"successful-resource-count": "Successfully migrated",
|
||||
"target-stack-title": "Uploading to",
|
||||
"upload-migration": "Upload & migrate snapshot"
|
||||
"total-resource-count": "Total resources",
|
||||
"upload-migration": "Upload snapshot"
|
||||
},
|
||||
"token-status": {
|
||||
"active": "Token created and active",
|
||||
|
@ -901,6 +901,16 @@
|
||||
}
|
||||
},
|
||||
"migrate-to-cloud": {
|
||||
"build-snapshot": {
|
||||
"description": "Ŧĥįş ŧőőľ čäʼn mįģřäŧę şőmę řęşőūřčęş ƒřőm ŧĥįş įʼnşŧäľľäŧįőʼn ŧő yőūř čľőūđ şŧäčĸ. Ŧő ģęŧ şŧäřŧęđ, yőū'ľľ ʼnęęđ ŧő čřęäŧę ä şʼnäpşĥőŧ őƒ ŧĥįş įʼnşŧäľľäŧįőʼn. Cřęäŧįʼnģ ä şʼnäpşĥőŧ ŧypįčäľľy ŧäĸęş ľęşş ŧĥäʼn ŧŵő mįʼnūŧęş. Ŧĥę şʼnäpşĥőŧ įş şŧőřęđ äľőʼnģşįđę ŧĥįş Ğřäƒäʼnä įʼnşŧäľľäŧįőʼn.",
|
||||
"title": "Ńő şʼnäpşĥőŧ ęχįşŧş",
|
||||
"when-complete": "Øʼnčę ŧĥę şʼnäpşĥőŧ įş čőmpľęŧę, yőū ŵįľľ þę äþľę ŧő ūpľőäđ įŧ ŧő yőūř čľőūđ şŧäčĸ."
|
||||
},
|
||||
"building-snapshot": {
|
||||
"description": "Ŵę'řę čřęäŧįʼnģ ä pőįʼnŧ-įʼn-ŧįmę şʼnäpşĥőŧ őƒ ŧĥę čūřřęʼnŧ şŧäŧę őƒ ŧĥįş įʼnşŧäľľäŧįőʼn. Øʼnčę ŧĥę şʼnäpşĥőŧ įş čőmpľęŧę. yőū'ľľ þę äþľę ŧő ūpľőäđ įŧ ŧő Ğřäƒäʼnä Cľőūđ.",
|
||||
"description-eta": "Cřęäŧįʼnģ ä şʼnäpşĥőŧ ŧypįčäľľy ŧäĸęş ľęşş ŧĥäʼn ŧŵő mįʼnūŧęş.",
|
||||
"title": "ßūįľđįʼnģ įʼnşŧäľľäŧįőʼn şʼnäpşĥőŧ"
|
||||
},
|
||||
"can-i-move": {
|
||||
"body": "Øʼnčę yőū čőʼnʼnęčŧ ŧĥįş įʼnşŧäľľäŧįőʼn ŧő ä čľőūđ şŧäčĸ, yőū'ľľ þę äþľę ŧő ūpľőäđ đäŧä şőūřčęş äʼnđ đäşĥþőäřđş.",
|
||||
"link-title": "Ŀęäřʼn äþőūŧ mįģřäŧįʼnģ őŧĥęř şęŧŧįʼnģş",
|
||||
@ -1003,14 +1013,20 @@
|
||||
"unknown": "Ůʼnĸʼnőŵʼn"
|
||||
},
|
||||
"summary": {
|
||||
"cancel-snapshot": "Cäʼnčęľ şʼnäpşĥőŧ",
|
||||
"disconnect": "Đįşčőʼnʼnęčŧ",
|
||||
"disconnect-error-description": "Ŝęę ŧĥę Ğřäƒäʼnä şęřvęř ľőģş ƒőř mőřę đęŧäįľş",
|
||||
"disconnect-error-title": "Ŧĥęřę ŵäş äʼn ęřřőř đįşčőʼnʼnęčŧįʼnģ",
|
||||
"errored-resource-count": "Ēřřőřş",
|
||||
"run-migration-error-description": "Ŝęę ŧĥę Ğřäƒäʼnä şęřvęř ľőģş ƒőř mőřę đęŧäįľş",
|
||||
"run-migration-error-title": "Ŧĥęřę ŵäş äʼn ęřřőř mįģřäŧįʼnģ yőūř řęşőūřčęş",
|
||||
"snapshot-date": "Ŝʼnäpşĥőŧ ŧįmęşŧämp",
|
||||
"snapshot-not-created": "Ńőŧ yęŧ čřęäŧęđ",
|
||||
"start-migration": "ßūįľđ şʼnäpşĥőŧ",
|
||||
"successful-resource-count": "Ŝūččęşşƒūľľy mįģřäŧęđ",
|
||||
"target-stack-title": "Ůpľőäđįʼnģ ŧő",
|
||||
"upload-migration": "Ůpľőäđ & mįģřäŧę şʼnäpşĥőŧ"
|
||||
"total-resource-count": "Ŧőŧäľ řęşőūřčęş",
|
||||
"upload-migration": "Ůpľőäđ şʼnäpşĥőŧ"
|
||||
},
|
||||
"token-status": {
|
||||
"active": "Ŧőĸęʼn čřęäŧęđ äʼnđ äčŧįvę",
|
||||
|
Loading…
Reference in New Issue
Block a user