mirror of
https://github.com/grafana/grafana.git
synced 2025-07-31 06:22:56 +08:00

* Update the generator to include version * Add versioned APIs * Update imports * Prettier
301 lines
12 KiB
TypeScript
301 lines
12 KiB
TypeScript
import { css } from '@emotion/css';
|
|
import { useMemo } from 'react';
|
|
|
|
import { Trans, t } from '@grafana/i18n';
|
|
import { Box, Card, CellProps, Grid, InteractiveTable, LinkButton, Stack, Text, useStyles2 } from '@grafana/ui';
|
|
import { Repository, ResourceCount } from 'app/api/clients/provisioning/v0alpha1';
|
|
|
|
import { RecentJobs } from '../Job/RecentJobs';
|
|
import { formatTimestamp } from '../utils/time';
|
|
|
|
import { CheckRepository } from './CheckRepository';
|
|
import { RepositoryHealth } from './RepositoryHealth';
|
|
import { SyncRepository } from './SyncRepository';
|
|
|
|
type StatCell<T extends keyof ResourceCount = keyof ResourceCount> = CellProps<ResourceCount, ResourceCount[T]>;
|
|
|
|
function getColumnCount(hasWebhook: boolean): 3 | 4 {
|
|
return hasWebhook ? 4 : 3;
|
|
}
|
|
|
|
export function RepositoryOverview({ repo }: { repo: Repository }) {
|
|
const styles = useStyles2(getStyles);
|
|
|
|
const status = repo.status;
|
|
const webhookURL = getWebhookURL(repo);
|
|
const columns = getColumnCount(Boolean(repo.status?.webhook));
|
|
|
|
const resourceColumns = useMemo(
|
|
() => [
|
|
{
|
|
id: 'Resource',
|
|
header: 'Resource Type',
|
|
cell: ({ row: { original } }: StatCell<'resource'>) => {
|
|
return <span>{original.resource}</span>;
|
|
},
|
|
size: 'auto',
|
|
},
|
|
{
|
|
id: 'count',
|
|
header: 'Count',
|
|
cell: ({ row: { original } }: StatCell<'count'>) => {
|
|
return <span>{original.count}</span>;
|
|
},
|
|
size: 100,
|
|
},
|
|
],
|
|
[]
|
|
);
|
|
return (
|
|
<Box padding={2}>
|
|
<Stack direction="column" gap={2}>
|
|
<Grid columns={columns} gap={2}>
|
|
<div className={styles.cardContainer}>
|
|
<Card className={styles.card}>
|
|
<Card.Heading>
|
|
<Trans i18nKey="provisioning.repository-overview.resources">Resources</Trans>
|
|
</Card.Heading>
|
|
<Card.Description>
|
|
{repo.status?.stats ? (
|
|
<InteractiveTable
|
|
columns={resourceColumns}
|
|
data={repo.status.stats}
|
|
getRowId={(r: ResourceCount) => `${r.group}-${r.resource}`}
|
|
/>
|
|
) : null}
|
|
</Card.Description>
|
|
<Card.Actions className={styles.actions}>
|
|
<LinkButton fill="outline" size="md" href={getFolderURL(repo)} icon="folder-open">
|
|
<Trans i18nKey="provisioning.repository-overview.view-folder">View Folder</Trans>
|
|
</LinkButton>
|
|
</Card.Actions>
|
|
</Card>
|
|
</div>
|
|
{repo.status?.health && (
|
|
<div className={styles.cardContainer}>
|
|
<Card className={styles.card}>
|
|
<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 className={styles.cardContainer}>
|
|
<Card className={styles.card}>
|
|
<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}>
|
|
<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}>
|
|
<Stack gap={1}>
|
|
{status.sync.message.map((msg, idx) => (
|
|
<Text key={idx} variant="body">
|
|
{msg}
|
|
</Text>
|
|
))}
|
|
</Stack>
|
|
</div>
|
|
</>
|
|
)}
|
|
</Grid>
|
|
</Card.Description>
|
|
<Card.Actions className={styles.actions}>
|
|
<SyncRepository repository={repo} />
|
|
</Card.Actions>
|
|
</Card>
|
|
</div>
|
|
{repo.status?.webhook && (
|
|
<div className={styles.cardContainer}>
|
|
<Card className={styles.card}>
|
|
<Card.Heading>
|
|
<Trans i18nKey="provisioning.repository-overview.webhook">Webhook</Trans>
|
|
</Card.Heading>
|
|
<Card.Description>
|
|
<Grid columns={12} gap={1} alignItems="baseline">
|
|
<div className={styles.labelColumn}>
|
|
<Text color="secondary">
|
|
<Trans i18nKey="provisioning.repository-overview.webhook-id">ID:</Trans>
|
|
</Text>
|
|
</div>
|
|
<div className={styles.valueColumn}>
|
|
<Text variant="body">{status?.webhook?.id ?? 'N/A'}</Text>
|
|
</div>
|
|
<div className={styles.labelColumn}>
|
|
<Text color="secondary">
|
|
<Trans i18nKey="provisioning.repository-overview.webhook-events">Events:</Trans>
|
|
</Text>
|
|
</div>
|
|
<div className={styles.valueColumn}>
|
|
<Text variant="body">{status?.webhook?.subscribedEvents?.join(', ') ?? 'N/A'}</Text>
|
|
</div>
|
|
<div className={styles.labelColumn}>
|
|
<Text color="secondary">
|
|
<Trans i18nKey="provisioning.repository-overview.webhook-last-event">Last Event:</Trans>
|
|
</Text>
|
|
</div>
|
|
<div className={styles.valueColumn}>
|
|
<Text variant="body">{formatTimestamp(status?.webhook?.lastEvent)}</Text>
|
|
</div>
|
|
</Grid>
|
|
</Card.Description>
|
|
{webhookURL && (
|
|
<Card.Actions className={styles.actions}>
|
|
<LinkButton fill="outline" href={webhookURL} icon="external-link-alt">
|
|
<Trans i18nKey="provisioning.repository-overview.webhook-url">View Webhook</Trans>
|
|
</LinkButton>
|
|
</Card.Actions>
|
|
)}
|
|
</Card>
|
|
</div>
|
|
)}
|
|
</Grid>
|
|
<div className={styles.cardContainer}>
|
|
<RecentJobs repo={repo} />
|
|
</div>
|
|
</Stack>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
function getFolderURL(repo: Repository) {
|
|
if (repo.spec?.sync.target === 'folder') {
|
|
return `/dashboards/f/${repo.metadata?.name}`;
|
|
}
|
|
return '/dashboards';
|
|
}
|
|
|
|
const getStyles = () => {
|
|
return {
|
|
cardContainer: css({
|
|
height: '100%',
|
|
}),
|
|
card: css({
|
|
height: '100%',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
}),
|
|
actions: css({
|
|
marginTop: 'auto',
|
|
}),
|
|
labelColumn: css({
|
|
gridColumn: 'span 3',
|
|
}),
|
|
valueColumn: css({
|
|
gridColumn: 'span 9',
|
|
}),
|
|
};
|
|
};
|
|
|
|
function getWebhookURL(repo: Repository) {
|
|
const { status, spec } = repo;
|
|
if (spec?.type === 'github' && status?.webhook?.url && spec.github?.url) {
|
|
return `${spec.github.url}/settings/hooks/${status.webhook?.id}`;
|
|
}
|
|
return undefined;
|
|
}
|