RepositoryOverview: Adjust layout and extract necessary components out (#112190)
* RepositoryOverview: adjust layout and extract necessary components out * clean up * clean up
This commit is contained in:
@@ -3,11 +3,12 @@ import { useMemo } from 'react';
|
|||||||
import { intervalToAbbreviatedDurationString, TraceKeyValuePair } from '@grafana/data';
|
import { intervalToAbbreviatedDurationString, TraceKeyValuePair } from '@grafana/data';
|
||||||
import { t, Trans } from '@grafana/i18n';
|
import { t, Trans } from '@grafana/i18n';
|
||||||
import { Alert, Badge, Box, Card, InteractiveTable, Spinner, Stack, Text } from '@grafana/ui';
|
import { Alert, Badge, Box, Card, InteractiveTable, Spinner, Stack, Text } from '@grafana/ui';
|
||||||
import { Job, Repository, SyncStatus } from 'app/api/clients/provisioning/v0alpha1';
|
import { Job, Repository } from 'app/api/clients/provisioning/v0alpha1';
|
||||||
import KeyValuesTable from 'app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable';
|
import KeyValuesTable from 'app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable';
|
||||||
|
|
||||||
import { ProvisioningAlert } from '../Shared/ProvisioningAlert';
|
import { ProvisioningAlert } from '../Shared/ProvisioningAlert';
|
||||||
import { useRepositoryAllJobs } from '../hooks/useRepositoryAllJobs';
|
import { useRepositoryAllJobs } from '../hooks/useRepositoryAllJobs';
|
||||||
|
import { getStatusColor } from '../utils/repositoryStatus';
|
||||||
import { formatTimestamp } from '../utils/time';
|
import { formatTimestamp } from '../utils/time';
|
||||||
|
|
||||||
import { JobSummary } from './JobSummary';
|
import { JobSummary } from './JobSummary';
|
||||||
@@ -22,24 +23,12 @@ type JobCell = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStatusColor = (state?: SyncStatus['state']) => {
|
|
||||||
switch (state) {
|
|
||||||
case 'success':
|
|
||||||
return 'green';
|
|
||||||
case 'working':
|
|
||||||
return 'blue';
|
|
||||||
case 'warning':
|
|
||||||
return 'orange';
|
|
||||||
case 'pending':
|
|
||||||
return 'darkgrey';
|
|
||||||
case 'error':
|
|
||||||
return 'red';
|
|
||||||
default:
|
|
||||||
return 'darkgrey';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getJobColumns = () => [
|
const getJobColumns = () => [
|
||||||
|
{
|
||||||
|
id: 'jobId',
|
||||||
|
header: t('provisioning.recent-jobs.column-job-id', 'Job ID'),
|
||||||
|
cell: ({ row: { original: job } }: JobCell) => <Text variant="body">{job.metadata?.name || ''}</Text>,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'status',
|
id: 'status',
|
||||||
header: t('provisioning.recent-jobs.column-status', 'Status'),
|
header: t('provisioning.recent-jobs.column-status', 'Status'),
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
import { Trans, t } from '@grafana/i18n';
|
|
||||||
import { Stack, Alert, Text } from '@grafana/ui';
|
|
||||||
import { HealthStatus } from 'app/api/clients/provisioning/v0alpha1';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
health: HealthStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function RepositoryHealth({ health }: Props) {
|
|
||||||
return (
|
|
||||||
<Stack gap={2} direction="column" alignItems="flex-start">
|
|
||||||
{health.healthy ? (
|
|
||||||
<Alert
|
|
||||||
title={t('provisioning.repository-health.title-repository-is-healthy', 'Repository is healthy')}
|
|
||||||
severity="success"
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
>
|
|
||||||
<Trans i18nKey="provisioning.repository-health.no-errors-found">No errors found</Trans>
|
|
||||||
</Alert>
|
|
||||||
) : (
|
|
||||||
<Alert
|
|
||||||
title={t('provisioning.repository-health.title-repository-is-unhealthy', 'Repository is unhealthy')}
|
|
||||||
severity="warning"
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
>
|
|
||||||
{health.message && health.message.length > 0 && (
|
|
||||||
<>
|
|
||||||
<Text>
|
|
||||||
<Trans i18nKey="provisioning.repository-health.details">Details:</Trans>
|
|
||||||
</Text>
|
|
||||||
<ul>
|
|
||||||
{health.message.map((message) => (
|
|
||||||
<li key={message}>{message}</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
|
||||||
|
import { t, Trans } from '@grafana/i18n';
|
||||||
|
import { Badge, Card, Grid, Stack, Text, useStyles2 } from '@grafana/ui';
|
||||||
|
import { Repository } from 'app/api/clients/provisioning/v0alpha1';
|
||||||
|
|
||||||
|
import { formatTimestamp } from '../utils/time';
|
||||||
|
|
||||||
|
import { CheckRepository } from './CheckRepository';
|
||||||
|
|
||||||
|
export function RepositoryHealthCard({ repo }: { repo: Repository }) {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
const status = repo.status;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card noMargin className={styles.card}>
|
||||||
|
<Card.Heading>
|
||||||
|
<Trans i18nKey="provisioning.repository-overview.health">Health</Trans>
|
||||||
|
</Card.Heading>
|
||||||
|
<Card.Description>
|
||||||
|
<Grid columns={3} gap={1} alignItems="baseline">
|
||||||
|
{/* Status */}
|
||||||
|
<Text color="secondary">
|
||||||
|
<Trans i18nKey="provisioning.repository-overview.status">Status:</Trans>
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<div className={styles.spanTwo}>
|
||||||
|
<Badge
|
||||||
|
color={status?.health?.healthy ? 'green' : 'red'}
|
||||||
|
text={
|
||||||
|
status?.health?.healthy
|
||||||
|
? t('provisioning.repository-overview.healthy', 'Healthy')
|
||||||
|
: t('provisioning.repository-overview.unhealthy', 'Unhealthy')
|
||||||
|
}
|
||||||
|
icon={status?.health?.healthy ? 'check-circle' : 'exclamation-triangle'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Checked */}
|
||||||
|
<Text color="secondary">
|
||||||
|
<Trans i18nKey="provisioning.repository-overview.checked">Checked:</Trans>
|
||||||
|
</Text>
|
||||||
|
<div className={styles.spanTwo}>
|
||||||
|
<Text variant="body">{formatTimestamp(status?.health?.checked)}</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!!status?.health?.message?.length && (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<Text color="secondary">
|
||||||
|
<Trans i18nKey="provisioning.repository-overview.messages">Messages:</Trans>
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Stack gap={1}>
|
||||||
|
{status.health.message.map((msg, idx) => (
|
||||||
|
<Text key={idx} variant="body">
|
||||||
|
{msg}
|
||||||
|
</Text>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
</Card.Description>
|
||||||
|
<Card.Actions className={styles.actions}>
|
||||||
|
<CheckRepository repository={repo} />
|
||||||
|
</Card.Actions>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStyles = () => {
|
||||||
|
return {
|
||||||
|
spanTwo: css({
|
||||||
|
gridColumn: 'span 2',
|
||||||
|
}),
|
||||||
|
card: css({
|
||||||
|
height: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}),
|
||||||
|
actions: css({
|
||||||
|
marginTop: 'auto',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,25 +1,26 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { GrafanaEdition } from '@grafana/data/internal';
|
import { GrafanaEdition } from '@grafana/data/internal';
|
||||||
import { Trans, t } from '@grafana/i18n';
|
import { Trans } from '@grafana/i18n';
|
||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
import { Box, Card, CellProps, Grid, InteractiveTable, LinkButton, Stack, Text, useStyles2 } from '@grafana/ui';
|
import { Box, Card, CellProps, Grid, InteractiveTable, LinkButton, Stack, Text, useStyles2 } from '@grafana/ui';
|
||||||
import { Repository, ResourceCount } from 'app/api/clients/provisioning/v0alpha1';
|
import { Repository, ResourceCount } from 'app/api/clients/provisioning/v0alpha1';
|
||||||
|
|
||||||
import { RecentJobs } from '../Job/RecentJobs';
|
import { RecentJobs } from '../Job/RecentJobs';
|
||||||
import { MessageList } from '../Shared/MessageList';
|
|
||||||
import { formatTimestamp } from '../utils/time';
|
import { formatTimestamp } from '../utils/time';
|
||||||
|
|
||||||
import { CheckRepository } from './CheckRepository';
|
import { RepositoryHealthCard } from './RepositoryHealthCard';
|
||||||
import { RepositoryHealth } from './RepositoryHealth';
|
import { RepositoryPullStatusCard } from './RepositoryPullStatusCard';
|
||||||
import { SyncRepository } from './SyncRepository';
|
|
||||||
|
|
||||||
type StatCell<T extends keyof ResourceCount = keyof ResourceCount> = CellProps<ResourceCount, ResourceCount[T]>;
|
type StatCell<T extends keyof ResourceCount = keyof ResourceCount> = CellProps<ResourceCount, ResourceCount[T]>;
|
||||||
|
|
||||||
function getColumnCount(hasWebhook: boolean): 3 | 4 {
|
function getColumnCount(hasWebhook: boolean): { xxlColumn: 5 | 4; lgColumn: 3 | 2 } {
|
||||||
return hasWebhook ? 4 : 3;
|
return {
|
||||||
|
xxlColumn: hasWebhook ? 5 : 4,
|
||||||
|
lgColumn: hasWebhook ? 3 : 2,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RepositoryOverview({ repo }: { repo: Repository }) {
|
export function RepositoryOverview({ repo }: { repo: Repository }) {
|
||||||
@@ -27,7 +28,7 @@ export function RepositoryOverview({ repo }: { repo: Repository }) {
|
|||||||
|
|
||||||
const status = repo.status;
|
const status = repo.status;
|
||||||
const webhookURL = getWebhookURL(repo);
|
const webhookURL = getWebhookURL(repo);
|
||||||
const columns = getColumnCount(Boolean(repo.status?.webhook));
|
const { lgColumn, xxlColumn } = getColumnCount(Boolean(repo.status?.webhook));
|
||||||
|
|
||||||
const resourceColumns = useMemo(
|
const resourceColumns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
@@ -53,7 +54,7 @@ export function RepositoryOverview({ repo }: { repo: Repository }) {
|
|||||||
return (
|
return (
|
||||||
<Box padding={2}>
|
<Box padding={2}>
|
||||||
<Stack direction="column" gap={2}>
|
<Stack direction="column" gap={2}>
|
||||||
<Grid columns={{ xs: 1, sm: 2, lg: columns }} gap={2}>
|
<Grid columns={{ xs: 1, sm: 2, lg: lgColumn, xxl: xxlColumn }} gap={2} alignItems={'flex-start'}>
|
||||||
<div className={styles.cardContainer}>
|
<div className={styles.cardContainer}>
|
||||||
<Card noMargin className={styles.card}>
|
<Card noMargin className={styles.card}>
|
||||||
<Card.Heading>
|
<Card.Heading>
|
||||||
@@ -69,144 +70,20 @@ export function RepositoryOverview({ repo }: { repo: Repository }) {
|
|||||||
) : null}
|
) : null}
|
||||||
</Card.Description>
|
</Card.Description>
|
||||||
<Card.Actions className={styles.actions}>
|
<Card.Actions className={styles.actions}>
|
||||||
<LinkButton fill="outline" size="md" href={getFolderURL(repo)} icon="folder-open">
|
<LinkButton size="md" href={getFolderURL(repo)} icon="folder-open" variant="secondary">
|
||||||
<Trans i18nKey="provisioning.repository-overview.view-folder">View Folder</Trans>
|
<Trans i18nKey="provisioning.repository-overview.view-folder">View Folder</Trans>
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
</Card.Actions>
|
</Card.Actions>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{repo.status?.health && (
|
{repo.status?.health && (
|
||||||
<div className={styles.cardContainer}>
|
<div className={styles.cardContainer}>
|
||||||
<Card noMargin className={styles.card}>
|
<RepositoryHealthCard repo={repo} />
|
||||||
<Card.Heading>
|
|
||||||
<Trans i18nKey="provisioning.repository-overview.health">Health</Trans>
|
|
||||||
</Card.Heading>
|
|
||||||
<Card.Description>
|
|
||||||
<RepositoryHealth health={repo.status?.health} />
|
|
||||||
<Grid columns={12} gap={1} alignItems="baseline">
|
|
||||||
<div className={styles.labelColumn}>
|
|
||||||
<Text color="secondary">
|
|
||||||
<Trans i18nKey="provisioning.repository-overview.status">Status:</Trans>
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
<div className={styles.valueColumn}>
|
|
||||||
<Text variant="body">
|
|
||||||
{status?.health?.healthy
|
|
||||||
? t('provisioning.repository-overview.healthy', 'Healthy')
|
|
||||||
: t('provisioning.repository-overview.unhealthy', 'Unhealthy')}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.labelColumn}>
|
|
||||||
<Text color="secondary">
|
|
||||||
<Trans i18nKey="provisioning.repository-overview.checked">Checked:</Trans>
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
<div className={styles.valueColumn}>
|
|
||||||
<Text variant="body">{formatTimestamp(status?.health?.checked)}</Text>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{!!status?.health?.message?.length && (
|
|
||||||
<>
|
|
||||||
<div className={styles.labelColumn}>
|
|
||||||
<Text color="secondary">
|
|
||||||
<Trans i18nKey="provisioning.repository-overview.messages">Messages:</Trans>
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
<div className={styles.valueColumn}>
|
|
||||||
<Stack gap={1}>
|
|
||||||
{status.health.message.map((msg, idx) => (
|
|
||||||
<Text key={idx} variant="body">
|
|
||||||
{msg}
|
|
||||||
</Text>
|
|
||||||
))}
|
|
||||||
</Stack>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Grid>
|
|
||||||
</Card.Description>
|
|
||||||
<Card.Actions className={styles.actions}>
|
|
||||||
<CheckRepository repository={repo} />
|
|
||||||
</Card.Actions>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={styles.cardContainer}>
|
|
||||||
<Card className={styles.card} noMargin>
|
|
||||||
<Card.Heading>
|
|
||||||
<Trans i18nKey="provisioning.repository-overview.pull-status">Pull status</Trans>
|
|
||||||
</Card.Heading>
|
|
||||||
<Card.Description>
|
|
||||||
<Grid columns={12} gap={1} alignItems="baseline">
|
|
||||||
<div className={styles.labelColumn}>
|
|
||||||
<Text color="secondary">
|
|
||||||
<Trans i18nKey="provisioning.repository-overview.status">Status:</Trans>
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
<div className={styles.valueColumn}>
|
|
||||||
<Text variant="body">{status?.sync.state ?? 'N/A'}</Text>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.labelColumn}>
|
{/* Webhook */}
|
||||||
<Text color="secondary">
|
|
||||||
<Trans i18nKey="provisioning.repository-overview.job-id">Job ID:</Trans>
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
<div className={styles.valueColumn}>
|
|
||||||
<Text variant="body">{status?.sync.job ?? 'N/A'}</Text>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.labelColumn}>
|
|
||||||
<Text color="secondary">
|
|
||||||
<Trans i18nKey="provisioning.repository-overview.last-ref">Last Ref:</Trans>
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
<div className={styles.valueColumn}>
|
|
||||||
<Text variant="body">
|
|
||||||
{status?.sync.lastRef
|
|
||||||
? status.sync.lastRef.substring(0, 7)
|
|
||||||
: t('provisioning.repository-overview.not-available', 'N/A')}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.labelColumn}>
|
|
||||||
<Text color="secondary">
|
|
||||||
<Trans i18nKey="provisioning.repository-overview.started">Started:</Trans>
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
<div className={styles.valueColumn}>
|
|
||||||
<Text variant="body">{formatTimestamp(status?.sync.started)}</Text>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.labelColumn}>
|
|
||||||
<Text color="secondary">
|
|
||||||
<Trans i18nKey="provisioning.repository-overview.finished">Finished:</Trans>
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
<div className={styles.valueColumn}>
|
|
||||||
<Text variant="body">{formatTimestamp(status?.sync.finished)}</Text>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{!!status?.sync?.message?.length && (
|
|
||||||
<>
|
|
||||||
<div className={styles.labelColumn}>
|
|
||||||
<Text color="secondary">
|
|
||||||
<Trans i18nKey="provisioning.repository-overview.messages">Messages:</Trans>
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
<div className={styles.valueColumn}>
|
|
||||||
<MessageList messages={status.sync.message} variant="body" />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Grid>
|
|
||||||
</Card.Description>
|
|
||||||
<Card.Actions className={styles.actions}>
|
|
||||||
<SyncRepository repository={repo} />
|
|
||||||
</Card.Actions>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
{repo.status?.webhook && (
|
{repo.status?.webhook && (
|
||||||
<div className={styles.cardContainer}>
|
<div className={styles.cardContainer}>
|
||||||
<Card noMargin className={styles.card}>
|
<Card noMargin className={styles.card}>
|
||||||
@@ -251,6 +128,16 @@ export function RepositoryOverview({ repo }: { repo: Repository }) {
|
|||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Pull status */}
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
styles.pullStatusCard,
|
||||||
|
repo.status?.webhook ? styles.pullStatusCardLgSpan3 : styles.pullStatusCardLgSpan2
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<RepositoryPullStatusCard repo={repo} />
|
||||||
|
</div>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{/* job status is not ready for Cloud yet */}
|
{/* job status is not ready for Cloud yet */}
|
||||||
@@ -292,6 +179,23 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
valueColumn: css({
|
valueColumn: css({
|
||||||
gridColumn: 'span 9',
|
gridColumn: 'span 9',
|
||||||
}),
|
}),
|
||||||
|
pullStatusCard: css({
|
||||||
|
gridColumn: 'span 2',
|
||||||
|
|
||||||
|
[theme.breakpoints.down('lg')]: {
|
||||||
|
gridColumn: 'span 2',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
pullStatusCardLgSpan3: css({
|
||||||
|
[theme.breakpoints.down('xxl')]: {
|
||||||
|
gridColumn: 'span 3',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
pullStatusCardLgSpan2: css({
|
||||||
|
[theme.breakpoints.down('xxl')]: {
|
||||||
|
gridColumn: 'span 2',
|
||||||
|
},
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
|
||||||
|
import { t, Trans } from '@grafana/i18n';
|
||||||
|
import { Badge, Card, Grid, Text, TextLink, useStyles2 } from '@grafana/ui';
|
||||||
|
import { Repository } from 'app/api/clients/provisioning/v0alpha1';
|
||||||
|
|
||||||
|
import { MessageList } from '../Shared/MessageList';
|
||||||
|
import { getRepoCommitUrl } from '../utils/git';
|
||||||
|
import { getStatusColor, getStatusIcon } from '../utils/repositoryStatus';
|
||||||
|
import { formatTimestamp } from '../utils/time';
|
||||||
|
|
||||||
|
import { SyncRepository } from './SyncRepository';
|
||||||
|
|
||||||
|
export function RepositoryPullStatusCard({ repo }: { repo: Repository }) {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
const status = repo.status;
|
||||||
|
const statusColor = getStatusColor(status?.sync.state);
|
||||||
|
const statusIcon = getStatusIcon(status?.sync.state);
|
||||||
|
|
||||||
|
const { url: lastCommitUrl, hasUrl } = getRepoCommitUrl(repo.spec, status?.sync.lastRef);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card noMargin>
|
||||||
|
<Card.Heading>
|
||||||
|
<Trans i18nKey="provisioning.repository-overview.pull-status">Pull status</Trans>
|
||||||
|
</Card.Heading>
|
||||||
|
<Card.Description>
|
||||||
|
<Grid columns={3} gap={1} alignItems="baseline">
|
||||||
|
{/* Status */}
|
||||||
|
<Text color="secondary">
|
||||||
|
<Trans i18nKey="provisioning.repository-overview.status">Status:</Trans>
|
||||||
|
</Text>
|
||||||
|
<div className={styles.spanTwo}>
|
||||||
|
<Badge icon={statusIcon} color={statusColor} text={status?.sync.state ?? 'N/A'} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Job ID */}
|
||||||
|
<Text color="secondary">
|
||||||
|
<Trans i18nKey="provisioning.repository-overview.job-id">Job ID:</Trans>
|
||||||
|
</Text>
|
||||||
|
<div className={styles.spanTwo}>
|
||||||
|
<Text variant="body">{status?.sync.job ?? 'N/A'}</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Last Ref */}
|
||||||
|
<Text color="secondary">
|
||||||
|
<Trans i18nKey="provisioning.repository-overview.last-ref">Last Ref:</Trans>
|
||||||
|
</Text>
|
||||||
|
<div className={styles.spanTwo}>
|
||||||
|
{hasUrl && lastCommitUrl ? (
|
||||||
|
<TextLink href={lastCommitUrl} external>
|
||||||
|
<Text variant="body">
|
||||||
|
{status?.sync.lastRef
|
||||||
|
? status.sync.lastRef.substring(0, 7)
|
||||||
|
: t('provisioning.repository-overview.not-available', 'N/A')}
|
||||||
|
</Text>
|
||||||
|
</TextLink>
|
||||||
|
) : (
|
||||||
|
<Text variant="body">
|
||||||
|
{status?.sync.lastRef
|
||||||
|
? status.sync.lastRef.substring(0, 7)
|
||||||
|
: t('provisioning.repository-overview.not-available', 'N/A')}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Text color="secondary">
|
||||||
|
<Trans i18nKey="provisioning.repository-overview.finished">Last successful pull:</Trans>
|
||||||
|
</Text>
|
||||||
|
<div className={styles.spanTwo}>
|
||||||
|
<Text variant="body">{formatTimestamp(status?.sync.finished)}</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!!status?.sync?.message?.length && (
|
||||||
|
<>
|
||||||
|
<Text color="secondary">
|
||||||
|
<Trans i18nKey="provisioning.repository-overview.messages">Messages:</Trans>
|
||||||
|
</Text>
|
||||||
|
<div className={styles.spanTwo}>
|
||||||
|
<MessageList messages={status.sync.message} variant="body" />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
</Card.Description>
|
||||||
|
<Card.Actions>
|
||||||
|
<SyncRepository repository={repo} />
|
||||||
|
</Card.Actions>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStyles = () => {
|
||||||
|
return {
|
||||||
|
spanTwo: css({
|
||||||
|
gridColumn: 'span 2',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -70,3 +70,40 @@ export const getRepoHrefForProvider = (spec?: RepositorySpec) => {
|
|||||||
export function getHasTokenInstructions(type: RepoType): type is InstructionAvailability {
|
export function getHasTokenInstructions(type: RepoType): type is InstructionAvailability {
|
||||||
return type === 'github' || type === 'gitlab' || type === 'bitbucket';
|
return type === 'github' || type === 'gitlab' || type === 'bitbucket';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getRepoCommitUrl(spec?: RepositorySpec, commit?: string) {
|
||||||
|
let url: string | undefined = undefined;
|
||||||
|
let hasUrl = false;
|
||||||
|
|
||||||
|
if (!spec || !spec.type || !commit) {
|
||||||
|
return { hasUrl, url };
|
||||||
|
}
|
||||||
|
|
||||||
|
const gitType = spec.type;
|
||||||
|
|
||||||
|
// local repositories don't have a URL
|
||||||
|
if (gitType !== 'local' && commit) {
|
||||||
|
switch (gitType) {
|
||||||
|
case 'github':
|
||||||
|
if (spec.github?.url) {
|
||||||
|
url = `${spec.github.url}/commit/${commit}`;
|
||||||
|
hasUrl = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'gitlab':
|
||||||
|
if (spec.gitlab?.url) {
|
||||||
|
url = `${spec.gitlab.url}/-/commit/${commit}`;
|
||||||
|
hasUrl = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'bitbucket':
|
||||||
|
if (spec.bitbucket?.url) {
|
||||||
|
url = `${spec.bitbucket.url}/commits/${commit}`;
|
||||||
|
hasUrl = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { hasUrl, url };
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { BadgeColor, IconName } from '@grafana/ui';
|
||||||
|
import { SyncStatus } from 'app/api/clients/provisioning/v0alpha1';
|
||||||
|
|
||||||
|
export interface RepositoryStatus {
|
||||||
|
color: BadgeColor;
|
||||||
|
text: string;
|
||||||
|
icon: IconName;
|
||||||
|
tooltip?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getStatusColor = (state?: SyncStatus['state']) => {
|
||||||
|
switch (state) {
|
||||||
|
case 'success':
|
||||||
|
return 'green';
|
||||||
|
case 'working':
|
||||||
|
return 'blue';
|
||||||
|
case 'warning':
|
||||||
|
return 'orange';
|
||||||
|
case 'pending':
|
||||||
|
return 'darkgrey';
|
||||||
|
case 'error':
|
||||||
|
return 'red';
|
||||||
|
default:
|
||||||
|
return 'darkgrey';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getStatusIcon = (state?: SyncStatus['state']): IconName => {
|
||||||
|
switch (state) {
|
||||||
|
case 'success':
|
||||||
|
return 'check';
|
||||||
|
case 'working':
|
||||||
|
case 'warning':
|
||||||
|
return 'exclamation-triangle';
|
||||||
|
case 'pending':
|
||||||
|
return 'spinner';
|
||||||
|
case 'error':
|
||||||
|
return 'exclamation-triangle';
|
||||||
|
default:
|
||||||
|
return 'exclamation-triangle';
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -11584,6 +11584,7 @@
|
|||||||
"active-jobs": "active jobs",
|
"active-jobs": "active jobs",
|
||||||
"column-action": "Action",
|
"column-action": "Action",
|
||||||
"column-duration": "Duration",
|
"column-duration": "Duration",
|
||||||
|
"column-job-id": "Job ID",
|
||||||
"column-message": "Message",
|
"column-message": "Message",
|
||||||
"column-started": "Started",
|
"column-started": "Started",
|
||||||
"column-status": "Status",
|
"column-status": "Status",
|
||||||
@@ -11602,12 +11603,6 @@
|
|||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"view": "View"
|
"view": "View"
|
||||||
},
|
},
|
||||||
"repository-health": {
|
|
||||||
"details": "Details:",
|
|
||||||
"no-errors-found": "No errors found",
|
|
||||||
"title-repository-is-healthy": "Repository is healthy",
|
|
||||||
"title-repository-is-unhealthy": "Repository is unhealthy"
|
|
||||||
},
|
|
||||||
"repository-link": {
|
"repository-link": {
|
||||||
"delete-or-move-job": {
|
"delete-or-move-job": {
|
||||||
"compare-branch": "Compare branch",
|
"compare-branch": "Compare branch",
|
||||||
@@ -11622,7 +11617,7 @@
|
|||||||
},
|
},
|
||||||
"repository-overview": {
|
"repository-overview": {
|
||||||
"checked": "Checked:",
|
"checked": "Checked:",
|
||||||
"finished": "Finished:",
|
"finished": "Last successful pull:",
|
||||||
"health": "Health",
|
"health": "Health",
|
||||||
"healthy": "Healthy",
|
"healthy": "Healthy",
|
||||||
"job-id": "Job ID:",
|
"job-id": "Job ID:",
|
||||||
@@ -11631,7 +11626,6 @@
|
|||||||
"not-available": "N/A",
|
"not-available": "N/A",
|
||||||
"pull-status": "Pull status",
|
"pull-status": "Pull status",
|
||||||
"resources": "Resources",
|
"resources": "Resources",
|
||||||
"started": "Started:",
|
|
||||||
"status": "Status:",
|
"status": "Status:",
|
||||||
"unhealthy": "Unhealthy",
|
"unhealthy": "Unhealthy",
|
||||||
"view-folder": "View Folder",
|
"view-folder": "View Folder",
|
||||||
|
|||||||
Reference in New Issue
Block a user